/* ==========================================================================
   Powerdrop - Sri Lanka grid watch
   --------------------------------------------------------------------------
   Design direction: a night-lights map. A district with power glows the
   colour of a sodium-vapour street lamp; a district that has lost power goes
   dark with a hot filament-red rim. The alarm state is the absence of light,
   which is what an outage actually looks like from the ground.

   Layout: one wall board (the island) and one instrument rack (readouts).
   ========================================================================== */

:root {
    /* Palette */
    --night: #060a11;
    /* Nav */
    --nav-h: 4.4rem;   /* MUST match the real .app-nav footer height (see below) */
    --warning: #fbbf24;
    --panel: #0d141f;
    --rail: #111a27;
    --rail-high: #16202e;
    --hairline: #1d2a3b;
    --sodium: #ffb765;
    --sodium-deep: #d98f45;
    --sodium-pale: #ffd7a3;
    --filament: #ff4d3d;
    --porcelain: #e8eff8;
    --graphite: #7e90a8;
    --graphite-dim: #55657c;

    /* District fills */
    --d-out-fill: #101825;
    --d-none-fill: #131c29;
    --d-none-stroke: #223045;
    --d-partial-fill: #7a5530;

    /* Night-lights map */
    --coast: #37a6d8;
    --land: #080d15;
    --land-lit: #0e1826;
    --land-partial: #17131b;
    --land-out: #0d0608;
    --land-none: #0a0f17;
    --land-edge: #1a2636;
    --light-hot: #fff4e0;
    --light-warm: #ffc078;

    /* Type */
    --display: "Poppins", "Segoe UI", system-ui, sans-serif;
    --body: "Archivo", "Segoe UI", system-ui, sans-serif;
    --data: "IBM Plex Mono", "Cascadia Mono", Consolas, monospace;

    /* Metrics */
    --rack-width: 25rem;
    --radius: 4px;
    --radius-lg: 8px;
    --ease: cubic-bezier(0.22, 0.8, 0.24, 1);
}

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

html {
    height: 100%;
}

body {
    /* min-height (NOT height:100%) so the page grows with its content. With a
       fixed height:100%, taller-than-screen content overflows the body box and
       the padding-bottom below ends up mid-page — leaving the footer to cover
       the last rows. min-height lets the padding-bottom sit under the content
       where it belongs, clearing the seated bottom nav on every page. */
    min-height: 100%;
}

body {
    margin: 0;
    background: var(--night);
    color: var(--porcelain);
    font-family: var(--body);
    font-size: 15px;
    line-height: 1.45;
    -webkit-font-smoothing: antialiased;
    /* NOTE: bottom-nav clearance padding lives in the scoped
       `body:has(.app-nav)` rule further down — pages without a footer must
       NOT reserve that space (it left an empty band at the bottom). */
}

h1,
h2,
h3,
p,
dl,
dd,
dt,
ol,
ul,
figure {
    margin: 0;
}

ol,
ul {
    padding: 0;
    list-style: none;
}

button {
    font: inherit;
    color: inherit;
    cursor: pointer;
}

:focus-visible {
    outline: 2px solid var(--sodium);
    outline-offset: 2px;
}

::selection {
    background: var(--sodium);
    color: var(--night);
}

/* Scrollbars kept in the instrument palette rather than the OS default. */
.zones__list,
.feed__list,
.chips,
.sheet__body {
    scrollbar-width: thin;
    scrollbar-color: var(--hairline) transparent;
}

.zones__list::-webkit-scrollbar,
.feed__list::-webkit-scrollbar,
.chips::-webkit-scrollbar,
.sheet__body::-webkit-scrollbar {
    width: 8px;
    height: 8px;
}

.zones__list::-webkit-scrollbar-thumb,
.feed__list::-webkit-scrollbar-thumb,
.chips::-webkit-scrollbar-thumb,
.sheet__body::-webkit-scrollbar-thumb {
    background: var(--hairline);
    border-radius: 99px;
}

.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* ==========================================================================
   Shell
   ========================================================================== */

.shell {
    display: flex;
    flex-direction: column;
    min-height: 100dvh;
}

/* ==========================================================================
   Status bar
   ========================================================================== */

.bar {
    position: sticky;
    top: 0;
    z-index: 40;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    padding: 0.75rem 1.25rem;
    background: var(--panel);
    border-bottom: 1px solid var(--hairline);
}

.bar__brand {
    display: flex;
    align-items: baseline;
    gap: 0.7rem;
    min-width: 0;
}

.bar__mark {
    align-self: center;
    display: grid;
    place-items: center;
    width: 1.85rem;
    height: 1.85rem;
    flex: none;
    color: var(--night);
    background: var(--sodium);
    border-radius: var(--radius);
    box-shadow: 0 0 18px rgba(255, 183, 101, 0.35);
}

.bar__mark svg {
    width: 1rem;
    height: 1rem;
    display: block;
}

.bar__name {
    font-family: var(--display);
    font-size: 1.4rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    line-height: 1;
}

.bar__tag {
    font-family: var(--display);
    font-size: 0.8rem;
    font-weight: 500;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--graphite);
    white-space: nowrap;
}

.bar__right {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    flex-wrap: wrap;
}

.bar__clock {
    font-family: var(--data);
    font-size: 0.9rem;
    font-variant-numeric: tabular-nums;
    color: var(--graphite);
    letter-spacing: 0.04em;
}

.bar__who {
    font-family: var(--data);
    font-size: 0.78rem;
    color: var(--sodium);
    padding: 0.2rem 0.5rem;
    border: 1px solid rgba(255, 183, 101, 0.3);
    border-radius: 99px;
}

.bar__who[hidden] {
    display: none;
}

/* Live indicator */
.pulse {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    font-family: var(--display);
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--graphite);
}

.pulse i {
    width: 7px;
    height: 7px;
    border-radius: 99px;
    background: var(--graphite-dim);
}

.pulse[data-state="live"] {
    color: var(--sodium);
}

.pulse[data-state="live"] i {
    background: var(--sodium);
    animation: beat 2.4s ease-in-out infinite;
}

.pulse[data-state="down"] {
    color: var(--filament);
}

.pulse[data-state="down"] i {
    background: var(--filament);
}

@keyframes beat {
    0%,
    100% {
        box-shadow: 0 0 0 0 rgba(255, 183, 101, 0.55);
    }
    50% {
        box-shadow: 0 0 0 5px rgba(255, 183, 101, 0);
    }
}

/* ==========================================================================
   Buttons and fields
   ========================================================================== */

.btn {
    font-family: var(--display);
    font-size: 0.85rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    padding: 0.5rem 0.95rem;
    border-radius: var(--radius);
    border: 1px solid var(--hairline);
    background: var(--rail);
    color: var(--porcelain);
    transition: border-color 0.18s var(--ease), background 0.18s var(--ease),
        color 0.18s var(--ease);
}

.btn:hover {
    border-color: var(--graphite-dim);
    background: var(--rail-high);
}

.btn--primary {
    background: var(--sodium);
    border-color: var(--sodium);
    color: #2a1a08;
}

.btn--primary:hover {
    background: var(--sodium-pale);
    border-color: var(--sodium-pale);
    color: #2a1a08;
}

.btn--wide {
    width: 100%;
    padding-block: 0.65rem;
}

.btn[disabled] {
    opacity: 0.5;
    cursor: not-allowed;
}

.field {
    font-family: var(--body);
    font-size: 0.85rem;
    color: var(--porcelain);
    background: var(--night);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    padding: 0.42rem 0.6rem;
    min-width: 0;
    transition: border-color 0.18s var(--ease);
}

.field:hover,
.field:focus {
    border-color: var(--graphite-dim);
}

.field::placeholder {
    color: var(--graphite-dim);
}

select.field {
    appearance: none;
    padding-right: 1.5rem;
    background-image: linear-gradient(45deg, transparent 50%, var(--graphite) 50%),
        linear-gradient(135deg, var(--graphite) 50%, transparent 50%);
    background-position: right 0.7rem center, right 0.45rem center;
    background-size: 5px 5px, 5px 5px;
    background-repeat: no-repeat;
}

/* ==========================================================================
   Board: wall board + instrument rack
   ========================================================================== */

.board {
    flex: 1;
    min-height: 0;
    display: grid;
    grid-template-columns: minmax(0, 1fr) var(--rack-width);
    gap: 1px;
    background: var(--hairline);
}

/* ==========================================================================
   The wall board - signature element
   ========================================================================== */

.wall {
    position: relative;
    overflow: hidden;
    /* Deep-blue field, VERTICAL gradient so the left & right edges are exactly
       the same blue (no darker side bands) — top slightly lighter, easing down. */
    background: linear-gradient(180deg, #102a45 0%, #0c2038 40%, #091628 72%, #071322 100%);
}

/* The upper panel holds the SVG map and all its absolutely-positioned
   overlays (reading card, tools, chips rail). Starts visible; gets
   .is-behind when the Leaflet detail map is shown. */
.wall__svgpanel {
    position: absolute;
    inset: 0;
    z-index: 2;
    overflow: hidden;
    /* Same vertical blue gradient as .wall — identical on both side edges. */
    background: linear-gradient(180deg, #102a45 0%, #0c2038 40%, #091628 72%, #071322 100%);
    transition: opacity 0.45s var(--ease);
}

.wall__svgpanel.is-behind {
    opacity: 0;
    pointer-events: none;
}

/* Faint scanning sweep, so the dark ocean never feels completely static. */
.wall__svgpanel::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: radial-gradient(
        120% 80% at 50% 42%,
        rgba(55, 166, 216, 0.06),
        transparent 60%
    );
}

/* Leaflet detail panel — real map, GPS-exact transformer dots, zoom/pan.
   Hidden by default; gets .is-active when a district is selected. */
.wall__detail {
    position: absolute;
    inset: 0;
    z-index: 1;
    background: var(--night);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.45s var(--ease);
}

.wall__detail.is-active {
    opacity: 1;
    pointer-events: auto;
    z-index: 3;
}

/* Back button: sits below the Leaflet zoom controls, top-left corner */
.detail__back {
    position: absolute;
    top: 5.5rem;      /* below the two zoom buttons with breathing room */
    left: 0.65rem;
    z-index: 500;
    font-family: var(--display);
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--sodium);
    background: rgba(9, 14, 22, 0.92);
    border: 1px solid rgba(255, 183, 101, 0.35);
    border-radius: var(--radius);
    padding: 0.32rem 0.65rem;
    backdrop-filter: blur(6px);
    box-shadow: 0 0 10px rgba(255, 183, 101, 0.15);
    transition: color 0.18s var(--ease), border-color 0.18s var(--ease),
        box-shadow 0.18s var(--ease);
}

.detail__back:hover {
    color: var(--sodium-pale);
    border-color: rgba(255, 183, 101, 0.7);
    box-shadow: 0 0 16px rgba(255, 183, 101, 0.35);
}

/* Pulsing red glow for OFF transformer markers (Leaflet) */
@keyframes dotFault {
    0%, 100% { box-shadow: 0 0 8px rgba(255,77,61,0.85), 0 0 20px rgba(255,77,61,0.55); }
    50%       { box-shadow: 0 0 14px rgba(255,90,70,1),  0 0 32px rgba(255,60,45,0.9); }
}

.lf-dot--off {
    animation: dotFault 2.4s ease-in-out infinite;
}

/* Transformer dots on SVG overview map */
@keyframes svgDotFault {
    0%, 100% { opacity: 0.8; }
    50%       { opacity: 1; }
}

#svgDotsLayer circle[data-status="OFF"] {
    animation: svgDotFault 2.4s ease-in-out infinite;
}

#leafletMap {
    width: 100%;
    height: 100%;
}

.detail__label {
    position: absolute;
    bottom: 0.55rem;
    left: 0.7rem;
    z-index: 500;
    font-family: var(--display);
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--graphite);
    background: rgba(9, 14, 22, 0.82);
    padding: 0.2rem 0.5rem;
    border-radius: 99px;
    border: 1px solid var(--hairline);
    pointer-events: none;
}

/* --- Leaflet dark-theme overrides --------------------------------------- */

.leaflet-container {
    background: #060a11;
    font-family: var(--body);
}

/* Attribution badge */
.leaflet-control-attribution {
    background: rgba(6, 10, 17, 0.75) !important;
    color: var(--graphite-dim) !important;
    font-size: 0.6rem !important;
}

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

/* Zoom controls */
.leaflet-bar a {
    background: rgba(13, 20, 31, 0.88) !important;
    color: var(--graphite) !important;
    border-color: var(--hairline) !important;
}

.leaflet-bar a:hover {
    background: var(--rail-high) !important;
    color: var(--sodium) !important;
}

/* Marker popups */
.lf-popup-wrap .leaflet-popup-content-wrapper {
    background: var(--panel);
    color: var(--porcelain);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6);
    padding: 0;
}

.lf-popup-wrap .leaflet-popup-content {
    margin: 0.6rem 0.75rem;
}

.lf-popup-wrap .leaflet-popup-tip {
    background: var(--panel);
}

.lf-popup-wrap .leaflet-popup-close-button {
    color: var(--graphite) !important;
}

.lf-popup strong {
    font-size: 0.88rem;
    font-weight: 600;
    display: block;
}

.lf-popup__meta {
    display: block;
    font-family: var(--data);
    font-size: 0.68rem;
    color: var(--graphite-dim);
    margin-top: 0.15rem;
}

.lf-popup__status {
    display: inline-block;
    font-family: var(--display);
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    margin-top: 0.35rem;
    padding: 0.18rem 0.45rem;
    border-radius: 99px;
}

.lf-popup__status--on {
    color: #2a1a08;
    background: var(--sodium);
}

.lf-popup__status--off {
    color: #fff;
    background: var(--filament);
}

.wall__map {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    padding: 1.25rem 1rem 4.5rem;
    overflow: visible;
    /* A tap must never leave the browser's grey selection box over a district. */
    -webkit-tap-highlight-color: transparent;
    -webkit-user-select: none;
    user-select: none;
}

/* Ocean and coastline ------------------------------------------------------ */

/* The blurred silhouette of the island, sitting behind the land, bleeds a
   cyan halo out past the coast the way a lit shoreline does from orbit. */
#auraLayer path {
    fill: var(--coast);
    stroke: var(--coast);
    stroke-width: 1.4;
    opacity: 0.5;
}

/* City lights are decorative and must never intercept a click meant for the
   district beneath them. */
#lightLayer,
#lightAmbient,
#waveLayer,
#auraLayer {
    pointer-events: none;
}

/* Sea ripple: the island silhouette swells outward and fades, so a soft wave
   keeps rolling off the coast into the dark water. */
.wave {
    transform-box: view-box;
    transform-origin: 50% 49%;
    opacity: 0;
    animation: seaRipple 6.5s ease-out infinite;
}

.wave path {
    fill: var(--coast);
    stroke: none;
}

.wave--2 {
    animation-delay: -3.25s;
}

@keyframes seaRipple {
    0% {
        transform: scale(1);
        opacity: 0.24;
    }
    60% {
        opacity: 0.06;
    }
    100% {
        transform: scale(1.085);
        opacity: 0;
    }
}

#lightLayer {
    animation: breathe 9s ease-in-out infinite;
}

@keyframes breathe {
    0%,
    100% {
        opacity: 0.92;
    }
    50% {
        opacity: 1;
    }
}

/* Ambient glow scattered across a populated district. */
.amb-d {
    transition: opacity 0.6s var(--ease);
}

.amb-d circle {
    fill: url(#dotGlow);
    /* PERF: the ambient light field holds ~1000 dots. Animating ALL of them
       (1000+ simultaneous opacity tweens on gradient-filled circles) is what
       made the app lag and scroll in 4-second jerks on phones. Every dot stays
       visible at its own randomised brightness (so the field looks identical),
       but only a scattered ~1-in-10 actually twinkles — see the rule below.
       This cuts animating nodes ~10x with no visible change. */
    opacity: var(--tw-lo, 0.6);
}

/* The scattered subset that still twinkles. :nth-of-type keeps the randomised
   per-dot --tw-dur / --tw-delay, so the motion stays organic, just far rarer. */
.amb-d circle:nth-of-type(10n + 1) {
    animation: twinkle var(--tw-dur, 4s) ease-in-out var(--tw-delay, 0s) infinite;
}

/* Each light breathes between its own dim floor and full brightness, so the
   field reads as real lamps rather than static dots. */
@keyframes twinkle {
    0%,
    100% {
        opacity: var(--tw-lo, 0.5);
    }
    50% {
        opacity: 1;
    }
}

.amb-d[data-state="lit"] {
    opacity: 0.72;
}

.amb-d[data-state="partial"] {
    opacity: 0.52;
}

.amb-d[data-state="dark"] {
    opacity: 0;
}

.amb-d[data-state="none"] {
    opacity: 0.2;
}

/* One bright, bloomed core per transformer zone, placed at its coordinates. */
.core {
    transition: opacity 0.6s var(--ease);
}

.core__warm {
    fill: var(--light-warm);
}

.core__hot {
    fill: var(--light-hot);
}

.core[data-status="OFF"] {
    opacity: 0;
}

/* --- district geometry ---------------------------------------------------- */

.district-path {
    fill: var(--land);
    stroke: var(--land-edge);
    stroke-width: 0.45;
    stroke-linejoin: round;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: fill 0.5s var(--ease), stroke 0.4s var(--ease),
        stroke-width 0.18s var(--ease);
}

.district-path[data-state="lit"] {
    fill: var(--land-lit);
    stroke: #2a3d54;
}

.district-path[data-state="partial"] {
    fill: var(--land-partial);
    stroke: var(--sodium-deep);
}

.district-path[data-state="dark"] {
    fill: var(--land-out);
    stroke: var(--filament);
    animation: fault 3.6s ease-in-out infinite;
}

/* An outed district throbs: its red border blooms bright, sinks almost to
   nothing, then blooms again - a wound on the grid you cannot miss. */
@keyframes fault {
    0%,
    100% {
        stroke-opacity: 0.35;
        filter: drop-shadow(0 0 2px rgba(255, 77, 61, 0.35));
    }
    50% {
        stroke-opacity: 1;
        filter: drop-shadow(0 0 7px rgba(255, 90, 70, 0.9))
            drop-shadow(0 0 18px rgba(255, 60, 45, 0.55));
    }
}

.district-path[data-state="none"] {
    fill: var(--land-none);
    stroke: var(--d-none-stroke);
}

/* Repair crew active in this district — amber beacon overrides any fault colour */
@keyframes repairBeacon {
    0%, 100% {
        stroke-opacity: 0.55;
        filter: drop-shadow(0 0 3px rgba(251, 191, 36, 0.4));
    }
    50% {
        stroke-opacity: 1;
        filter: drop-shadow(0 0 9px rgba(251, 191, 36, 1))
            drop-shadow(0 0 22px rgba(251, 191, 36, 0.55));
    }
}

.district-path[data-repairing="true"] {
    stroke: var(--warning);
    stroke-width: 0.9;
    animation: repairBeacon 2.8s ease-in-out infinite;
}

.district-path:hover {
    stroke-width: 0.9;
}

.district-path.is-selected {
    stroke: var(--porcelain);
    stroke-width: 1.1;
}

.district-path:focus,
.district-path:focus-visible {
    outline: none;
    stroke: var(--porcelain);
    stroke-width: 1.4;
}

/* Animated hover outline, drawn above every fill so a shared border never
   hides behind a neighbour. app.js sets the dash length per district. */
#districtHighlightOverlay {
    fill: none;
    stroke: var(--light-hot);
    stroke-width: 1.5;
    stroke-linejoin: round;
    stroke-linecap: round;
    pointer-events: none;
    opacity: 0;
    filter: drop-shadow(0 0 2px #ffffff)
        drop-shadow(0 0 6px var(--light-warm))
        drop-shadow(0 0 15px rgba(255, 183, 101, 0.75))
        drop-shadow(0 0 28px rgba(255, 146, 60, 0.45));
    transition: opacity 0.25s var(--ease);
}

#districtHighlightOverlay.is-visible {
    opacity: 1;
    animation: outlineGlow 2.6s var(--ease) infinite;
}

/* A slow breath on the glow so a lit border feels alive, not painted on. */
@keyframes outlineGlow {
    0%,
    100% {
        opacity: 0.88;
    }
    50% {
        opacity: 1;
    }
}

/* Page-load sequence: the island comes up to power. */
@keyframes energise {
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}

.wall__map.is-energising .district-path {
    animation: energise 0.5s var(--ease) both;
    animation-delay: calc(var(--i, 0) * 20ms);
}

/* --- map furniture ------------------------------------------------------- */

.wall__tools {
    position: absolute;
    top: 1rem;
    right: 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.tool {
    width: 2.1rem;
    height: 2.1rem;
    display: grid;
    place-items: center;
    font-family: var(--data);
    font-size: 0.95rem;
    color: var(--graphite);
    background: rgba(13, 20, 31, 0.82);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    backdrop-filter: blur(6px);
    transition: color 0.18s var(--ease), border-color 0.18s var(--ease);
}

.tool:hover {
    color: var(--sodium);
    border-color: rgba(255, 183, 101, 0.4);
}

.tool--reset {
    font-size: 0.62rem;
    font-family: var(--display);
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

/* Reading panel: what the map is telling you about one district. */
.wall__reading {
    position: absolute;
    top: 1rem;
    left: 1rem;
    width: min(15rem, calc(100% - 2rem));
    padding: 0.75rem 0.85rem;
    background: rgba(9, 14, 22, 0.9);
    border: 1px solid var(--hairline);
    border-left: 2px solid var(--sodium);
    border-radius: var(--radius);
    backdrop-filter: blur(8px);
    pointer-events: none;
}

.wall__reading[hidden] {
    display: none;
}

/* When the hovered district is in the upper half of the SVG, drop the
   reading card below rather than letting it overlap the northern coast. */
.wall__reading--bottom {
    top: auto;
    bottom: 5rem; /* above the chips/rail at the bottom */
}

.wall__reading[data-state="dark"] {
    border-left-color: var(--filament);
}

.wall__reading[data-state="none"] {
    border-left-color: var(--graphite-dim);
}

.reading__name {
    font-family: var(--display);
    font-size: 1.15rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    line-height: 1.1;
}

.reading__line {
    margin-top: 0.15rem;
    font-family: var(--data);
    font-size: 0.72rem;
    color: var(--graphite);
}

.reading__zones {
    margin-top: 0.5rem;
    padding-top: 0.5rem;
    border-top: 1px solid var(--hairline);
    display: grid;
    gap: 0.22rem;
}

.reading__zone {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.6rem;
    font-size: 0.78rem;
    color: var(--graphite);
}

.reading__zone b {
    font-family: var(--data);
    font-size: 0.68rem;
    font-weight: 500;
    letter-spacing: 0.04em;
}

.reading__zone[data-status="OFF"] b {
    color: var(--filament);
}

.reading__zone[data-status="ON"] b {
    color: var(--sodium);
}

/* Docked rail: legend + district chips */
.wall__rail {
    position: absolute;
    inset: auto 0 0 0;
    padding: 0.7rem 1rem 0.85rem;
    background: linear-gradient(180deg, rgba(6, 10, 17, 0), rgba(6, 10, 17, 0.94) 45%);
}

.key {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 1rem;
    margin-bottom: 0.55rem;
    font-family: var(--display);
    font-size: 0.72rem;
    font-weight: 500;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--graphite);
}

.key li {
    display: flex;
    align-items: center;
    gap: 0.4rem;
}

.key__dot {
    width: 9px;
    height: 9px;
    border-radius: 99px;
    border: 1px solid var(--d-none-stroke);
    background: #24405d;
}

.key__dot[data-state="lit"] {
    background: var(--light-warm);
    border-color: var(--light-hot);
    box-shadow: 0 0 7px rgba(255, 192, 120, 0.85);
}

.key__dot[data-state="partial"] {
    background: var(--sodium-deep);
    border-color: var(--sodium);
    box-shadow: 0 0 5px rgba(217, 143, 69, 0.6);
}

.key__dot[data-state="dark"] {
    background: var(--land-out);
    border-color: var(--filament);
    box-shadow: 0 0 7px rgba(255, 77, 61, 0.6);
}

.key__dot[data-state="repairing"] {
    background: var(--warning);
    border-color: #fcd34d;
    box-shadow: 0 0 7px rgba(251, 191, 36, 0.75);
}

.chips {
    display: flex;
    gap: 0.3rem;
    overflow-x: auto;
    padding-bottom: 0.2rem;
    -webkit-overflow-scrolling: touch;
}

.chip {
    flex: none;
    font-family: var(--display);
    font-size: 0.78rem;
    font-weight: 500;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    white-space: nowrap;
    padding: 0.28rem 0.6rem;
    color: var(--graphite);
    background: rgba(17, 26, 39, 0.85);
    border: 1px solid var(--hairline);
    border-radius: 99px;
    transition: color 0.18s var(--ease), border-color 0.18s var(--ease),
        background 0.18s var(--ease);
}

.chip:hover {
    color: var(--porcelain);
    border-color: var(--graphite-dim);
}

.chip[aria-pressed="true"] {
    color: #2a1a08;
    background: var(--sodium);
    border-color: var(--sodium);
}

.chip__dot {
    display: inline-block;
    width: 5px;
    height: 5px;
    margin-left: 0.35rem;
    border-radius: 99px;
    background: var(--filament);
    vertical-align: middle;
}

/* `display: inline-block` above beats the browser's own [hidden]{display:none},
   so paintChips() setting dot.hidden = true did nothing and EVERY district chip
   showed an "outage" dot permanently. Restore the hide. */
.chip__dot[hidden] {
    display: none;
}

.chip[aria-pressed="true"] .chip__dot {
    background: #2a1a08;
}

/* ==========================================================================
   Instrument rack
   ========================================================================== */

.rack {
    display: flex;
    flex-direction: column;
    min-height: 0;
    background: var(--night);
}

.rack__unit {
    display: flex;
    flex-direction: column;
    min-height: 0;
    padding: 1rem 1.1rem;
    background: var(--panel);
    border-bottom: 1px solid var(--hairline);
}

.rack__unit:last-child {
    border-bottom: 0;
}

.rack__unit--cats {
    flex: 1 1 auto;
    padding: 0;
    overflow-y: auto;
}

.unit__head--search {
    padding: 0.85rem 1.1rem 0.75rem;
    border-bottom: 1px solid var(--hairline);
}

.field--search {
    width: 100%;
}

/* Category accordion cards */
.cat {
    border-bottom: 1px solid var(--hairline);
}

.cat:last-child {
    border-bottom: 0;
}

.cat__head {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.85rem 1.1rem;
    background: none;
    border: none;
    cursor: pointer;
    text-align: left;
    color: var(--porcelain);
    font-family: var(--display);
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    transition: background 0.15s var(--ease);
}

.cat__head:hover {
    background: rgba(255, 255, 255, 0.03);
}

.cat__dot {
    width: 7px;
    height: 7px;
    border-radius: 99px;
    flex-shrink: 0;
}

.cat__dot--off {
    background: var(--filament);
    box-shadow: 0 0 6px rgba(255, 77, 61, 0.7);
}

.cat__dot--on {
    background: var(--sodium);
    box-shadow: 0 0 6px rgba(255, 183, 101, 0.6);
}

.cat__dot--activity {
    background: var(--graphite);
}

.cat__name {
    flex: 1 1 auto;
}

.cat__badge {
    font-family: var(--data);
    font-size: 0.72rem;
    font-weight: 500;
    letter-spacing: 0;
    padding: 0.1rem 0.45rem;
    border-radius: 99px;
    background: var(--rail);
    color: var(--graphite);
    min-width: 1.5rem;
    text-align: center;
    transition: background 0.2s var(--ease), color 0.2s var(--ease);
}

.cat__badge--off {
    color: var(--filament);
}

.cat__badge--on {
    color: var(--sodium);
}

.cat__arrow {
    font-size: 0.7rem;
    color: var(--graphite-dim);
    transition: transform 0.2s var(--ease);
    flex-shrink: 0;
}

.cat[aria-expanded="true"] .cat__arrow {
    transform: rotate(180deg);
}

.cat[aria-expanded="true"] .cat__head {
    color: var(--sodium);
}

.cat__body {
    padding-bottom: 0.25rem;
}

.zones__empty {
    padding: 0.6rem 1.1rem;
    font-size: 0.78rem;
    color: var(--graphite-dim);
    font-style: italic;
}

.unit__label {
    font-family: var(--display);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--graphite);
}

.unit__count {
    font-family: var(--data);
    letter-spacing: 0;
    color: var(--graphite-dim);
}

.unit__head {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    margin-bottom: 0.75rem;
}

.unit__tools {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 0.4rem;
}

/* --- panel meter --------------------------------------------------------- */

/* Top bar: label left, search right */
.meter__topbar {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 0.85rem;
    align-items: center;
    margin-bottom: 0.75rem;
}

/* Two-column hero: big number + district text left, stats right */
.meter__hero {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 0 1rem;
    align-items: center;
    padding-top: 0.85rem;
    border-top: 1px solid var(--hairline);
}

.meter__left {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 0.55rem;
    min-width: 0;
}

.meter__value {
    font-family: var(--display);
    font-size: 4.8rem;
    font-weight: 700;
    line-height: 0.88;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
    margin-top: 0;
    flex-shrink: 0;
    color: var(--sodium);
    transition: color 0.3s var(--ease);
}

.meter[data-alarm="true"] .meter__value {
    color: var(--filament);
    text-shadow: 0 0 26px rgba(255, 77, 61, 0.4);
}

.meter[data-alarm="false"] .meter__value {
    text-shadow: 0 0 26px rgba(255, 183, 101, 0.32);
}

.meter__state {
    margin-top: 0;
    font-family: var(--display);
    font-size: 0.82rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--porcelain);
    line-height: 1.2;
    white-space: nowrap;
}

.meter[data-alarm="true"] .meter__state {
    color: var(--filament);
}

/* Category buttons column — POWER OFF / POWER ON stacked */
.meter__cats {
    display: flex;
    flex-direction: column;
    gap: 0.55rem;
    min-width: 0;
}

.meter__cat {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.45rem 0.65rem;
    background: var(--rail);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    font-family: var(--display);
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--porcelain);
    cursor: pointer;
    transition: background 0.18s var(--ease), border-color 0.18s var(--ease);
    white-space: nowrap;
}

.meter__cat:hover {
    background: var(--rail-high);
    border-color: var(--graphite-dim);
}

.meter__cat-dot {
    width: 7px;
    height: 7px;
    border-radius: 99px;
    flex-shrink: 0;
}

.meter__cat-dot--off {
    background: var(--filament);
    box-shadow: 0 0 6px rgba(255, 77, 61, 0.7);
}

.meter__cat-dot--on {
    background: var(--sodium);
    box-shadow: 0 0 6px rgba(255, 183, 101, 0.6);
}

.meter__cat-name {
    flex: 1 1 auto;
}

.meter__cat-count {
    font-family: var(--data);
    font-size: 0.75rem;
    font-weight: 500;
    letter-spacing: 0;
    padding: 0.12rem 0.5rem;
    border-radius: 99px;
    background: var(--night);
    color: var(--graphite);
    min-width: 1.8rem;
    text-align: center;
}

.meter__cat-arrow {
    font-size: 0.7rem;
    color: var(--graphite-dim);
    flex-shrink: 0;
}

/* Stats column — single column, all three stacked vertically */
.meter__grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 0.45rem;
    margin-top: 0;
    padding-top: 0;
    border-top: none;
    align-content: start;
}

.meter__grid > div {
    display: block;
}

.meter__grid dt {
    font-family: var(--display);
    font-size: 0.66rem;
    font-weight: 500;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--graphite-dim);
}

.meter__grid dd {
    font-family: var(--data);
    font-size: 1.05rem;
    font-variant-numeric: tabular-nums;
    color: var(--porcelain);
    margin-top: 0.05rem;
}

/* --- zone list ----------------------------------------------------------- */

.zones__list {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    display: grid;
    gap: 1px;
    background: var(--hairline);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
}

.zone {
    display: grid;
    grid-template-columns: 3px minmax(0, 1fr) auto;
    align-items: center;
    gap: 0.7rem;
    padding-right: 0.75rem;
    background: var(--rail);
    transition: background 0.18s var(--ease);
}

.zone:hover {
    background: var(--rail-high);
}

.zone__flag {
    align-self: stretch;
    background: var(--sodium);
}

.zone__body,
.zone__right {
    padding-block: 0.6rem;
}

.zone[data-status="OFF"] .zone__flag {
    background: var(--filament);
    box-shadow: 0 0 10px rgba(255, 77, 61, 0.6);
}

.zone__name {
    font-size: 0.92rem;
    font-weight: 600;
    letter-spacing: 0.005em;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.zone__meta {
    margin-top: 0.1rem;
    font-family: var(--data);
    font-size: 0.68rem;
    color: var(--graphite-dim);
    letter-spacing: 0.02em;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.zone__right {
    text-align: right;
    flex: none;
}

.zone__status {
    display: block;
    font-family: var(--display);
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--sodium);
}

.zone[data-status="OFF"] .zone__status {
    color: var(--filament);
}

.zone__time {
    display: block;
    font-family: var(--data);
    font-size: 0.66rem;
    color: var(--graphite-dim);
}

/* --- activity feed ------------------------------------------------------- */

.feed__list {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    display: grid;
    gap: 0.55rem;
    padding-left: 0.85rem;
    border-left: 1px solid var(--hairline);
}

.event {
    position: relative;
}

.event::before {
    content: "";
    position: absolute;
    top: 0.42rem;
    left: -1.13rem;
    width: 7px;
    height: 7px;
    border-radius: 99px;
    background: var(--graphite-dim);
    box-shadow: 0 0 0 3px var(--panel);
}

.event[data-status="OFF"]::before,
.event[data-status="REPORTED"]::before {
    background: var(--filament);
}

.event[data-status="ON"]::before,
.event[data-status="RESOLVED"]::before {
    background: var(--sodium);
}

.event__text {
    font-size: 0.85rem;
    line-height: 1.35;
}

.event__text b {
    font-weight: 600;
}

.event__meta {
    margin-top: 0.1rem;
    font-family: var(--data);
    font-size: 0.66rem;
    color: var(--graphite-dim);
}

.event__note {
    margin-top: 0.25rem;
    padding-left: 0.55rem;
    border-left: 2px solid var(--hairline);
    font-size: 0.78rem;
    color: var(--graphite);
}

/* --- empty, loading and error states ------------------------------------- */

.state {
    padding: 1.4rem 1rem;
    text-align: center;
    background: var(--rail);
}

.state__title {
    font-family: var(--display);
    font-size: 0.9rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--porcelain);
}

.state__body {
    margin-top: 0.3rem;
    font-size: 0.82rem;
    color: var(--graphite);
}

.state--error .state__title {
    color: var(--filament);
}

.state--bare {
    background: none;
    padding: 0.9rem 0;
    text-align: left;
}

/* ==========================================================================
   Sheets (dialogs)
   ========================================================================== */

.sheet {
    width: min(29rem, calc(100vw - 2rem));
    padding: 0;
    color: var(--porcelain);
    background: var(--panel);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-lg);
    box-shadow: 0 30px 70px rgba(0, 0, 0, 0.65);
}

.sheet::backdrop {
    background: rgba(4, 7, 12, 0.76);
    backdrop-filter: blur(3px);
}

.sheet__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    padding: 1.1rem 1.2rem 0.9rem;
    border-bottom: 1px solid var(--hairline);
}

.sheet__title {
    font-family: var(--display);
    font-size: 1.5rem;
    font-weight: 700;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    line-height: 1;
}

.sheet__sub {
    margin-top: 0.3rem;
    font-size: 0.82rem;
    color: var(--graphite);
}

.sheet__close {
    flex: none;
    width: 1.9rem;
    height: 1.9rem;
    display: grid;
    place-items: center;
    font-family: var(--data);
    font-size: 1rem;
    color: var(--graphite);
    background: none;
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    transition: color 0.18s var(--ease), border-color 0.18s var(--ease);
}

.sheet__close:hover {
    color: var(--filament);
    border-color: var(--filament);
}

.sheet__body {
    padding: 1.1rem 1.2rem 1.2rem;
    max-height: min(60vh, 30rem);
    overflow-y: auto;
    display: grid;
    gap: 0.85rem;
}

.form-row {
    display: grid;
    gap: 0.3rem;
}

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

.form-label {
    font-family: var(--display);
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--graphite);
}

.form-hint {
    font-size: 0.75rem;
    color: var(--graphite-dim);
}

.field--block {
    width: 100%;
    padding: 0.55rem 0.7rem;
    font-size: 0.9rem;
}

textarea.field--block {
    resize: vertical;
    min-height: 4.5rem;
    font-family: var(--body);
}

/* Location readout inside a sheet */
.locator {
    display: grid;
    gap: 0.5rem;
    padding: 0.75rem 0.85rem;
    background: var(--night);
    border: 1px solid var(--hairline);
    border-left: 2px solid var(--hairline);
    border-radius: var(--radius);
}

.locator[data-state="ready"] {
    border-left-color: var(--sodium);
}

.locator[data-state="error"] {
    border-left-color: var(--filament);
}

.locator__line {
    font-family: var(--data);
    font-size: 0.75rem;
    color: var(--graphite);
    letter-spacing: 0.02em;
}

.locator__zone {
    font-family: var(--display);
    font-size: 1rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--porcelain);
}

.form-error {
    font-size: 0.8rem;
    color: var(--filament);
}

.form-error[hidden] {
    display: none;
}

/* ==========================================================================
   App navigation (mobile bottom bar)
   ========================================================================== */

.app-nav {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 50;
    height: var(--nav-h);
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    background: var(--panel);
    border-top: 1px solid var(--hairline);
    box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.45);
    /* Respect notched-phone safe areas */
    padding-bottom: env(safe-area-inset-bottom, 0);
}

.nav-item {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.18rem;
    padding: 0.35rem 0.25rem;
    color: var(--graphite-dim);
    font-family: var(--display);
    font-size: 0.58rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    text-decoration: none;
    background: none;
    border: none;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: color 0.18s var(--ease);
}

.nav-item:hover {
    color: var(--graphite);
}

.nav-item.is-active {
    color: var(--sodium);
}

/* Active indicator — thin sodium bar at the top edge */
.nav-item.is-active::after {
    content: "";
    position: absolute;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    width: 1.8rem;
    height: 2px;
    background: var(--sodium);
    border-radius: 0 0 3px 3px;
    box-shadow: 0 0 8px rgba(255, 183, 101, 0.65);
}

.nav-item__icon {
    width: 1.3rem;
    height: 1.3rem;
    flex: none;
}

.nav-item__label {
    line-height: 1;
}

/* Centre report button — sodium pill, stands out as the primary CTA */
.nav-item--report {
    color: var(--night);
    background: var(--sodium);
    border-radius: var(--radius);
    margin: 0.42rem 0.4rem;
    padding: 0.3rem 0.35rem;
    box-shadow: 0 0 14px rgba(255, 183, 101, 0.4);
    transition: background 0.18s var(--ease), box-shadow 0.18s var(--ease), color 0.18s var(--ease);
}

.nav-item--report:hover {
    color: var(--night);
    background: var(--sodium-pale);
    box-shadow: 0 0 22px rgba(255, 183, 101, 0.6);
}

/* Report never gets the top-bar indicator */
.nav-item--report::after {
    display: none !important;
}

/* Notification badge on Alerts item */
.nav-item__badge {
    position: absolute;
    top: 0.28rem;
    right: calc(50% - 1.05rem);
    min-width: 1rem;
    height: 1rem;
    padding: 0 0.22rem;
    font-family: var(--data);
    font-size: 0.56rem;
    font-weight: 700;
    line-height: 1rem;
    text-align: center;
    color: var(--night);
    background: var(--filament);
    border-radius: 99px;
    border: 2px solid var(--panel);
    letter-spacing: 0;
    box-shadow: 0 0 6px rgba(255, 77, 61, 0.55);
}

.nav-item__badge[hidden] {
    display: none;
}

/* ==========================================================================
   Toast (repositioned above nav bar)
   ========================================================================== */

.toast {
    position: fixed;
    left: 50%;
    bottom: calc(var(--nav-h) + 1rem);
    transform: translate(-50%, 1.5rem);
    z-index: 60;
    max-width: min(26rem, calc(100vw - 2rem));
    padding: 0.7rem 1rem;
    font-size: 0.88rem;
    background: var(--rail-high);
    border: 1px solid var(--hairline);
    border-left: 3px solid var(--sodium);
    border-radius: var(--radius);
    box-shadow: 0 18px 40px rgba(0, 0, 0, 0.6);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.22s var(--ease), transform 0.22s var(--ease);
}

.toast.is-visible {
    opacity: 1;
    transform: translate(-50%, 0);
}

.toast[data-tone="error"] {
    border-left-color: var(--filament);
}

/* ==========================================================================
   Responsive
   ========================================================================== */

/* Desktop and up: keep the whole console on a single screen so the map panel
   never stretches to the rack's content height. The rack scrolls in its own
   column instead of pushing the island below the fold. */
@media (min-width: 881px) {
    /* Pin the whole console to one screen (minus the fixed bottom nav + the
       body padding that reserves its space) so the map panel can never grow
       past the fold when the POWER ON/OFF rack list gets long. */
    .shell {
        height: calc(100dvh - var(--nav-h) - 0.75rem);
    }

    .board {
        grid-template-rows: minmax(0, 1fr);
    }

    /* Let the rack scroll inside its own column instead of stretching the
       board (and the map) to its content height. */
    .rack {
        min-height: 0;
        overflow-y: auto;
    }
}

@media (max-width: 1080px) {
    :root {
        --rack-width: 21rem;
    }

    .meter__value {
        font-size: 3.6rem;
    }
}

@media (max-width: 880px) {
    .board {
        grid-template-columns: minmax(0, 1fr);
    }

    /* On mobile the wall is not in a height-constrained grid row, so we
       must set an explicit height for the absolute children to fill. */
    .wall {
        height: 74vh;
        min-height: 30rem;
    }

    .rack__unit--zones,
    .rack__unit--feed {
        flex: none;
    }

    .zones__list,
    .feed__list {
        max-height: 24rem;
        overflow-y: auto;
    }
}

@media (max-width: 560px) {
    .bar {
        padding: 0.7rem 0.9rem;
    }

    /* Put the profile / actions cluster on its own row under the logo and
       right-align it, so the avatar sits at the top-RIGHT like a phone app. */
    .bar__right {
        width: 100%;
        justify-content: flex-end;
    }

    /* Force the profile menu to be the right-most item in that cluster. */
    .user-menu {
        order: 9;
    }

    /* Anchor the dropdown to the right edge and cap its width so it opens
       fully on-screen instead of being clipped off the left. */
    .user-menu__panel {
        right: 0;
        left: auto;
        max-width: calc(100vw - 1.5rem);
    }

    .bar__tag {
        display: none;
    }

    .bar__clock {
        display: none;
    }

    .wall__map {
        /* Keep the dark container full-bleed, but pad the SVG in a bit more so
           the island scales DOWN and sits balanced in the middle — no longer
           kissing the text above/below (per request). */
        padding: 1.35rem 2.25rem 6.25rem;
    }

    /* PHONE: restore the large map. It was cut to 54vh for scroll smoothness,
       but the static light-field is a baked canvas bitmap now, so a taller map
       no longer costs anything — and the user wants it big again. */
    .wall {
        height: 74vh;
        min-height: 26rem;
    }

    .wall__reading {
        width: calc(100% - 4.5rem);
    }

    .meter__value {
        font-size: 3.2rem;
    }

    .unit__tools {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* ==========================================================================
   Reduced motion
   ========================================================================== */

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

    #lightLayer {
        opacity: 1;
    }

    /* No twinkle and no rolling sea; the lights simply hold steady. */
    .amb-d circle {
        animation: none;
        opacity: 1;
    }

    .wave {
        animation: none;
        opacity: 0;
    }

    .district-path[data-state="dark"] {
        animation: none;
        filter: drop-shadow(0 0 6px rgba(255, 77, 61, 0.55));
    }

    .district-path[data-repairing="true"] {
        animation: none;
        filter: drop-shadow(0 0 8px rgba(251, 191, 36, 0.7));
    }

    /* No stroke tracing and no glow breath; the outline simply appears. */
    #districtHighlightOverlay {
        stroke-dasharray: none !important;
        stroke-dashoffset: 0 !important;
    }

    #districtHighlightOverlay.is-visible {
        animation: none !important;
        opacity: 1;
    }
}

/* --------------------------------------------------------------------------
   Smooth scrolling on phones: while the finger is actually moving the page,
   freeze the decorative infinite animations on the map (the throbbing outage
   districts, sea ripple, twinkle, pulsing dots). Those animate `filter` /
   `drop-shadow`, which forces the whole map to repaint every frame and fights
   the scroll for the compositor — that is the "kadi kadi / hiravi hiravi"
   stutter. `animation-play-state: paused` just holds each animation on its
   current frame (a glow held still looks identical to a glow mid-breath), and
   the class is removed ~180ms after scrolling stops, so the static look is
   completely unchanged.
   -------------------------------------------------------------------------- */
body.is-scrolling .wall__map *,
body.is-scrolling .pulse,
body.is-scrolling .live-dot {
    animation-play-state: paused !important;
}

/* ==========================================================================
   User Notification Panel
   ========================================================================== */

.user-notification {
    position: relative;
    margin-right: 0.2rem;
}

.notification-badge {
    background: transparent;
    border: none;
    cursor: pointer;
    padding: 0.5rem;
    position: relative;
    color: var(--graphite);
    transition: color 0.2s;
}

.notification-badge:hover {
    color: var(--porcelain);
}

.notification-icon {
    font-size: 1.25rem;
    display: block;
}

.notification-dot {
    position: absolute;
    top: 0.25rem;
    right: 0.25rem;
    width: 8px;
    height: 8px;
    background: var(--filament);
    border-radius: 50%;
    animation: notificationPulse 2s ease-in-out infinite;
}

@keyframes notificationPulse {
    0%, 100% { opacity: 1; transform: scale(1); }
    50% { opacity: 0.6; transform: scale(1.2); }
}

.notification-panel {
    position: absolute;
    top: calc(var(--nav-h) + 0.5rem);
    right: 1rem;
    width: 320px;
    background: var(--panel);
    border: 1px solid var(--hairline);
    border-radius: 8px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
    z-index: 1000;
}

.notification-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 1rem;
    border-bottom: 1px solid var(--hairline);
}

.notification-header h3 {
    margin: 0;
    font-size: 1rem;
    font-weight: 600;
    color: var(--porcelain);
}

.notification-close {
    background: transparent;
    border: none;
    color: var(--graphite);
    font-size: 1.25rem;
    cursor: pointer;
    padding: 0;
    width: 24px;
    height: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: color 0.2s;
}

.notification-close:hover {
    color: var(--porcelain);
}

.notification-body {
    padding: 1rem;
}

.notification-loading {
    text-align: center;
    color: var(--graphite);
    font-size: 0.875rem;
}

.notification-message {
    padding: 1rem;
    border-radius: 6px;
    margin-bottom: 0.75rem;
    font-size: 0.9375rem;
    line-height: 1.5;
}

.notification-message.status-off {
    background: rgba(255, 77, 61, 0.1);
    border-left: 3px solid var(--filament);
    color: var(--porcelain);
}

.notification-message.status-on {
    background: rgba(34, 197, 94, 0.1);
    border-left: 3px solid #22c55e;
    color: var(--porcelain);
}

/* Staff-authored CEB update — amber, distinct from the synthesized status. */
.notification-message.notification-staff {
    background: rgba(245, 158, 11, 0.12);
    border-left: 3px solid #f59e0b;
    color: var(--porcelain);
}

.notification-staff-label {
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    color: #f59e0b;
    margin-bottom: 0.35rem;
}

.notification-staff-text {
    white-space: pre-line;
}

.notification-details {
    margin-top: 0.75rem;
    padding-top: 0.75rem;
    border-top: 1px solid var(--hairline);
}

.notification-detail-item {
    display: flex;
    justify-content: space-between;
    margin: 0.5rem 0;
    font-size: 0.875rem;
}

.notification-detail-label {
    color: var(--graphite);
}

.notification-detail-value {
    color: var(--porcelain);
    font-family: var(--data);
}

.notification-no-zone {
    text-align: center;
    color: var(--graphite);
    font-size: 0.875rem;
    padding: 1.5rem;
}

.notification-no-zone button {
    margin-top: 1rem;
    background: var(--rail);
    color: var(--porcelain);
    border: 1px solid var(--hairline);
    padding: 0.5rem 1rem;
    border-radius: 4px;
    cursor: pointer;
    font-size: 0.875rem;
    transition: background 0.2s;
}

.notification-no-zone button:hover {
    background: var(--rail-high);
}

/* ==========================================================================
   User Profile Header
   ========================================================================== */

.user-menu {
    position: relative;
}

.user-menu__trigger {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.3rem 0.55rem 0.3rem 0.35rem;
    background: var(--rail);
    border: 1px solid var(--hairline);
    border-radius: 99px;
    color: var(--porcelain);
    cursor: pointer;
    transition: background 0.18s var(--ease), border-color 0.18s var(--ease);
}

.user-menu__trigger:hover,
.user-menu__trigger[aria-expanded="true"] {
    background: var(--rail-high);
    border-color: var(--sodium);
}

.user-menu__avatar {
    width: 2rem;
    height: 2rem;
    flex: none;
    border-radius: 50%;
    background: var(--rail-high);
    border: 1px solid var(--sodium-deep);
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
}

.user-menu__avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.user-menu__letter {
    font-family: var(--display);
    font-size: 1rem;
    font-weight: 700;
    line-height: 1;
    color: var(--sodium);
}

.user-menu__caret {
    width: 1rem;
    height: 1rem;
    color: var(--graphite);
    transition: transform 0.18s var(--ease);
}

.user-menu__trigger[aria-expanded="true"] .user-menu__caret {
    transform: rotate(180deg);
}

.user-menu__panel {
    position: absolute;
    top: calc(100% + 0.5rem);
    right: 0;
    min-width: 210px;
    padding: 0.4rem;
    background: var(--panel);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-lg);
    box-shadow: 0 18px 42px rgba(0, 0, 0, 0.55);
    z-index: 60;
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    animation: userMenuIn 0.16s var(--ease);
}

.user-menu__panel[hidden] {
    display: none;
}

@keyframes userMenuIn {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: translateY(0); }
}

.user-menu__item {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    width: 100%;
    padding: 0.6rem 0.7rem;
    font-family: var(--body);
    font-size: 0.9rem;
    color: var(--porcelain);
    text-align: left;
    text-decoration: none;
    background: transparent;
    border: 0;
    border-radius: var(--radius);
    cursor: pointer;
    transition: background 0.15s var(--ease);
}

.user-menu__item:hover {
    background: var(--rail);
}

.user-menu__ic {
    font-size: 1rem;
    width: 1.25rem;
    text-align: center;
    flex: none;
}

.user-menu__item--danger {
    color: var(--filament);
}

.user-menu__sep {
    height: 1px;
    background: var(--hairline);
    margin: 0.3rem 0.2rem;
}

.user-name {
    font-size: 0.9rem;
    font-weight: 500;
    max-width: 130px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

@media (max-width: 768px) {
    .user-name {
        display: none;
    }
}

/* Avatar Picker */
.avatar-picker {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    gap: 0.75rem;
    margin-top: 0.5rem;
}

.avatar-option {
    width: 100%;
    aspect-ratio: 1;
    padding: 0.5rem;
    background: var(--rail);
    border: 2px solid var(--hairline);
    border-radius: 50%;
    cursor: pointer;
    transition: all 0.2s ease;
}

.avatar-option:hover {
    background: var(--rail-high);
    border-color: var(--sodium);
}

.avatar-option.active {
    background: var(--rail-high);
    border-color: var(--sodium);
    box-shadow: 0 0 0 3px rgba(255, 183, 101, 0.2);
}

.avatar-option img {
    width: 100%;
    height: 100%;
    display: block;
    border-radius: 50%;
}

@media (max-width: 560px) {
    .avatar-picker {
        grid-template-columns: repeat(3, 1fr);
    }
}

/* ======================================================================
   HEADER + FOOTER REDESIGN (2026-08-21) — floating, rounded, orange glow
   Self-contained override block. To revert this look, delete everything
   below this banner and restore index.php header/footer from _backups/.
   ====================================================================== */

/* ---- Flat header (reference-style: sits directly on the page, no pill box) ---- */
/* Guard against anything ever nudging the page into a sliver of horizontal
   scroll on narrow screens. */
html { overflow-x: hidden; }
.bar {
    position: sticky;
    top: 0;
    z-index: 60;
    margin: 0;
    padding: 0.9rem 1.1rem 0.8rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.6rem;
    flex-wrap: nowrap;
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-bottom: none;
    /* Round only the top corners; the bottom stays square (per request) */
    border-radius: 1.9rem 1.9rem 0 0;
    /* Solid dark bar — no orange glow */
    background: linear-gradient(180deg, #10161f 0%, #0b1119 100%);
    box-shadow: 0 10px 26px -18px rgba(0, 0, 0, 0.9);
    backdrop-filter: none;
}
.bar__left,
.bar__right,
.bar__center {
    display: flex;
    align-items: center;
    min-width: 0;
}
.bar__left { gap: 0.5rem; justify-content: flex-start; flex: 1 1 auto; width: auto; min-width: 0; }
.bar__right { gap: 0.05rem; justify-content: flex-end; flex: 0 0 auto; width: auto; }
.bar__center {
    gap: 0.5rem;
    justify-content: flex-end;
    flex: 0 0 auto;
    overflow: hidden;
    /* Hide the techy Connecting/Live pulse + clock to keep the header clean. */
    display: none !important;
}

/* Greeting stack next to the avatar (reference-style) */
.bar__hello {
    display: flex;
    flex-direction: column;
    justify-content: center;
    line-height: 1.1;
    min-width: 0;
    margin-left: 0.1rem;
}
.bar__hello-hi {
    font-family: "Poppins", var(--display);
    font-weight: 600;
    font-size: 1.08rem;
    letter-spacing: 0.01em;
    color: var(--porcelain);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.bar__hello-sub {
    font-family: var(--body);
    font-size: 0.7rem;
    color: var(--graphite);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* PLACEHOLDER_HEADER_ICON */

/* Round icon buttons (login person, hamburger, drawer close) — understated,
   dark, faint hairline; the orange only warms up on hover. */
.bar__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.1rem;
    height: 2.1rem;
    padding: 0;
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.045);
    color: var(--graphite);
    cursor: pointer;
    transition: transform 0.15s var(--ease), box-shadow 0.2s var(--ease), border-color 0.2s var(--ease), color 0.2s var(--ease);
}
.bar__icon svg { width: 1.3rem; height: 1.3rem; }
.bar__icon:hover {
    color: var(--sodium-pale);
    border-color: rgba(255, 183, 101, 0.5);
    box-shadow: 0 0 14px -4px rgba(255, 141, 60, 0.6);
    transform: translateY(-1px);
}
.bar__icon:active { transform: translateY(0); }

/* Avatar trigger — glowing ring, hide the name text (avatar is enough) */
.bar .user-menu { position: relative; order: 0; flex: 0 0 auto; }
.bar .user-menu__trigger {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.2rem;
    border: none;
    background: transparent;
    cursor: pointer;
    border-radius: 999px;
}
.bar .user-menu__avatar {
    width: 2.15rem;
    height: 2.15rem;
    border-radius: 50%;
    overflow: hidden;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(180deg, #24303f, #141c27);
    /* No orange ring / glow around the photo (per request) */
    border: none;
    box-shadow: none;
    color: var(--sodium-pale);
    font-family: var(--display);
    font-size: 0.95rem;
}
.bar .user-menu__avatar img { width: 100%; height: 100%; object-fit: cover; }
.bar .user-menu__trigger .user-name { display: none; }
.bar .user-menu__caret { display: none; }
/* PLACEHOLDER_CENTER */

/* Center: live status as a compact chip (dot + label), sits by the icons */
.bar__center .pulse {
    margin: 0;
    max-width: 100%;
    gap: 0.35rem;
    padding: 0.3rem 0.6rem;
    font-size: 0.62rem;
    letter-spacing: 0.12em;
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.04);
}
.bar__center .bar__clock {
    font-family: var(--data);
    font-size: 0.78rem;
    color: var(--graphite);
    white-space: nowrap;
}
@media (max-width: 560px) {
    .bar__center .bar__clock { display: none; }
    .bar__center .pulse span:last-child { display: none; } /* keep just the dot */
    .bar__center .pulse { padding: 0.35rem; } /* tidy round dot chip */
}

/* Notification bell as a round icon to match the subtle icon buttons */
.bar .notification-badge {
    width: 2.4rem;
    height: 2.4rem;
    border-radius: 50%;
    border: 1px solid rgba(255, 255, 255, 0.08);
    background: rgba(255, 255, 255, 0.045);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    position: relative;
    cursor: pointer;
}
.bar .notification-badge .notification-dot {
    position: absolute;
    top: 0.35rem;
    right: 0.4rem;
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    background: var(--sodium);
    box-shadow: 0 0 8px rgba(255, 141, 60, 0.9);
}

/* ---- Seated bottom nav (solid dark bar, full-width, rounded bottom corners,
   no glow — sits flush at the bottom, content clears above it) ---- */
.app-nav {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    height: 4.4rem;
    padding-bottom: env(safe-area-inset-bottom, 0px);
    z-index: 55;
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    align-items: center;
    padding-left: 0.5rem;
    padding-right: 0.5rem;
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-top: 1px solid rgba(255, 255, 255, 0.08);
    /* Round only the BOTTOM corners; the top stays square (per request) */
    border-radius: 0 0 1.9rem 1.9rem;
    /* Solid dark bar — no orange glow */
    background: linear-gradient(180deg, #10161f 0%, #0b1119 100%);
    box-shadow: 0 -10px 26px -18px rgba(0, 0, 0, 0.9);
    backdrop-filter: none;
    overflow: visible;
}

/* Every slot: a plain muted icon, centered — no labels (reference look) */
.app-nav .nav-item {
    position: relative;
    height: 100%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    background: none;
    color: var(--graphite-dim);
    transition: color 0.2s var(--ease), transform 0.15s var(--ease);
}
.app-nav .nav-item__label { display: none; }          /* icon-only */
.app-nav .nav-item__icon { width: 1.55rem; height: 1.55rem; position: relative; z-index: 1; }
.app-nav .nav-item:hover { color: var(--sodium-pale); }
.app-nav .nav-item:active { transform: scale(0.92); }

/* Kill the old top-bar active indicator inside the floating pill */
.app-nav .nav-item.is-active::after { display: none !important; }

/* Active slot: no filled circle — just tint the icon itself with the theme
   amber/orange (per request "ape orange eka, kahapata"). */
.app-nav .nav-item.is-active { color: var(--sodium); }
.app-nav .nav-item.is-active::before { display: none !important; }

/* Report is now an equal slot (no raised center button) */
.app-nav .nav-item--report {
    justify-self: stretch;
    width: auto;
    height: 100%;
    margin: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
    color: var(--graphite-dim);
}
.app-nav .nav-item--report:hover { transform: none; color: var(--sodium-pale); }
.app-nav .nav-item--report .nav-item__icon { width: 1.55rem; height: 1.55rem; }

/* Content clears the seated footer bar. No extra breathing gap beyond the
   footer height, so the content panel ends flush at the footer's top edge —
   no dark --night strip left between the last section and the footer. */
body:has(.app-nav) { padding-bottom: calc(var(--nav-h) + env(safe-area-inset-bottom, 0px)); }

/* Solid strip behind the footer: clips scrolling content at the footer's top
   edge so nothing slides under it. Also scoped to footer pages only, so the
   dark strip never shows on pages without a footer. */
body:has(.app-nav)::after {
    content: "";
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    height: calc(4.4rem + env(safe-area-inset-bottom, 0px));
    z-index: 54;
    pointer-events: none;
    background: var(--night);
}

/* ---- Side drawer ---- */
.drawer-backdrop {
    position: fixed;
    inset: 0;
    z-index: 90;
    background: rgba(4, 8, 14, 0.6);
    backdrop-filter: blur(2px);
    opacity: 0;
    transition: opacity 0.25s var(--ease);
}
.drawer-backdrop.is-open { opacity: 1; }
body.drawer-lock { overflow: hidden; }

.drawer {
    position: fixed;
    top: 0;
    left: 0;
    right: auto;
    z-index: 95;
    width: min(80vw, 300px);
    height: 100%;
    padding: 1rem 0.9rem calc(1.2rem + env(safe-area-inset-bottom, 0px));
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    border-right: 1px solid rgba(255, 183, 101, 0.25);
    /* Round only the inner edge (the side that slides into the app) */
    border-radius: 0 1.4rem 1.4rem 0;
    background:
        radial-gradient(120% 60% at 0% 0%, rgba(255, 141, 60, 0.22), transparent 55%),
        linear-gradient(180deg, #121924, #0a0f16);
    box-shadow: 18px 0 40px -18px rgba(0, 0, 0, 0.9);
    transform: translateX(-100%);
    transition: transform 0.26s var(--ease);
}
.drawer.is-open { transform: translateX(0); }
/* When closed (hidden attribute), fully remove from layout so the off-screen
   panel does not create horizontal page overflow. */
.drawer[hidden] { display: none; }
/* PLACEHOLDER_DRAWER_ITEMS */

.drawer__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.2rem 0.3rem 0.6rem;
}
.drawer__brand {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-family: var(--display);
    font-size: 1.15rem;
    letter-spacing: 0.02em;
    color: var(--porcelain);
}
.drawer__bolt { color: var(--sodium); }

/* Profile block at the top of the drawer (reference-style) */
.drawer__profile {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    padding: 0.4rem 0.3rem 0.9rem;
    margin-bottom: 0.4rem;
    border-bottom: 1px solid var(--hairline);
}
.drawer__avatar {
    width: 3.2rem;
    height: 3.2rem;
    flex: 0 0 auto;
    border-radius: 50%;
    overflow: hidden;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(180deg, #24303f, #141c27);
    border: 2px solid rgba(255, 183, 101, 0.65);
    box-shadow: 0 0 18px -4px rgba(255, 141, 60, 0.8);
    color: var(--sodium-pale);
    font-family: var(--display);
    font-size: 1.3rem;
}
.drawer__avatar img { width: 100%; height: 100%; object-fit: cover; }
.drawer__who { display: flex; flex-direction: column; min-width: 0; }
.drawer__name {
    font-family: "Poppins", var(--display);
    font-weight: 600;
    font-size: 1.05rem;
    color: var(--porcelain);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.drawer__role {
    font-family: var(--body);
    font-size: 0.72rem;
    color: var(--graphite);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.drawer__sep {
    height: 1px;
    margin: 0.5rem 0.3rem;
    background: var(--hairline);
}
.drawer__nav { display: flex; flex-direction: column; gap: 0.2rem; }
.drawer__item {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    width: 100%;
    padding: 0.7rem 0.7rem;
    border: none;
    border-radius: 0.8rem;
    background: transparent;
    color: var(--porcelain);
    font-family: var(--body);
    font-size: 0.95rem;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
    transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.drawer__item:hover {
    background: rgba(255, 183, 101, 0.12);
    color: var(--sodium-pale);
}
.drawer__ic {
    display: inline-flex;
    width: 1.4rem;
    justify-content: center;
    font-size: 1.05rem;
}
.drawer__item--danger { color: var(--filament); }
.drawer__item--danger:hover { background: rgba(255, 77, 61, 0.14); color: var(--filament); }
/* ===== END HEADER + FOOTER REDESIGN ===== */

/* ==========================================================================
   ONE-SCROLL-SURFACE FIX (phones/tablets)
   --------------------------------------------------------------------------
   Symptom the user reported: swiping up did not glide — the page moved "one or
   two" notches then stopped dead, and momentum/fling never carried.

   Cause: the lists inside the rack were their own scroll containers
   (`.zones__list`, `.feed__list`, `.rack__unit--cats` all had
   `overflow-y: auto`, plus a `max-height: 24rem` cap on phones). A touch that
   started over one of them was captured by that inner scroller, which ran out
   of its own short content almost immediately — and a fling does NOT chain
   from a nested scroller out to the document. So the swipe died there.

   Fix: on touch-sized screens let the lists flow at their natural height so
   the DOCUMENT is the only vertical scroll surface. Momentum then works the
   way the phone's native scrolling does. Nothing about the visual design
   changes — the same cards in the same order, just not clipped into a box.
   ========================================================================== */
@media (max-width: 880px) {
    .zones__list,
    .feed__list,
    .rack__unit--cats,
    .cat__body {
        max-height: none;
        overflow: visible;
        overflow-y: visible;
    }

    /* These were sized to fill a scroll box; let content decide instead. */
    .zones__list,
    .feed__list {
        flex: none;
    }

    /* The horizontal district chip strip stays swipeable sideways, but a
       sideways fling must not leak into the page scroll (and vice versa). */
    .chips {
        overscroll-behavior-x: contain;
    }

    /* Nothing else should trap a vertical touch. */
    .rack,
    .rack__unit,
    .board,
    .shell {
        overflow: visible;
    }
}





/* ==========================================================================
   PERF TEST — "map effects off" (reversible)
   Turned on by <html class="perf-lite"> (see the ?lite=1 toggle in index.php).
   Goal: prove whether the map's glow/motion is what makes scrolling stutter.
   It kills EVERY animation and EVERY drop-shadow/blur filter inside the map,
   but leaves all colours, fills and strokes intact — so the map still shows
   every district and its power state, just flat and still. Remove this block
   and the index.php toggle script to revert.
   ========================================================================== */
html.perf-lite .wall__map *,
html.perf-lite .wall__map *::before,
html.perf-lite .wall__map *::after,
html.perf-lite #districtHighlightOverlay {
    animation: none !important;
    filter: none !important;
}

/* Every ambient dot stays visible at its own steady brightness (no twinkle). */
html.perf-lite .amb-d circle {
    animation: none !important;
    opacity: var(--tw-lo, 0.6);
}

/* Yellow/amber light field OFF: hide every warm light-emitting layer — the
   ambient dot field (#lightAmbient, ~1021 dots), the bright bloomed cores
   (#lightLayer > #lightCores), the soft zone auras (#auraLayer) and the
   scattered transformer status dots (#svgDotsLayer) — i.e. all the "kaha pata"
   glowing lights. District fills/strokes stay, so the island and each area's
   power state (red = out, etc.) still read clearly from the map itself.
   Removing ~1100 gradient-filled nodes from paint is also the single biggest
   scroll-perf win. */
html.perf-lite #lightLayer,
html.perf-lite #lightAmbient,
html.perf-lite #auraLayer,
html.perf-lite #svgDotsLayer,
html.perf-lite .amb-d,
html.perf-lite .core {
    display: none !important;
}

/* The sea ripple and the slow "breathe" are pure decoration — drop them. */
html.perf-lite .wave {
    animation: none !important;
    opacity: 0;
}

/* Outed / repairing districts keep their red / amber colour, just no throb. */
html.perf-lite .district-path[data-state="dark"],
html.perf-lite .district-path[data-repairing="true"] {
    animation: none !important;
    filter: none !important;
}

/* ==========================================================================
   MAP SCROLL PERFORMANCE (2026-08-22) — keep the full night-lights look,
   make scrolling buttery.
   --------------------------------------------------------------------------
   The original map is intentionally rich: ~1000 gradient light dots, SVG
   blur filters (cityBloom / coastGlow) and several throbbing drop-shadow
   glows. The stutter was NOT the look itself — it was that every scroll frame
   re-rasterised all of that as the map moved up the page.

   Two independent fixes, no visual change:
   1. Promote the whole map SVG to its own GPU compositing layer. Once it is a
      texture, scrolling just SLIDES that texture on the GPU — the expensive
      dots + blurs are painted once, not on every scroll frame.
   2. `body.is-scrolling` (wired in app.js) already pauses the decorative
      animations while the finger is moving, so the texture stays stable and
      never needs re-rasterising mid-fling. It resumes ~180ms after scrolling
      stops, so the map looks 100% identical.
   ========================================================================== */

/* Give the map its own GPU layer so scrolling SLIDES a pre-painted texture
   instead of repainting the land + dots every frame. We promote ONLY the
   outer panel — promoting the inner SVG as well just splits the work into two
   layers the compositor has to manage, which can cost more than it saves on a
   phone. One texture, one slide. `backface-visibility: hidden` keeps it crisp.
   Invisible change: same pixels, cheaper compositing. */
.wall__svgpanel {
    transform: translateZ(0);
    -webkit-backface-visibility: hidden;
    backface-visibility: hidden;
    will-change: transform;
}

/* NOTE: we intentionally do NOT toggle `contain` on scroll. Adding/removing
   `contain: layout paint` at the moment scrolling starts forces the browser to
   recompute layout on that exact frame — a hitch right where you least want it.
   The steady GPU promotion below is enough on its own. */

/* --------------------------------------------------------------------------
   Baked light field (see lightRaster in app.js). Once the warm "city lights"
   have been painted into the flat #lightRaster bitmap, we hide the live SVG
   layers they replace so the map carries almost no per-frame paint cost while
   scrolling. Only the red power-OUT dots and the throbbing red district
   borders stay live. Toggled by the `lights-baked` class on #MAP-LK.
   -------------------------------------------------------------------------- */
#lightRaster {
    display: none;
    pointer-events: none;
}

#MAP-LK.lights-baked #lightRaster {
    display: inline;
}

/* The three warm layers now live inside the bitmap — drop the live versions. */
#MAP-LK.lights-baked #lightAmbient,
#MAP-LK.lights-baked #lightLayer {
    display: none;
}

/* ON transformer dots are baked in too; keep only the live red OUT dots. */
#MAP-LK.lights-baked #svgDotsLayer circle:not([data-status="OFF"]) {
    display: none;
}

/* --------------------------------------------------------------------------
   THE REAL SCROLL KILLER: animated `filter: drop-shadow`.
   A CSS filter that changes every animation frame forces the browser to
   RE-RASTERISE that part of the map on every single frame — and while the
   page is scrolling, that fights the GPU for the same frames, so the map
   stutters. Baking the warm lights into a bitmap was not enough on its own,
   because the red OUT districts were STILL animating a live drop-shadow.

   Fix: in baked mode the red/amber bloom is painted into the #lightRaster
   bitmap (see lightRaster.build() in app.js), so the live district only needs
   to throb its BORDER. We drop the filter entirely and pulse `stroke-opacity`
   instead — that is a compositor-only property (no repaint), so it is
   essentially free and never blocks a scroll frame. Same alarm look, zero
   per-frame rasterisation. This is the change that makes 25k dots viable:
   scroll cost no longer scales with how many things glow. */
#MAP-LK.lights-baked .district-path[data-state="dark"] {
    filter: none;
    animation: faultStroke 3.6s ease-in-out infinite;
}

#MAP-LK.lights-baked .district-path[data-repairing="true"] {
    filter: none;
    animation: repairStroke 2.8s ease-in-out infinite;
}

@keyframes faultStroke {
    0%, 100% { stroke-opacity: 0.4; }
    50% { stroke-opacity: 1; }
}

@keyframes repairStroke {
    0%, 100% { stroke-opacity: 0.55; }
    50% { stroke-opacity: 1; }
}

/* The sea ripple animates a BLUR — another per-frame filter re-raster. Its
   motion is decorative and reads almost the same when still, so once the field
   is baked we drop the animated wave layer entirely. The static cyan coast
   halo (#auraLayer) stays: it never animates, so the GPU bakes it into the
   map texture once and just slides it. */
#MAP-LK.lights-baked #waveLayer {
    display: none;
}
