/* Wildcaster Tactical UI - Dark Theme */
/* Color scheme: black background, yellow (#FFFF00) primary, cyan (#00FFFF) secondary */
/* Vd/Hd viewport-relative sizing: reference 1920×1080 */

:root {
    --Vd: 1vh;   /* vertical dimension unit (1% of viewport height) */
    --Hd: 1vw;   /* horizontal dimension unit (1% of viewport width) */
}

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    background: #000;
    color: #FFFF00;
    font-family: Arial, sans-serif;
    overflow: hidden;
    width: 100vw;
    height: 100vh;
}

.app-container {
    position: relative;
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
}

/* ---- Monitoring Bar (top) ---- */
.monitoring-bar {
    display: flex;
    align-items: stretch;
    height: 3vh;
    background: #000;
    z-index: 10;
    flex-shrink: 0;
    font-family: Arial, sans-serif;
    font-weight: bold;
    font-size: 2.5vh;
}

.monitor-field {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    white-space: nowrap;
    padding: 0 calc(0.3 * var(--Hd));
}

.monitor-yellow {
    background: #FFFF00;
    color: #000;
}

.monitor-black {
    background: #000;
    color: #FFFF00;
}

.monitor-label {
    margin-right: calc(0.2 * var(--Hd));
}

.monitor-value {
    /* inherits color from parent */
}

.monitor-field.monitor-cyan {
    background: #003333;
    color: #00FFFF;
}

/* ---- Coordinates Bar ---- */
.coordinates-bar {
    display: flex;
    align-items: center;
    height: 6vh;
    background: #000;
    border-bottom: 1px solid #333;
    padding: calc(0.2 * var(--Vd)) calc(0.5 * var(--Hd));
    font-family: Arial, sans-serif;
    font-weight: bold;
    font-size: 2.5vh;
    z-index: 10;
    flex-shrink: 0;
}

.coordinates-left {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-width: 0;
    gap: 0.3vh;
}

.coordinates-line {
    color: #FFFF00;
    line-height: 1;
}

.coordinates-cyan {
    color: #00FFFF;
}
/* Centre line turns bright green while live-location broadcast is active. */
.coordinates-line.coordinates-green,
.coordinates-line.coordinates-green .center-label {
    color: #00ff00;
}

/* Named-Event selector in the coordinates bar (COP audit). */
.cop-event-bar {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    gap: 0.5vh;
    margin: 0 1vh;
}
/* Phone (narrow) tactical view only: lift the event selector out of the
   header flex flow — where it pushed the weather bar around — and float it at
   the top-centre of the map, between the two compasses. On wide screens (and
   in tech view) it stays inline in the header, as originally. */
@media (max-width: 600px) {
    .cop-event-bar.cop-event-floating {
        position: absolute;
        top: 13vh;
        left: 50%;
        transform: translateX(-50%);
        z-index: 100;
        margin: 0;
        justify-content: center;
    }
}
.cop-event-select,
.cop-event-input {
    height: 4vh;
    background: #111;
    color: #e5e7eb;
    border: 1px solid #3a3a3a;
    border-radius: 0.4vh;
    font-family: Arial, sans-serif;
    font-size: 1.9vh;
    padding: 0 0.8vh;
    outline: none;
}
.cop-event-select { max-width: 22vh; }
.cop-event-input { width: 16vh; }
/* Event Hub chip (2026-07-20): the centre header control — shows the active
   event's name/state; one tap opens the Hub. Inherits the .cop-event-select
   frame so it sits exactly where the old dropdown did. */
.event-chip {
    display: flex;
    align-items: center;
    gap: 0.8vh;
    cursor: pointer;
    max-width: 30vh;
}
.event-chip:hover { border-color: #00CCCC; }
.event-chip-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: bold;
}
.event-chip-caret { color: #7f8da0; font-size: 1.6vh; flex: 0 0 auto; }
.cop-event-select:focus,
.cop-event-input:focus { border-color: #00CCCC; }
.cop-event-input::placeholder { color: #9ca3af; font-weight: normal; }

.weather-run-info {
    flex-shrink: 0;
    padding: 0.3vh 1.5vh;
    border-radius: 0.4vh;
    font-family: Arial, sans-serif;
    font-weight: bold;
    font-size: 2.2vh;
    white-space: nowrap;
    border: 2px solid rgba(255,255,255,0.3);
}

.coordinates-right {
    flex-shrink: 0;
    margin-left: calc(0.5 * var(--Hd));
    display: flex;
    align-items: center;
    gap: calc(0.6 * var(--Hd));
}

.coordinates-auth {
    color: #00FFFF;
    white-space: nowrap;
    font-size: 1vh;
}

/* ---- Map Container ---- */
.tv-map-slot {
    flex: 1;
    width: 100%;
    min-height: 0;
    position: relative;
}


#map {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}

/* ---- Action Buttons ---- */
.action-buttons-left,
.action-buttons-right {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    flex-direction: column;
    gap: calc(0.6 * var(--Vd));
    z-index: 20;
}

.action-buttons-left {
    left: calc(0.4 * var(--Hd));
}

.action-buttons-right {
    right: calc(0.4 * var(--Hd));
}

/* Smoke AQI legend (built in JS as #smoke-aqi-legend, carrying .fuel-legend so
   it shares the black legend card styling). In TACTICAL it is appended to
   .legend-stack and positioned by it — `.legend-stack .fuel-legend` resets
   position/inset, and outranks this rule, so the values below apply only in
   the other home: Technical view, where the card rides the map element. There
   it must clear the TV slot's own fuel legend (top-left) and sit LEFT of the
   right action-button column (10.5vh wide) plus a gap. Phone hides all
   legends. */
.smoke-legend {
    top: auto;
    left: auto;
    right: calc(10.5vh + 2.5vw);
    bottom: 9.5vh;
}

/* Disabled during Time-travel: light-grey, non-interactive. Overrides every
   state colour/glow (hence !important). Removing the class restores the button's
   real look, so its pre-toggle state comes back automatically. Applies to both
   .action-button and .hr-toggle-btn variants. */
.action-disabled::after,
.action-inactive::after {
    /* The glow rings live on ::after now — a greyed-out button must not
       keep a live halo pulsing behind the grey. */
    display: none !important;
}
.action-disabled,
.action-disabled.hr-active,
.action-disabled.live-loc-active,
.action-disabled.fw-warn-active {
    background: #b8b8b8 !important;
    background-image: none !important;
    border-color: #999 !important;
    box-shadow: none !important;
    animation: none !important;
    filter: none !important;
    opacity: 0.55 !important;
    pointer-events: none !important;
    cursor: default !important;
}

/* Same grey "not available" look but STILL CLICKABLE — for tools that explain
   themselves with a toast when tapped (Drive on a geozone without a road
   graph) instead of silently ignoring the tap. */
.action-inactive,
.action-inactive.hr-active {
    background: #b8b8b8 !important;
    background-image: none !important;
    border-color: #999 !important;
    box-shadow: none !important;
    animation: none !important;
    filter: none !important;
    opacity: 0.55 !important;
}

.action-button {
    width: 10.5vh;
    height: 10.5vh;
    border-radius: 50%;
    border: calc(0.2 * var(--Vd)) solid #555;
    font-family: 'Courier New', monospace;
    font-size: calc(1.3 * var(--Vd));
    font-weight: bold;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    text-shadow: 1px 1px 2px rgba(0,0,0,0.8);
    transition: transform 0.1s;
    padding: 0;
}

.action-button img,
.action-button svg {
    width: 7vh;
    height: 7vh;
    pointer-events: none;
    filter: drop-shadow(0 0 3px rgba(0,0,0,0.9));
}

/* Operational-marker icon holder (Task 7). The SVG is injected by
   fillMarkerMenuIcons(); the div sizes it. Menu cells default to 8vh; inside a
   button (button B) it inherits the button's 7vh svg sizing. */
.marker-menu-icon {
    width: 8vh;
    height: 8vh;
    display: flex;
    align-items: center;
    justify-content: center;
}
.marker-menu-icon svg {
    width: 100%;
    height: 100%;
}
.action-button .marker-menu-icon {
    width: 7vh;
    height: 7vh;
}

.action-button img {
    filter: brightness(0) invert(1) drop-shadow(0 0 3px rgba(0,0,0,0.9));
}

.action-button.icon-large img,
.action-button.icon-large svg {
    width: 8vh;
    height: 8vh;
}

.action-button.icon-xlarge svg {
    width: 9vh;
    height: 9vh;
}

.action-button.icon-small img,
.action-button.icon-small svg {
    width: 6vh;
    height: 6vh;
}

.action-button.icon-xsmall img,
.action-button.icon-xsmall svg {
    width: 5vh;
    height: 5vh;
}

.action-button:active {
    transform: scale(0.9);
}

.action-red { background: #cc0000; }
.action-red-blink {
    animation: red-blink 300ms ease-in-out infinite alternate;
}
@keyframes red-blink {
    from { background: #cc0000; box-shadow: 0 0 calc(0.6 * var(--Vd)) #cc0000; }
    to   { background: #FF4000; box-shadow: 0 0 calc(1.3 * var(--Vd)) #ff0000; }
}
/* AUTO area-ignition armed (button J long-press): a steady white-ringed red so the
   big "A" reads as a distinct, deliberate mode rather than the paint-mode blink. */
.auto-area-armed {
    background: #cc0000;
    box-shadow: 0 0 0 calc(0.25 * var(--Vd)) #ffffff, 0 0 calc(1.0 * var(--Vd)) #ff0000;
}
.action-orange { background: #cc6600; }
.action-orange-blink {
    animation: orange-blink 600ms ease-in-out infinite alternate;
}
@keyframes orange-blink {
    from { background: #cc6600; box-shadow: 0 0 calc(0.6 * var(--Vd)) #cc6600; }
    to   { background: #7a3d00; box-shadow: 0 0 calc(1.3 * var(--Vd)) #ff8800; }
}
.action-yellow { background: #cccc00; color: #000; }
.action-green { background: #00cc00; }
.action-cyan { background: #00cccc; color: #000; }
.action-yellow-blink {
    animation: yellow-blink 300ms ease-in-out infinite alternate;
}
@keyframes yellow-blink {
    from { background: #cccc00; box-shadow: 0 0 calc(0.6 * var(--Vd)) #cccc00; }
    to   { background: #7a7a00; box-shadow: 0 0 calc(1.3 * var(--Vd)) #ffff00; }
}
.action-yellowred-blink {
    animation: yellowred-blink 500ms ease-in-out infinite alternate;
}
@keyframes yellowred-blink {
    from { background: #cccc00; box-shadow: 0 0 calc(0.6 * var(--Vd)) #cccc00; }
    to   { background: #cc0000; box-shadow: 0 0 calc(1.3 * var(--Vd)) #ff0000; }
}
.action-blue { background: #0066cc; }
.action-blue-blink {
    animation: blue-blink 300ms ease-in-out infinite alternate;
}
@keyframes blue-blink {
    from { background: #0066cc; box-shadow: 0 0 calc(0.6 * var(--Vd)) #0066cc; }
    to   { background: #00107D; box-shadow: 0 0 calc(1.3 * var(--Vd)) #0088ff; }
}
/* Wind button F: blue=off, cyan-blink=arrows, dark-blue-blink=flow */
.action-wind { background: #0066cc; border-color: #0066cc; }
.action-wind.wind-arrows-blink {
    animation: wind-arrows-blink 400ms ease-in-out infinite alternate;
}
@keyframes wind-arrows-blink {
    from { background: #06b6d4; border-color: #06b6d4; box-shadow: 0 0 calc(0.6 * var(--Vd)) #06b6d4; }
    to   { background: #0e7490; border-color: #0891b2; box-shadow: 0 0 calc(1.3 * var(--Vd)) #22d3ee; }
}
.action-wind.wind-flow-blink {
    animation: wind-flow-blink 400ms ease-in-out infinite alternate;
}
@keyframes wind-flow-blink {
    from { background: #1e40af; border-color: #1e40af; box-shadow: 0 0 calc(0.6 * var(--Vd)) #1e40af; }
    to   { background: #1e3a8a; border-color: #1d4ed8; box-shadow: 0 0 calc(1.3 * var(--Vd)) #3b82f6; }
}
/* Warning/fire-weather (F) button: blue when off, pulsing orange glow when a
   warning map is active (overrides .action-wind's blue while the class is set). */
.action-wind.fw-warn-active {
    background: #ee7700;
    border-color: #ff8800;
    box-shadow: 0 0 calc(0.6 * var(--Vd)) #ff8800;
    position: relative;
}
.action-wind.fw-warn-active::after {
    content: "";
    position: absolute;
    inset: calc(-0.2 * var(--Vd));
    border-radius: 50%;
    box-shadow: 0 0 calc(1.7 * var(--Vd)) #ffaa33;
    pointer-events: none;
    animation: glow-pulse 700ms ease-in-out infinite alternate;
}
/* Action button A: bright-green blink while live-location broadcast is on. */
.action-button.live-loc-active {
    animation: live-loc-btn-blink 800ms ease-in-out infinite alternate;
}
@keyframes live-loc-btn-blink {
    from { background: #008800; border-color: #008800; box-shadow: 0 0 calc(0.5 * var(--Vd)) #00ff00; }
    to   { background: #00dd00; border-color: #00ff00; box-shadow: 0 0 calc(1.7 * var(--Vd)) #66ff66; }
}
.ignition-active {
    animation: ignition-blink 600ms ease-in-out infinite alternate;
}
@keyframes ignition-blink {
    from { background: #cc6600; box-shadow: 0 0 calc(0.6 * var(--Vd)) #cc6600; }
    to   { background: #7a3d00; box-shadow: 0 0 calc(1.3 * var(--Vd)) #ff8800; }
}
.area-active {
    animation: area-blink 600ms ease-in-out infinite alternate;
}
@keyframes area-blink {
    from { background: #cccc00; box-shadow: 0 0 calc(0.6 * var(--Vd)) #cccc00; }
    to   { background: #7a7a00; box-shadow: 0 0 calc(1.3 * var(--Vd)) #ffff00; }
}
.cm-active {
    animation: cm-blink 600ms ease-in-out infinite alternate;
}
@keyframes cm-blink {
    from { background: #00cc00; box-shadow: 0 0 calc(0.6 * var(--Vd)) #00cc00; }
    to   { background: #006600; box-shadow: 0 0 calc(1.3 * var(--Vd)) #00ff00; }
}
.action-black { background: #222; border-color: #666; }

/* Left buttons override — all dark blue for tactical look */
.action-buttons-left .action-button {
    background: #0052CC;
    border-color: #0044AA;
}

.action-buttons-left .action-button.action-yellow img {
    filter: brightness(0) invert(1) drop-shadow(0 0 3px rgba(0,0,0,0.9));
}

/* Secondary toggle pills (CP, WI, HD, S) — uniform two-state look:
   dark gray when off, lit green with halo when on. The HD variant
   keeps its lock-blink animation on top of the green so users still
   see the "LOCKED ON" cue visually. */
.hr-toggle-btn {
    width: 6.5vh;
    height: 6.5vh;
    border-radius: 50%;
    background: #4b5563;          /* off — dark gray */
    border: 0.3vh solid #9ca3af;  /* lighter gray rim */
    color: #fff;
    font-family: Arial, sans-serif;
    font-weight: bold;
    font-size: 2.2vh;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 0 0.5vh rgba(0,0,0,0.5);
    text-shadow: 1px 1px 2px rgba(0,0,0,0.8);
    flex-shrink: 0;
}
.hr-toggle-btn.hr-active {
    background: #16a34a;          /* on — lit green */
    border-color: #fff;
    box-shadow: 0 0 0.6vh rgba(22,163,74,0.8);
    position: relative;
}
/* Slow pulse to draw the eye to any toggled-on secondary button — uniform
   across CP / WI / HD / S so they share the same on-state grammar.
   PERF (review 2026-08-18, finding 6): the pulse used to animate
   background + box-shadow on the button itself — paint-only properties,
   repainted at 60 fps forever (HD lock keeps at least one lit at all
   times) and invalidating the icon's drop-shadow cache with every frame.
   The face is now STATIC and only this ::after glow ring pulses, via
   opacity — composited, no repaint. Same treatment on hr-computing,
   fw-warn-active and live-loc below. */
.hr-toggle-btn.hr-active::after {
    content: "";
    position: absolute;
    inset: -0.3vh;
    border-radius: 50%;
    box-shadow: 0 0 1.6vh rgba(34,197,94,1.0);
    pointer-events: none;
    animation: glow-pulse 600ms ease-in-out infinite alternate;
}
@keyframes glow-pulse {
    from { opacity: 0.15; }
    to   { opacity: 1; }
}
/* Quick-export share button: same small green look as the toggles (HD / 4D),
   but it's a permanent one-shot ACTION, not an on/off toggle — so it stays a
   STATIC lit green with no hr-active pulse. */
.hr-toggle-btn.share-static {
    background: #16a34a;
    border-color: #fff;
    box-shadow: 0 0 0.8vh rgba(22,163,74,0.55);
}
/* SNAP / Broadcast: the same static green, for the same reason. It is a
   one-shot ACTION, so it never gets .hr-active and was falling through to the
   toggles' OFF grey — reading as unavailable exactly when it is ready
   (operator report 2026-08-10). Grey now means only what it means everywhere
   else: you cannot press this. `.action-inactive` still wins on a closed
   event, since that rule carries !important. */
.hr-toggle-btn.snap-btn {
    background: #16a34a;
    border-color: #fff;
    box-shadow: 0 0 0.8vh rgba(22,163,74,0.55);
}
/* Smoke button WHILE the smoke engine is computing: pulse ORANGE to signal
   "working". Declared AFTER .hr-active so it overrides the green on-state pulse
   when both classes are present; when computing ends the class is dropped and
   the button reverts to the standard green hr-active-pulse. */
.hr-toggle-btn.hr-computing {
    background: #ea580c;          /* working — lit orange */
    border-color: #fff;
    box-shadow: 0 0 0.6vh rgba(234,88,12,0.8);
    position: relative;
}
.hr-toggle-btn.hr-computing::after {
    content: "";
    position: absolute;
    inset: -0.3vh;
    border-radius: 50%;
    box-shadow: 0 0 1.6vh rgba(251,146,60,1.0);
    pointer-events: none;
    animation: glow-pulse 600ms ease-in-out infinite alternate;
}

/* ---- License Overlay ---- */
.license-overlay {
    position: absolute;
    bottom: 9vh;
    left: 50%;
    transform: translateX(-50%);
    background: rgba(0, 0, 0, 0.55);
    color: #00FFFF;
    font-family: Arial, sans-serif;
    font-weight: normal;
    font-size: 1.5vh;
    padding: 0.3vh 1vw;
    border-radius: 0.3vh;
    z-index: 21;
    white-space: nowrap;
    pointer-events: auto;
    touch-action: manipulation;
    -webkit-user-select: none;
    user-select: none;
    cursor: default;
}

.license-bold {
    font-weight: bold;
}

/* ---- Time Slider ---- */
.time-slider-container {
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
    height: 8vh;
    background-color: rgba(0, 0, 0, 0.8);
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 20;
    flex-shrink: 0;
}

.time-slider {
    width: 96%;
    height: 5vh;
    background-color: transparent;
    border-radius: calc(0.5 * var(--Vd));
    position: relative;
    display: flex;
    align-items: center;
    justify-content: space-between;
}
.time-slider-day {
    position: absolute;
    top: 0; bottom: 0;
    background-color: #1a2a2a;
    border: calc(0.3 * var(--Vd)) solid #00cccc;
    border-radius: calc(0.5 * var(--Vd));
    z-index: 0;
    pointer-events: none;
}
.time-slider-night {
    position: absolute;
    top: 0; bottom: 0;
    background-color: #111828;
    border: calc(0.3 * var(--Vd)) solid #3366cc;
    border-radius: calc(0.5 * var(--Vd));
    z-index: 0;
    pointer-events: none;
}

.time-marker {
    position: absolute;
    width: 6.5vh;
    height: 6.5vh;
    background-color: rgba(255, 0, 0, 0.6);
    border: 0.4vh solid #FF0000;
    border-radius: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    cursor: pointer;
    box-shadow: 0 0 calc(0.5 * var(--Vd)) rgba(0, 0, 0, 0.5);
    z-index: 10;
}

/* Event-range START scrubber: a red inverted (point-down) triangle at the
   range's oldest edge, sitting just above the track. Greyed and semi-faded when
   the range is bypassed (long-press). Positioned by style:left (%) in the same
   .time-slider frame as .time-marker. */
.time-start-marker {
    position: absolute;
    top: 0.2vh;
    transform: translateX(-50%);
    width: 0;
    height: 0;
    border-left: 2.2vh solid transparent;
    border-right: 2.2vh solid transparent;
    border-top: 3.4vh solid #FF0000;   /* points DOWN toward the track */
    cursor: pointer;
    z-index: 11;                        /* above the round last marker (10) */
    filter: drop-shadow(0 0 0.4vh rgba(0, 0, 0, 0.6));
    touch-action: none;
}
.time-start-marker.inactive {
    border-top-color: #888888;
    opacity: 0.6;
}
/* Yellow selected-range band spanning start → last. Behind the ticks/markers,
   above the month background. */
.time-range-fill {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    height: 5vh;
    /* SOLID yellow (not transparent). Sits ABOVE the month background (z 0) but
       BELOW the day numbers, ticks and block marks (z 2+), so the range reads as
       a solid yellow band with every tick and hour still visible on top. */
    background: #FFD400;
    border-left: 0.3vh solid #E0B000;
    border-right: 0.3vh solid #E0B000;
    z-index: 1;
    pointer-events: none;
}

.time-hour-mark {
    position: absolute;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    height: 100%;
    cursor: pointer;
    color: #FFFF00;
    font-family: Arial, sans-serif;
    font-weight: bold;
    font-size: calc(2 * var(--Vd));
    width: auto;
    padding: 0 calc(0.3 * var(--Hd));
    transform: translateX(-50%);
    padding-bottom: calc(1.4 * var(--Vd));
    z-index: 2;
}

/* Vertical tick mark for each hour */
.time-hour-mark::before {
    content: "";
    position: absolute;
    bottom: 0;
    height: calc(0.9 * var(--Vd));
    width: calc(0.2 * var(--Hd));
    background-color: #00FFFF;
    z-index: 1;
    left: 50%;
    transform: translateX(-50%);
}

.time-hour-mark:hover {
    background-color: rgba(255, 255, 255, 0.1);
}

.time-hour-mark.active {
    color: #FF0000;
}
.time-hour-mark.next-day {
    color: #00FFFF;
}
.time-hour-mark.next-day.active {
    color: #FF0000;
}

/* ============================================================
   30-DAY TIME-TRAVEL SCRUBBER (Historical View)
   Shown in place of the hourly scrubber while Time-travel is on.
   Reuses .time-slider-container / .time-slider / .time-marker and the
   .time-hour-mark geometry (font size + tick), but the labels/ticks are
   BLACK and the background is painted per-calendar-month:
     current month = green (dark-green frame), past month(s) = cyan (blue frame).
   ============================================================ */
/* ── EVENTS view navigation bar (snapshot travel) ──
   Same geometry as the Time-travel scrubber (a 96%-wide track inside
   .time-slider), but a light plate with black day separators, and the coloured
   marks live INSIDE the bar: each day is 8 invisible 3-hour slots, painted only
   where a snapshot exists — red = still-open event, green = closed only. */
.events-day {
    position: absolute;
    top: 0;
    height: 100%;
    background-color: #ffffff;
    border-right: calc(0.22 * var(--Vd)) solid #000;
    box-sizing: border-box;
    pointer-events: none;
}
.events-day:first-of-type { border-left: calc(0.22 * var(--Vd)) solid #000; }

/* Hourly banding. Zoomed IN the hour cell IS the plate; zoomed OUT a
   numberless band grid sits under the day frames so time-of-day still reads
   (operator decision 2026-08-09). Parity is keyed to the NUMBER the hour/day
   carries, never to its position, so a given hour always wears the same
   shade however the window is paged: odd = white, even = grey. */
.events-band {
    position: absolute;
    top: 0;
    height: 100%;
    background-color: #ffffff;
    pointer-events: none;
}
/* Hour number along the BOTTOM of each band in the wide span — the day
   label owns the top row. Smaller than the zoomed-in hour numbers: 72 of
   these share the bar. */
.events-band-hour {
    position: absolute;
    bottom: 0.1vh;
    left: 50%;
    transform: translateX(-50%);
    color: #333;
    font-weight: bold;
    font-size: 1.1vh;
    font-family: Arial, sans-serif;
}
.events-day.events-cell-alt,
.events-band.events-cell-alt { background-color: #e2e2e2; }

/* Transparent day plate for the wide span — separator + labels only, so the
   hour bands beneath stay visible. */
.events-day-frame {
    position: absolute;
    top: 0;
    height: 100%;
    background: transparent;
    border-right: calc(0.22 * var(--Vd)) solid #000;
    box-sizing: border-box;
    pointer-events: none;
}
.events-frame-first { border-left: calc(0.22 * var(--Vd)) solid #000; }
/* "9 Aug" sits on the top row, clear of the hour numbers below, with a
   plate behind it so it stays readable over the alternating bands. */
.events-day-frame .events-day-num {
    background: rgba(255, 255, 255, 0.82);
    padding: 0 0.4vh;
    border-radius: 2px;
}
.events-day-num {
    position: absolute;
    top: 0.1vh;
    left: 0.4vh;
    color: #000;
    font-weight: bold;
    font-size: 2.1vh;
    font-family: Arial, sans-serif;
}
.events-day-month {
    position: absolute;
    bottom: 0.1vh;
    left: 0.4vh;
    color: #000;
    font-weight: bold;
    font-size: 1.5vh;
    font-family: Arial, sans-serif;
    white-space: nowrap;
}
/* Snapshot marks — full-height bands so they read at a glance; they sit above
   the day plate but below the marker. The coloured mark keeps its true 1-hour
   width for accuracy; its parent .events-slot-hit is a transparent tap target
   ~2.5x wider (wide-span cells are ~6px — spec §3.3). */
/* The Events scrubber is driven by the TRACK, not by dragging the circle, so
   the circle must never intercept a pointer: at 6.5vh it covers ~15 of the
   10-minute slots around it and was swallowing the taps meant to move it.
   (The Historical marker keeps its own drag handler — hence the scoping.) */
.time-marker.events-marker { pointer-events: none; }

.events-slot-hit {
    position: absolute;
    top: 0;
    height: 100%;
    cursor: pointer;
    z-index: 3;
    background: transparent;
    /* 10-minute slots are ~4px wide in the 3-day span; the tap target has to
       stay finger-sized regardless (touch-parity rule). Painted slots are
       sparse, so overlapping hit areas only matter for back-to-back saves. */
    min-width: 22px;
}
.events-slot {
    position: absolute;
    top: 0;
    height: 100%;
    pointer-events: none;
    /* Keep a thin mark visible even at 10-min resolution. */
    min-width: 3px;
}
/* Event state (operator decision 2026-08-09): OPEN = GREEN (live, still
   running), CLOSED = RED (finalized). This is the INVERSE of the original
   events-redesign spec; the map anchors and the event chip follow the same
   rule so the three never disagree. */
.events-slot.open   { background-color: #00c000; }
.events-slot.closed { background-color: #ff1f1f; }
/* Cyan = before today (local midnight), both spans — spec §3.3. A light wash
   on the plate: the RED/GREEN marks above must stay unambiguous. Two tones so
   the odd/even banding survives into the past region instead of flattening. */
.events-day.events-cell-past,
.events-band.events-cell-past { background-color: #d3f3f9; }
.events-day.events-cell-past.events-cell-alt,
.events-band.events-cell-past.events-cell-alt { background-color: #b7e9f2; }
/* Narrow span: heavier separator where the day rolls over. */
.events-day.events-cell-dayline { border-left: calc(0.45 * var(--Vd)) solid #000; }
/* T1 relocate armed (spec §1.2) — a THICK pulsing cyan ring around the pin.
   Two jobs: it is the affordance a TOUCHSCREEN gets (the cursor:move change
   is a mouse-only bonus and must never be the only signal), and it is a much
   bigger GRIP than the 36px icon, so a gloved finger can drag the pin
   without covering it. Deliberately NOT pointer-events:none — the ring is
   part of the marker's hit area, which is the whole point. */
.marker-relocating { position: relative; }
/* The ring is CONCENTRIC WITH THE ICON, not with the container: the container
   stacks label + icon (+ dates on anchors), so the old container-anchored,
   bottom-offset ring sat visibly above the symbol (operator report
   2026-08-19). It lives on the icon slot instead (.mk-icon-slot for
   operational markers, .anchor-icon for event anchors) and centres on it
   whatever the icon size. Pseudo-element areas hit-test as their element,
   so the oversized grip is preserved. */
.marker-relocating .mk-icon-slot,
.marker-relocating .anchor-icon { position: relative; }
.marker-relocating .mk-icon-slot::before,
.marker-relocating .anchor-icon::before {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    width: 9vh;
    height: 9vh;
    margin: -4.5vh 0 0 -4.5vh;
    box-sizing: border-box;
    border: 0.55vh solid #22d3ee;
    border-radius: 50%;
    background: rgba(34, 211, 238, 0.14);
    animation: marker-relocate-pulse 0.85s ease-in-out infinite;
}
@keyframes marker-relocate-pulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(34, 211, 238, 0.55); opacity: 1; }
    50%      { box-shadow: 0 0 1.6vh 0.5vh rgba(34, 211, 238, 0.15); opacity: 0.72; }
}

/* Narrow span: the date caption floats OVER the cells instead of living
   inside one — an hour cell is ~4% of the bar and the label is wider than
   that, so as a child it was clipped by the next cell's opaque plate.
   Nudged right of the day line it marks, and above the cells in z-order. */
.events-caption-float {
    z-index: 2;
    margin-left: 0.3vh;
    pointer-events: none;
}
/* Narrow span: hour number on every cell (smaller than the day number). */
.events-hour-num {
    position: absolute;
    top: 0.1vh;
    left: 50%;
    transform: translateX(-50%);
    color: #000;
    font-weight: bold;
    font-size: 1.5vh;
    font-family: Arial, sans-serif;
}
/* UNUSED since 2026-08-09 — the Events bar's cyan read-out was removed; the
   heading chip names the selected event and carries its open/closed state.
   Kept only so a future readout has a style to reuse. */
.events-label {
    position: absolute;
    bottom: 0.2vh;
    left: 50%;
    transform: translateX(-50%);
    color: #00e5ff;
    font-weight: bold;
    font-size: 1.8vh;
    font-family: Arial, sans-serif;
    white-space: nowrap;
    text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.9);
    pointer-events: none;
}

/* ── Day-window paging arrows (Historical View scrubber) ──
   Overlay the container's black margins beside the 96%-wide slider; the left
   one pages 30 days back (always), the right one forward (only when the window
   is behind today). z-index above the slider so taps land on the chevron. */
.day-page-arrow {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 4.5vh;
    height: 6vh;
    background: transparent;
    border: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 25;
    padding: 0;
}
.day-page-arrow svg { width: 3.6vh; height: 3.6vh; }
.day-page-arrow.left  { left: 0.2vh; }
/* Same slot as the span toggle — they alternate on `day_window_offset == 0`,
   never coexist (see the Z button in app.rs). */
.day-page-arrow.right { right: 0.2vh; }

/* ── Scrubber SPAN toggle (30 day cells / 15) ──
   Right edge of the container, past the newest day cell. Placeholder glyph;
   cyan marks the narrow span so the state reads without a label. */
.day-span-toggle {
    position: absolute;
    top: 50%;
    right: 0.2vh;
    transform: translateY(-50%);
    width: 4.5vh;
    height: 6vh;
    background: transparent;
    border: none;
    cursor: pointer;
    z-index: 25;
    padding: 0;
    color: #fff;
    font-family: Arial, sans-serif;
    font-weight: bold;
    font-size: calc(2.6 * var(--Vd));
    text-shadow: 0 0 3px rgba(0, 0, 0, 0.9);
}
.day-span-toggle.narrow { color: #00e5ff; }
.day-span-toggle:active { transform: translateY(-50%) scale(0.9); }
.day-page-arrow:active { transform: translateY(-50%) scale(0.9); }

/* ── WEATHER availability strip (Historical View, under the day scrubber) ──
   One dash per LOCAL 6-hour block — the same blocks the scrubber steps through,
   so each dash sits directly under its tick and answers "is there weather for
   THIS step?". Sits in the container's lower
   band, below the slider bar. pointer-events:none — purely informational, the
   scrubber stays draggable straight through it. */
/* Exactly the day track's [2%, 98%] span IN THE SLIDER'S OWN FRAME (the strip
   is a child of #daySlider — the ticks are positioned in slider coords, and a
   container-frame strip is double-inset by the slider's centred 96% width and
   drifts off the ticks). Gapless 120-column grid (30 days × 4 six-hour
   segments): every 4th boundary lands precisely on a day-cell edge, 4 dashes
   between consecutive ticks. Hangs just below the bar via negative bottom. */
.weather-strip {
    position: absolute;
    left: 2%;
    width: 96%;
    bottom: -1.7vh;
    display: grid;
    grid-template-columns: repeat(120, 1fr);
    pointer-events: none;
    z-index: 5;
}
.weather-seg { height: 0.4vh; width: 72%; justify-self: center; border-radius: 0.2vh; }
.weather-seg.ok      { background: #22c55e; }
.weather-seg.stale   { background: #ffd400; }
.weather-seg.missing { background: #e11d1d; }
.weather-seg.future  { background: rgba(255, 255, 255, 0.12); }

.day-slider-month {
    position: absolute;
    top: 0; bottom: 0;
    border-radius: calc(0.5 * var(--Vd));
    box-sizing: border-box;
    z-index: 0;
    pointer-events: none;
}
/* Current-month green = the date-weather header's "fresh run" green (#00DD00). */
.day-slider-month.cur  { background-color: #00DD00; border: calc(0.3 * var(--Vd)) solid #007700; }
.day-slider-month.past { background-color: #00e5ff; border: calc(0.3 * var(--Vd)) solid #1e5fd0; }

/* MONTH/YY caption pinned to the bottom-left of each month segment */
.day-slider-month-label {
    position: absolute;
    bottom: calc(0.1 * var(--Vd));
    left: calc(0.5 * var(--Hd));
    color: #000;
    font-family: Arial, sans-serif;
    font-weight: bold;
    font-size: calc(1.3 * var(--Vd));
    white-space: nowrap;
    pointer-events: none;
    z-index: 3;
}

/* Day-of-month marks: inherit .time-hour-mark geometry, recolour to black and
   lift the number a little higher to clear the month caption. */
.day-mark {
    color: #000 !important;
    padding-bottom: calc(2.2 * var(--Vd)) !important;
}
.day-mark::before { background-color: #000 !important; }
.day-mark.active { color: #FF0000 !important; }

/* ── 6-hour block sub-ticks (4 per day cell) ──
   Shorter and lighter than the day boundaries so the day rhythm still reads at a
   glance while every scrub step has a target. Positioned like .time-hour-mark
   (centred on its block) but with no label. Blocks still in the future — today,
   after `now` — are dimmed and inert, matching the weather strip's future state. */
.day-block-mark {
    position: absolute;
    bottom: 0;
    transform: translateX(-50%);
    width: calc(2.2 * var(--Hd));
    height: calc(1.8 * var(--Vd));
    cursor: pointer;
    z-index: 4;
}
/* Half the height of the day tick (.time-hour-mark::before, 0.9Vd), same black
   as the day marks but faded — reads as a subdivision, not a boundary. */
.day-block-mark::before {
    content: "";
    position: absolute;
    left: 50%;
    bottom: 0;
    transform: translateX(-50%);
    width: calc(0.2 * var(--Hd));
    height: calc(0.45 * var(--Vd));
    background-color: rgba(0, 0, 0, 0.5);
}
.day-block-mark.active::before { background-color: #FF0000; }
.day-block-mark.future { pointer-events: none; }
.day-block-mark.future::before { background-color: rgba(0, 0, 0, 0.12); }

/* ============================================================
   EXPORT MENU — full-screen black overlay, 4 coloured options
   (PNG cyan · SHP red · KMZ yellow · Report green)
   ============================================================ */
.export-menu-overlay {
    position: fixed;
    inset: 0;
    z-index: 12000;
    background: #000;
    display: flex;
    align-items: center;
    justify-content: center;
}
.export-menu-close {
    position: absolute;
    top: 2vh;
    right: 2.5vh;
    width: 7vh;
    height: 7vh;
    border-radius: 50%;
    border: none;
    background: #cc0000;
    color: #fff;
    font: bold 3.5vh Arial, sans-serif;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
}
.export-menu-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 4vh;
    align-items: center;
    justify-content: center;
    max-width: 90vw;
}
.export-opt {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1.4vh;
    width: 26vh;
    height: 26vh;
    border-radius: 2vh;
    border: 0.4vh solid currentColor;
    background: rgba(255, 255, 255, 0.04);
    cursor: pointer;
    font: bold 3vh Arial, sans-serif;
    transition: background 0.12s, transform 0.08s;
}
.export-opt:hover { background: rgba(255, 255, 255, 0.12); }
.export-opt:active { transform: scale(0.96); }
.export-opt svg { width: 11vh; height: 11vh; }
.export-opt span { letter-spacing: 0.03em; }
/* Each option carries its bright brand colour (icon border + glyph + label). */
/* Operator-set palette (2026-07-26), in menu order:
   ScreenShot white · GIF cyan · SHP green · KMZ yellow ·
   Population Report orange · Detailed Report red. */
.export-opt-white  { color: #FFFFFF; }
.export-opt-cyan   { color: #00E5FF; }
.export-opt-green  { color: #22DD55; }
.export-opt-yellow { color: #FFD400; }
.export-opt-orange { color: #FF9500; }
.export-opt-red    { color: #FF3B30; }

/* ---- Notification Bar ---- */
.warning-overlay {
    position: absolute;
    /* Sit just BELOW the header (monitoring + coordinates bars), never over it.
       --header-h is measured at runtime (index.html); 9vh = the desktop header. */
    top: calc(var(--header-h, 9vh) + 1vh);
    left: 50%;
    transform: translateX(-50%);
    height: 3vh;
    line-height: 3vh;
    padding: 0 1.5vh;
    border-radius: 0.4vh;
    z-index: 200;
    pointer-events: none;
    background: rgba(204, 0, 0, 0.7);
    color: white;
    font-family: Arial, sans-serif;
    font-size: 2.5vh;
    font-weight: bold;
    white-space: nowrap;
}

/* Active warning-map name: same central position/size as .warning-overlay but
   black, stacked just below it (so both can show), permanently white text. */
.fw-mode-banner {
    position: absolute;
    /* Below the header, stacked just under .warning-overlay (both can show). */
    top: calc(var(--header-h, 9vh) + 4.6vh);
    left: 50%;
    transform: translateX(-50%);
    /* Two lines now (name + data stamp) — the fixed 3vh height and
       line-height moved onto .fw-banner-name so the pill can grow. */
    padding: 0.2vh 1.5vh 0.4vh;
    border-radius: 0.4vh;
    z-index: 199;
    pointer-events: none;
    background: rgba(0, 0, 0, 0.85);
    color: #fff;
    font-family: Arial, sans-serif;
    font-size: 2.5vh;
    font-weight: bold;
    white-space: nowrap;
    text-align: center;
}
.fw-banner-name {
    line-height: 3vh;
}
/* The space/time stamp of the pixels on display — moved out of the legend
   (operator request 2026-08-18) so map name and data instant read together. */
.fw-banner-date {
    font-size: 1.7vh;
    line-height: 2.1vh;
    font-weight: normal;
    opacity: 0.85;
}

/* Operational zoom gate (operator decision 2026-08-09): zoomed out past
   OPS_MIN_ZOOM the only thing an event leaves on the map is its Emergency
   anchor marker. GL layers use `minzoom`; DOM markers (ignition pins, team
   markers) get this class from map-bridge's zoom listener. Anchors never
   carry it — they are the exception the rule exists for. */
.ops-zoom-hidden { display: none !important; }

/* ── Event anchors (events redesign spec §4.1) ─────────────────────────── */
.anchor-marker {
    display: flex;
    flex-direction: column;
    align-items: center;
    pointer-events: auto;
}
.anchor-icon {
    width: 34px;
    height: 34px;
    filter: drop-shadow(0 0 3px rgba(0, 0, 0, 0.6));
}
/* The injected icon MUST obey this box. Without it an SVG that carries its
   own width/height renders at intrinsic size — a 512 px Spain icon filled the
   screen with a green/red disc (operator report 2026-08-13). The generator no
   longer emits intrinsic sizes; this is the guard so no future set can. */
.anchor-icon svg { width: 100%; height: 100%; display: block; }
.anchor-label {
    font: 700 11px/1.2 Arial, sans-serif;
    color: #fff;
    /* Fallback for anchors pushed before the open/closed flag existed. */
    background: rgba(180, 20, 20, 0.85);
    padding: 1px 6px;
    border-radius: 3px;
    white-space: nowrap;
    margin-bottom: 2px;
}
/* Event state (operator decision 2026-08-09): OPEN = green, CLOSED = red.
   Matches the anchor's scope circle and the Events-bar slots. */
.anchor-label.anchor-open   { background: rgba(0, 140, 48, 0.88); }
.anchor-label.anchor-closed { background: rgba(180, 20, 20, 0.88); }
/* Date range under the pin (2026-08-13): "[6Aug-10Aug]" once closed,
   "[6Aug >]" while open. Same state colour as the name plate, a size down
   so the name stays the primary read. */
.anchor-dates {
    font: 700 10px/1.2 Arial, sans-serif;
    color: #fff;
    padding: 1px 5px;
    border-radius: 3px;
    white-space: nowrap;
    margin-top: 2px;
}
.anchor-dates.anchor-open   { background: rgba(0, 140, 48, 0.88); }
.anchor-dates.anchor-closed { background: rgba(180, 20, 20, 0.88); }
/* The anchor ICON carries the same state colour as its label — bright green
   while open, red once closed. The asset hardcodes red on both `stroke` and
   `fill`, so both are overridden; CSS beats presentation attributes. The
   `fill="none"` outer ring is excluded or the pin would fill in solid.
   A WHITE fill is the icon's backing plate (the Spain set puts one under
   every point symbol so it reads on satellite imagery), never part of the
   symbol, so it is excluded too — recolouring it turned the whole icon into
   a solid state-coloured blob. */
.anchor-state-open .anchor-icon svg [stroke]              { stroke: #00e05a; }
.anchor-state-open .anchor-icon svg
    [fill]:not([fill="none"]):not([fill="#FFFFFF"]):not([fill="#ffffff"])   { fill: #00e05a; }
.anchor-state-closed .anchor-icon svg [stroke]            { stroke: #ff1f1f; }
.anchor-state-closed .anchor-icon svg
    [fill]:not([fill="none"]):not([fill="#FFFFFF"]):not([fill="#ffffff"]) { fill: #ff1f1f; }
.anchor-focused .anchor-label {
    outline: 2px solid #fff;
}

/* ── Track staleness signal (events redesign spec §3.4) ─────────────────
   Peers: live = blinking green glow, stalled = steady yellow cast,
   long-stalled = blinking yellow. Hand-placed markers reuse the yellow
   states ("position not confirmed") without any green/live state. */
/* Peer track states (operator decision 2026-08-13), driven by
   validity::track_self_state at a 30 s position beat:
     live   (<5 min)  blinking green - reporting now
     steady (<10 min) solid green    - a few beats missed
     stale  (<30 min) yellow
     old    (>=30 min) orange        - LAST KNOWN position, never removed
   A stale/old marker can be dismissed by hand; a fresher fix restores it. */
/* PERF (review 2026-08-18, finding 6): the blink used to animate `filter:
   drop-shadow` — a full re-render of the marker into a filter buffer every
   frame, on elements MapLibre also transforms every pan frame, times the
   number of live peers. The shadow is now STATIC (cached once) and the
   blink rides opacity alone, which composites. */
.peer-live {
    filter: drop-shadow(0 0 calc(0.9 * var(--Vd, 1vh)) #00f020);
    animation: peer-live-blink 900ms ease-in-out infinite alternate;
}
@keyframes peer-live-blink {
    from { opacity: 1; }
    to   { opacity: 0.6; }
}
/* Solid green: no animation, so a still dot reads as "not reporting right
   now" at a glance while staying a trusted position. */
.peer-steady { filter: drop-shadow(0 0 calc(0.6 * var(--Vd, 1vh)) #00e000); }
.peer-stale { filter: sepia(1) saturate(6) hue-rotate(10deg); }
.peer-old { filter: sepia(1) saturate(6) hue-rotate(-15deg) brightness(1.05); }
/* Hand-placed markers keep their own two-state "position not confirmed"
   treatment; they are not peer tracks and are unaffected by the above. */
.marker-stalled { filter: sepia(1) saturate(6) hue-rotate(10deg); }
.marker-long {
    filter: sepia(1) saturate(6) hue-rotate(10deg);
    animation: peer-long-blink 600ms ease-in-out infinite alternate;
}
@keyframes peer-long-blink {
    from { opacity: 1; }
    to   { opacity: 0.35; }
}
.marker-hidden { display: none; }

.notification-bar {
    position: absolute;
    /* Below the header, never covering it (see --header-h in index.html). */
    top: calc(var(--header-h, 9vh) + 1vh);
    left: 50%;
    transform: translateX(-50%);
    height: 3vh;
    line-height: 3vh;
    padding: 0 1.5vh;
    border-radius: 0.4vh;
    z-index: 200;
    color: white;
    font-family: Arial, sans-serif;
    font-size: 2.5vh;
    font-weight: bold;
    white-space: nowrap;
    pointer-events: none;
}

.notif-loading {
    background: rgba(0, 0, 0, 0.7);
}

.notif-success {
    background: rgba(0, 128, 0, 0.7);
}

.notif-error {
    background: rgba(255, 0, 0, 0.7);
}

/* Cyan info toast — historical-sim weather provenance ("Used Weather: …"). */
.notif-cyan {
    background: rgba(0, 229, 255, 0.9);
    color: #000;
    font-weight: bold;
}

/* ---- Affected Infrastructure Popup ---- */
/* In-app update banner (Android) — top strip, tappable, auto-dismisses after 3 s. */
/* Background-update READY chip: PERMANENT until tapped, dead-centre
   horizontally (operator spec 2026-08-05: central position, bright green —
   Claude-Code style "relaunch to update"). Sits below the top data strips so
   it never covers the map-centre crosshair. */
.update-ready-toast {
    position: absolute;
    left: 50%;
    top: 12vh;
    transform: translateX(-50%);
    z-index: 400;
    background: #00e100;
    color: #000;
    font-family: Arial, sans-serif;
    font-size: 2.2vh;
    font-weight: bold;
    line-height: 1.2;
    padding: 1.3vh 2.8vh;
    border: 0.3vh solid #004d00;
    border-radius: 1.2vh;
    box-shadow: 0 0.4vh 1.2vh rgba(0, 0, 0, 0.55);
    cursor: pointer;
    white-space: nowrap;
}
.update-ready-toast:hover { background: #33ff33; }

.update-overlay {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    z-index: 400;
    padding: 1.4vh 2vh;
    background: rgba(34, 197, 94, 0.95);
    color: #000;
    font-family: Arial, sans-serif;
    font-size: 2.1vh;
    font-weight: bold;
    text-align: center;
    cursor: pointer;
    box-shadow: 0 0.3vh 0.8vh rgba(0, 0, 0, 0.4);
}
.affected-popup {
    position: absolute;
    top: 9vh;
    left: 33.3%;
    width: 33.3%;
    bottom: 8vh;
    z-index: 300;
    flex-direction: column;
    background: rgba(0, 0, 0, 0.92);
    overflow: hidden;
    font-family: Arial, sans-serif;
    color: white;
}
.affected-popup-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 1vh 1.5vh;
    font-size: 2.2vh;
    font-weight: bold;
    background: rgba(50, 50, 50, 0.9);
}
.affected-popup-close {
    background: #cc0000;
    color: white;
    border: none;
    border-radius: 0.3vh;
    padding: 0.3vh 1vh;
    font-size: 2vh;
    font-weight: bold;
    cursor: pointer;
    font-family: Arial, sans-serif;
}
/* Population toggle in the Impact Forecast header. Yellow rectangle, black
   text — deliberately high-contrast against the dark header so the headline
   number reads at a glance in the field. Square corners (border-radius 0)
   keep it visually distinct from the rounded red close button beside it.
   margin-left:auto pushes it and the close button to the right, leaving the
   title on the left despite the header's space-between. */
.affected-popup-pop {
    background: #ffff00;
    color: #000;
    border: none;
    border-radius: 0;
    padding: 0.3vh 1.2vh;
    margin-left: auto;
    margin-right: 1vh;
    font-size: 1.9vh;
    font-weight: bold;
    letter-spacing: 0.02em;
    cursor: pointer;
    font-family: Arial, sans-serif;
    white-space: nowrap;
}
.affected-popup-pop:hover { background: #ffff66; }
/* No figure available — greyed so it reads as "unknown", never as a real
   zero. All Spanish land is municipal, so an empty result is always missing
   data rather than a fire that reached nobody. */
.affected-popup-pop.nodata { background: #777; color: #ddd; }
/* Toggled ON — inverted so the active view is unmistakable. */
.affected-popup-pop.active { background: #fff; color: #000; }

/* Municipality / population list (the toggled view of the impact body). */
.impact-pop-list { display: flex; flex-direction: column; }
.impact-pop-item {
    padding: 0.8vh 1.5vh;
    border-bottom: 1px solid rgba(255, 255, 255, 0.12);
}
.impact-pop-l1 { font-size: 2vh; font-weight: bold; }
.impact-pop-l2 { font-size: 1.6vh; opacity: 0.75; margin-top: 0.2vh; }
.impact-pop-pop { font-weight: bold; }

.affected-popup-body {
    overflow-y: auto;
    padding: 0.5vh;
    flex: 1;
    min-height: 0;
}
.affected-empty {
    text-align: center;
    padding: 2vh;
    font-size: 2vh;
    color: #888;
}
.affected-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 1.8vh;
}
.affected-table th {
    text-align: left;
    padding: 0.5vh 1vh;
    font-weight: bold;
    color: #fbbf24;
    border-bottom: 1px solid #444;
}
.affected-table td {
    padding: 0.4vh 1vh;
    border-bottom: 1px solid #333;
}

/* ---- Impact Forecast list (two lines per impact) ----
   Replaces the old 6-column table, which forced long facility names to wrap and
   pushed the Northing column off-screen behind a horizontal scrollbar.
   Line 1: colour dot + type + FULL name.  Line 2: date, time, coordinates.
   The yellow heading names only the coordinate system (see .impact-coordhead). */
/* Modifier on .affected-popup-body for the Impact Forecast only. Setting
   overflow-y:auto makes the X axis compute to auto too, which is where the old
   horizontal scrollbar came from; the two-line rows wrap, so kill it here. NOT on
   the shared class — the Ops Report popup reuses that body and still wants it. */
.affected-popup-body.impact-body {
}
.impact-list {
    font-size: 1.8vh;
}
.impact-coordhead {
    /* Sticks while the list scrolls so the coordinate system stays identifiable. */
    position: sticky;
    top: 0;
    z-index: 1;
    background: #1a1a1a;
    color: #fbbf24;
    font-weight: bold;
    padding: 0.5vh 1vh;
    border-bottom: 1px solid #444;
}
.impact-item {
    padding: 0.6vh 1vh;
    border-bottom: 1px solid #333;
}
.impact-l1 {
    display: flex;
    /* flex-start, NOT center/baseline: a long name wraps inside its own span and
       grows the row, and centre alignment would then float the dot to the middle
       of the block instead of sitting on the first line. */
    align-items: flex-start;
    gap: 0.8vh;
}
.impact-dot {
    display: inline-block;
    width: 1.5vh;
    height: 1.5vh;
    border-radius: 50%;
    flex: 0 0 auto;
    /* Optically centres the dot on the first text line. */
    margin-top: 0.35vh;
}
.impact-type {
    font-weight: bold;
    flex: 0 0 auto;
}
.impact-name {
    /* Takes the remaining width and wraps; never truncated. */
    flex: 1 1 auto;
    min-width: 0;
    overflow-wrap: anywhere;
}
.impact-l2 {
    display: flex;
    align-items: baseline;
    gap: 1.2vh;
    flex-wrap: wrap;
    margin-top: 0.2vh;
    font-size: 1.6vh;
    opacity: 0.75;
}
.impact-coord {
    /* Monospace keeps easting/northing digits aligned down the list. */
    font-family: "Consolas", "Menlo", monospace;
    overflow-wrap: anywhere;
}

/* ---- Status Overlay ---- */
.status-overlay {
    position: absolute;
    bottom: calc(5.4 * var(--Vd));
    left: calc(0.4 * var(--Hd));
    background: rgba(0, 0, 0, 0.8);
    border: 1px solid #333;
    border-radius: calc(0.4 * var(--Vd));
    padding: calc(0.4 * var(--Vd)) calc(0.5 * var(--Hd));
    font-size: calc(0.9 * var(--Vd));
    z-index: 30;
    color: #888;
}

.status-label {
    color: #666;
}

.status-value {
    color: #00FFFF;
}

.status-sep {
    color: #333;
}

/* ---- View Toggle Button ---- */
.view-toggle-btn {
    background: #0052CC;
    color: #fff;
    border: none;
    padding: calc(0.4 * var(--Vd)) calc(0.7 * var(--Hd));
    font-family: Arial, sans-serif;
    font-size: 2.5vh;
    font-weight: bold;
    cursor: pointer;
    border-radius: calc(0.3 * var(--Vd));
    white-space: nowrap;
    flex-shrink: 0;
}

.view-toggle-btn:hover {
    filter: brightness(0.88);
}

/* ============================================================
   TECHNICAL VIEW (WildView-faithful 3-column layout)
   ============================================================ */

.tech-view {
    flex: 1;
    display: flex;
    min-height: 0;
    background: #1a1a1a;
}

/* ── Sidebars ── */
.tv-sidebar {
    width: 20vw;
    min-width: 200px;
    flex-shrink: 0;
    overflow-y: auto;
    background: #2a2a2a;
    border-right: 1px solid #444;
    padding: 0.8vh 0.5vw 10vh 0.5vw;
    display: flex;
    flex-direction: column;
    gap: 0.6vh;
    font-family: Arial, sans-serif;
    font-size: 1.7vh;
    color: #e0e0e0;
}

.tv-sidebar-right {
    border-right: none;
    border-left: 1px solid #444;
}

/* ── Monitor sections (left sidebar) ── */
.tv-section {
    margin-bottom: 0.5vh;
}

.tv-section-title {
    color: #fbbf24;
    font-family: Arial, sans-serif;
    font-size: 1.8vh;
    font-weight: 700;
    text-transform: uppercase;
    margin: 0 0 0.4vh 0;
    padding: 0;
    letter-spacing: 0.5px;
}

.tv-box {
    background: #1a1a1a;
    border: 1px solid #444;
    border-radius: 0.4rem;
    padding: 0.5vh 0.5vw;
    margin-bottom: 0.4vh;
}

.tv-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 0.35vh;
}

.tv-row-separator {
    margin-top: 0.5vh;
    padding-top: 0.4vh;
    border-top: 1px solid #333;
}

/* Centre-of-map crosshair — cyan hairline target marking the point the
   coordinates bar reads out. Child of the #map container, always centred. */
.map-center-crosshair {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 9px;
    height: 9px;
    margin-left: -4.5px;
    margin-top: -4.5px;
    box-sizing: border-box;
    border: 1px solid #00ffff;
    border-radius: 50%;
    pointer-events: none;
    z-index: 4;
    box-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
}
.map-center-crosshair::before,
.map-center-crosshair::after {
    content: "";
    position: absolute;
    background: #00ffff;
    box-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
}
.map-center-crosshair::before {   /* vertical hairline */
    left: 50%;
    top: -7px;
    width: 1px;
    height: 23px;
    margin-left: -0.5px;
}
.map-center-crosshair::after {    /* horizontal hairline */
    top: 50%;
    left: -7px;
    height: 1px;
    width: 23px;
    margin-top: -0.5px;
}

/* Live-location operator marker — blinking-green icon + pulse ring + nametag
   (black bg / green letters), pinned to the device's reported GPS position. */
.live-loc-marker {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    pointer-events: none;
}
/* Origin locator (long-press D): the ring dropped on the earliest detection in
   view. Deliberately FASTER than the relocate grip ring (0.85s) and the live
   location pulse (1.5s) — this one is an answer to a question just asked, and
   has to be findable at a glance among a field of dots. Sized generously so it
   reads as a ring AROUND the hotspot rather than a bigger hotspot. */
.origin-locator {
    width: 9vh;
    height: 9vh;
    box-sizing: border-box;
    border: 0.6vh solid #ffd400;
    border-radius: 50%;
    background: rgba(255, 212, 0, 0.12);
    cursor: pointer;
    animation: origin-locator-blink 0.4s steps(1, end) infinite;
}
/* Earliest detection: YELLOW/BLACK. It answers a different question from the
   estimated origin (what was SEEN first, versus what was COMPUTED), so it must
   never be mistaken for it at a glance - hence a different colour pair, same
   ring size. */
@keyframes origin-locator-blink {
    0%,
    49%  { border-color: #ffd400; box-shadow: 0 0 1.6vh rgba(255, 212, 0, 0.9); }
    50%,
    100% { border-color: #000000; box-shadow: 0 0 0.4vh rgba(0, 0, 0, 0.6); }
}

/* Estimated origin: RED/WHITE, the pair the app already uses for a computed
   result the operator is meant to act on. */
@keyframes origin-estimate-blink {
    0%,
    49%  { border-color: #ff3b30; box-shadow: 0 0 1.6vh rgba(255, 59, 48, 0.9); }
    50%,
    100% { border-color: #ffffff; box-shadow: 0 0 0.4vh rgba(255, 255, 255, 0.6); }
}

/* Readouts for the located detection and the estimated origin. Sit above the
   time bar, out of the way of the left tool column, and pointer-transparent so
   they never eat a map tap. Each box wears its ring's colour: yellow for what
   was SEEN first, red for what was COMPUTED. */
.origin-readout {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    bottom: 12vh;
    z-index: 900;
    display: flex;
    gap: 1.2vh;
    pointer-events: none;
}
.origin-readout-box {
    padding: 0.8vh 1.6vh;
    border-radius: 0.8vh;
    background: rgba(0, 0, 0, 0.78);
    border: 0.25vh solid;
    text-align: center;
    white-space: nowrap;
}
.origin-readout-box .origin-readout-title {
    font-weight: bold;
    font-size: 2.1vh;
}
.origin-readout-box .origin-readout-coords {
    font-family: monospace;
    font-size: 2.4vh;
}
.origin-readout-detect { border-color: #ffd400; color: #ffd400; }
.origin-readout-origin { border-color: #ff3b30; color: #ff3b30; }
/* Reverse Origin Location: ranked candidate rings. Gold for the field, the
   top pick reuses the locator's red/white blink so the eye lands on it first. */
.origin-candidate {
    width: 4.5vh;
    height: 4.5vh;
    box-sizing: border-box;
    border: 0.5vh solid #f0c419;
    border-radius: 50%;
    background: rgba(240, 196, 25, 0.15);
    cursor: pointer;
}
/* Raw physics argmax: a second opinion, drawn mute so it never competes with
   the adjudicated candidate rings. No fill, no blink, no pointer affordance. */
.origin-candidate-raw {
    width: 3vh;
    height: 3vh;
    border: 0.4vh solid #9aa4ae;
    border-radius: 50%;
    background: transparent;
    box-sizing: border-box;
    pointer-events: none;
}
.origin-candidate-top {
    width: 9vh;
    height: 9vh;
    border-color: #ff3b30;
    background: rgba(255, 59, 48, 0.12);
    animation: origin-estimate-blink 0.8s steps(1, end) infinite;
}
.live-loc-pulse {
    position: absolute;
    top: 0;
    left: 50%;
    width: 2.8vh;
    height: 2.8vh;
    margin-left: -1.4vh;
    border-radius: 50%;
    background: rgba(0, 255, 0, 0.35);
    animation: live-loc-pulse 1.5s ease-out infinite;
}
@keyframes live-loc-pulse {
    0%   { transform: scale(0.5); opacity: 0.8; }
    100% { transform: scale(2.4); opacity: 0; }
}
.live-loc-icon {
    position: relative;
    width: 2.8vh;
    height: 2.8vh;
    border-radius: 50%;
    border: 0.2vh solid #003300;
    display: flex;
    align-items: center;
    justify-content: center;
    background: #00cc00;
    box-shadow: 0 0 0.4vh rgba(0, 255, 0, 0.6);
}
.live-loc-icon::after {
    content: "";
    position: absolute;
    inset: -0.2vh;
    border-radius: 50%;
    box-shadow: 0 0 1.4vh rgba(0, 255, 0, 1);
    pointer-events: none;
    animation: glow-pulse 1s ease-in-out infinite alternate;
}
.live-loc-icon svg { width: 70%; height: 70%; }
.live-loc-tag {
    margin-top: 0.3vh;
    background: #000;
    color: #00ff00;
    font: bold 1.4vh Arial, sans-serif;
    padding: 0.15vh 0.6vh;
    border-radius: 0.3vh;
    white-space: nowrap;
    box-shadow: 0 0 0.4vh rgba(0, 0, 0, 0.7);
}

/* Burnt-area perimeter hover tooltip (black bg, white text). pointer-events:none
   so the tooltip (a DOM element that follows the cursor) never swallows a
   mousedown/click meant for the canvas — otherwise it blocks area-fire painting
   and point ignitions placed on top of a detected fire perimeter. */
.perim-tooltip { pointer-events: none; }
/* Reverse Origin Location popups reuse the perim-tooltip skin but are PINNED,
   not hover tooltips: they carry a close button that has to be reachable by
   both mouse and touch. Opt those two back into pointer events rather than
   weakening the rule above, which the painting tools depend on. */
.perim-tooltip.obt-closable,
.perim-tooltip.obt-closable .maplibregl-popup-close-button {
    pointer-events: auto;
}
.perim-tooltip .maplibregl-popup-content {
    background: #000;
    color: #fff;
    padding: 0.3vh 0.7vh;
    border-radius: 0.4vh;
    font: bold 1.5vh Arial, sans-serif;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.55);
}
.perim-tooltip .maplibregl-popup-tip { display: none; }
.perim-tt { white-space: nowrap; }

/* Fuel model legend overlay (on map, left side) */
.fuel-legend {
    position: absolute;
    top: 1vh;
    left: 1vh;
    z-index: 150;
    background: rgba(0, 0, 0, 0.85);
    border-radius: 0.6vh;
    padding: 1.2vh 1.5vh;
    max-height: 80vh;
    overflow-y: auto;
    font-family: Arial, sans-serif;
    pointer-events: auto;
}

.fuel-legend-title {
    color: #fff;
    font-size: 1.6vh;
    font-weight: bold;
    text-align: center;
    padding-bottom: 0.5vh;
    margin-bottom: 0.3vh;
    border-bottom: 1px solid #555;
}

.fuel-legend-section {
    color: #999;
    font-size: 1.2vh;
    font-style: italic;
    margin-top: 0.6vh;
    margin-bottom: 0.25vh;
    border-top: 1px solid #333;
    padding-top: 0.3vh;
}

.fuel-legend-row {
    display: flex;
    align-items: center;
    gap: 0.6vh;
    color: #e0e0e0;
    font-size: 1.28vh;
    padding: 0.12vh 0 0.12vh 0.8vh;
}

.fuel-legend-swatch {
    display: inline-block;
    width: 1.6vh;
    height: 1.6vh;
    min-width: 1.6vh;
    border-radius: 0.2vh;
    border: 1px solid rgba(255, 255, 255, 0.2);
}

.fuel-legend-row-active {
    background: rgba(255, 255, 255, 0.15);
    color: #fff;
    font-weight: bold;
    border-radius: 0.3vh;
}

/* Warning-map legend: relocate from the fuel-legend's top-left home (which
   collides with the compass + left action-button stack) to the lower-RIGHT,
   sitting LEFT of the right action-button column (10.5vh wide) and above the
   timeline — the open zone the operator pointed to. Two-class selector so it
   beats .fuel-legend regardless of rule order. */
.fuel-legend.warning-legend {
    top: auto;
    left: auto;
    /* clear the right action-button column (10.5vh wide) ... */
    right: calc(10.5vh + 2.5vw);
    /* ... and sit ABOVE the 8vh time-slider bar so it never covers the scrubber */
    bottom: 9.5vh;
    max-height: 70vh;
}

/* Bottom-right legend stack (tactical view): the detection-age legend and the
   warning-map legend share this column so both can show at once without
   overlapping. The stack owns the corner position; its child .fuel-legend
   panels drop their own absolute positioning and flow top-to-bottom. */
.legend-stack {
    position: absolute;
    right: calc(10.5vh + 2.5vw);
    bottom: 9.5vh;
    z-index: 150;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 1vh;
    pointer-events: none;
}
.legend-stack .fuel-legend {
    position: static;
    top: auto;
    left: auto;
    right: auto;
    bottom: auto;
    max-height: 40vh;
}
.detect-legend .fuel-legend-row { font-size: 1.4vh; }

/* Fuel name sub-row (below Fuel Model: code line) */
.tv-fuel-name {
    font-size: 1.5vh;
    font-weight: bold;
    text-align: left;
    margin-bottom: 0.35vh;
}

/* Data layers 3-column grid (Debug checkbox | Layer name | Source) */
.tv-layers-grid {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    column-gap: 0.5vw;
    row-gap: 0.25vh;
}

.tv-layer-name {
    color: #e0e0e0;
    font-size: 1.6vh;
    text-align: left;
}

/* Data Layers read-only load-state LED (not a checkbox — indicates that
   the layer is present in the loaded dataset). Dim by default; glows green
   when the layer name appears in the backend's loaded-layers list. */
.tv-layer-led {
    width: 1.4vh;
    height: 1.4vh;
    border-radius: 50%;
    background: #2a2a2a;
    border: 1px solid #3a3a3a;
    box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.6);
    justify-self: center;
}
.tv-layer-led.tv-layer-led-on {
    background: #4ade80;
    border-color: #22c55e;
    box-shadow:
        0 0 4px #4ade80,
        0 0 8px rgba(74, 222, 128, 0.6),
        inset 0 0 2px rgba(255, 255, 255, 0.3);
}

.tv-layer-src {
    color: #4ade80;
    font-size: 1.6vh;
    font-weight: 600;
    text-align: right;
}

.tv-label {
    color: #4ade80;
    font-weight: 400;
}

.tv-value {
    color: #e0e0e0;
    font-weight: 600;
    text-align: right;
}

.tv-value-green { color: #22c55e; }
.tv-value-red { color: #dc2626; }
.tv-value-yellow { color: #fbbf24; }
.tv-value-large { font-size: 2vh; }

/* Fire spread graph */
.tv-graph-box {
    margin-top: 0.4vh;
    position: relative;
}

.tv-graph-y-labels {
    display: flex;
    justify-content: space-between;
    font-size: 1vh;
    font-weight: 600;
    margin-bottom: 0.29rem;
}

.tv-graph-y-left { color: #fbbf24; }
.tv-graph-y-right { color: #dc2626; }

.tv-graph-canvas-wrapper {
    background: #111;
    border-radius: 0.29rem;
}

.tv-graph-canvas {
    width: 100%;
    height: 15vh;
    display: block;
}

.tv-graph-axis {
    display: flex;
    justify-content: space-between;
    color: #888;
    font-size: 1.4vh;
    padding: 0.2vh 0.3vw;
}

/* Checkbox rows */
.tv-checkbox {
    display: flex;
    align-items: center;
    gap: 0.3vw;
    padding: 0.15vh 0;
    cursor: pointer;
    color: #e0e0e0;
    font-size: 1.6vh;
}

.tv-checkbox input[type="checkbox"] {
    accent-color: #4ade80;
}

/* BASE MAPS box — orange accents, so map state reads distinctly from the
   engine state above it (ENGINE OVERRIDE is green). */
.tv-checkbox-orange input[type="checkbox"],
.tv-checkbox-orange input[type="radio"] {
    accent-color: #ff9500;
}

/* Rows for styles that exist in the UI but have no implementation yet
   (Online Cloudless Terrain). Non-interactive and visibly inert. */
.tv-checkbox-disabled {
    opacity: 0.42;
    cursor: not-allowed;
}

.tv-checkbox-disabled input {
    cursor: not-allowed;
}

/* One BASE MAPS row: enable checkbox left, "open with this one" radio right. */
.tv-basemap-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.4vw;
}

.tv-basemap-default {
    display: flex;
    align-items: center;
    gap: 0.2vw;
    font-size: 1.3vh;
    color: #999;
    white-space: nowrap;
}

/* Numbered step headings on the first-run setup screen (language, licence
   key, imagery). Left-aligned against the full-width controls beneath them so
   the three read as a sequence rather than as centred captions. */
.setup-step {
    width: 100%;
    text-align: left;
    color: rgba(255, 255, 255, 0.9);
    font-family: Arial, sans-serif;
    font-size: 1.9vh;
    font-weight: bold;
    letter-spacing: 0.03em;
    margin-bottom: 0.8vh;
}

/* Offline-imagery download confirm. App-level (rendered next to the
   notification toast) so it is visible from the technical view. */
.tv-confirm-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.62);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 400;
}

.tv-confirm {
    background: #1a1a1a;
    border: 1px solid #ff9500;
    border-radius: 0.5rem;
    padding: 2vh 2vw;
    max-width: 34vw;
    color: #e0e0e0;
    font-family: Arial, sans-serif;
}

.tv-confirm h3 {
    margin: 0 0 1vh;
    color: #ff9500;
    font-size: 2vh;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.tv-confirm p {
    margin: 0 0 1.6vh;
    font-size: 1.6vh;
    line-height: 1.45;
}

/* The "you cannot use the app meanwhile" line. Amber rather than red: it is a
   consequence to expect, not an error. */
.tv-confirm-warn {
    margin: 0 0 1.6vh;
    font-size: 1.6vh;
    line-height: 1.45;
    color: #ffb44d;
    font-weight: bold;
}

.tv-confirm-progress {
    margin-bottom: 1.6vh;
}

.tv-confirm-progress-track {
    width: 100%;
    height: 1.2vh;
    background: rgba(255, 255, 255, 0.15);
    border-radius: 0.6vh;
    overflow: hidden;
}

.tv-confirm-progress-label {
    margin-top: 0.6vh;
    text-align: center;
    font-size: 1.4vh;
    color: rgba(255, 255, 255, 0.6);
}

.tv-confirm-actions {
    display: flex;
    justify-content: flex-end;
    gap: 0.8vw;
}

.tv-confirm-actions button {
    background: #2a2a2a;
    border: 1px solid #444;
    color: #e0e0e0;
    font-family: Arial, sans-serif;
    font-size: 1.6vh;
    padding: 0.6vh 1.2vw;
    border-radius: 0.3rem;
    cursor: pointer;
}

.tv-confirm-actions button:disabled {
    opacity: 0.5;
    cursor: default;
}

.tv-confirm-primary {
    background: #ff9500;
    border-color: #ff9500;
    color: #1a1a1a;
    font-weight: bold;
}


/* Number inputs in override section */
.tv-num-input {
    width: 4vw;
    background: #2a2a2a;
    border: 1px solid #444;
    color: #fff;
    font-family: Arial, sans-serif;
    font-size: 1.6vh;
    padding: 0.2vh 0.3vw;
    text-align: center;
    border-radius: 0.3rem;
}

/* Collapsible container */
.tv-collapsible {
    flex-direction: column;
    gap: 0.6vh;
}

/* ── Center section ── */
.tv-center {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-width: 0;
}

/* Header bar inside tech view center */
.tv-header-bar {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5vw;
    height: 4.5vh;
    background: #000;
    border-bottom: 1px solid #444;
    padding: 0 0.7vw;
    flex-shrink: 0;
    position: relative;
}

.tv-header-btn {
    padding: 0.4vh 0.8vw;
    border: none;
    border-radius: 0.4rem;
    font-family: Arial, sans-serif;
    font-size: 2.5vh;
    font-weight: 700;
    cursor: pointer;
    white-space: nowrap;
}

.tv-header-input {
    padding: 0.4vh 0.6vw;
    border: 1px solid #4b5563;
    border-radius: 0.4rem;
    background: #111827;
    color: #e5e7eb;
    font-family: Arial, sans-serif;
    font-size: 2.5vh;
    width: 12vw;
    max-width: 180px;
}
.tv-header-input::placeholder { color: #9ca3af; }

.tv-btn-yellow { background: #fbbf24; color: #000; }
.tv-btn-yellow:hover { background: #f59e0b; }
.tv-btn-green { background: #22c55e; color: #000; }
.tv-btn-green:hover { background: #16a34a; }
.tv-btn-orange { background: #f97316; color: #000; }
.tv-btn-orange:hover { background: #ea6c0a; }
.tv-btn-gray { background: #808080; color: #fff; }
.tv-btn-gray:hover { background: #666; }
.tv-btn-blue { background: #3b82f6; color: #fff; }
.tv-btn-blue:hover { background: #2563eb; }
.tv-btn-red { background: #dc2626; color: #fff; }
.tv-btn-red:hover { background: #b91c1c; }

.tv-title {
    font-size: 2vh;
    font-weight: 700;
    margin: 0;
    white-space: nowrap;
    flex-shrink: 1;
    overflow: hidden;
    text-overflow: ellipsis;
}

.tv-title-yellow { color: #fbbf24; }

/* tv-map-slot in center uses same .tv-map-slot class defined above */

/* ── Right sidebar controls ── */
.tv-ctrl-group {
    margin-bottom: 0.8vh;
}

.tv-ctrl-label {
    display: block;
    color: #fbbf24;
    font-family: Arial, sans-serif;
    font-size: 1.6vh;
    font-weight: 700;
    margin-bottom: 0.3vh;
    text-transform: uppercase;
    letter-spacing: 0.3px;
}

/* Cyan label variant — distinguishes the SMOKE solver toggles from the
   amber FIRE controls in the Technical view's Accuracy Fine-tuning panel. */
.tv-ctrl-label.tv-label-cyan { color: #06b6d4; }

/* ── Simulation dial (Run/Stop) ── */
.tv-sim-dial {
    display: flex;
    gap: 0.5vw;
}

.tv-sim-btn {
    flex: 1;
    padding: 1vh 0;
    border: 2px solid #555;
    border-radius: 0.7rem;
    font-family: Arial, sans-serif;
    font-size: 1.8vh;
    font-weight: 700;
    cursor: pointer;
    text-align: center;
    transition: all 0.15s;
}

.tv-sim-run {
    background: #111;
    color: #4ade80;
    border-color: #0f0;
    box-shadow: 0 0.3vh #0a0, 0 0.6vh #050;
}

.tv-sim-run:hover { background: #1a3a1a; }

.tv-sim-run.tv-sim-active {
    background: #22c55e;
    color: #000;
    border-color: #86efac;
    box-shadow: 0 0.15vh #15803d;
    transform: translateY(0.15vh);
    animation: tv-blink-run 0.4s infinite;
}

@keyframes tv-blink-run {
    0%, 100% { background: rgb(86, 235, 0); }
    50% { background: rgb(144, 255, 144); }
}

.tv-sim-stop {
    background: #dc2626;
    color: #fff;
    border-color: #fca5a5;
    box-shadow: 0 0.15vh #900;
    transform: translateY(0.15vh);
}

.tv-sim-stop:hover { background: #b91c1c; }

.tv-sim-stop.tv-sim-active {
    background: #dc2626;
    border-color: #fca5a5;
    box-shadow: 0 0.15vh #900;
    transform: translateY(0.15vh);
}

/* When running, swap: run=pressed+blink, stop=unpressed */
.tv-sim-run.tv-sim-active ~ .tv-sim-stop {
    background: #111;
    color: #4ade80;
    border-color: #0f0;
    box-shadow: 0 0.3vh #0a0, 0 0.6vh #050;
    transform: none;
}

/* ── Toggle bar (generic radio-like) ── */
.tv-toggle-bar {
    display: flex;
    background: #212121;
    border: 2px solid #555;
    border-radius: 0.7rem;
    overflow: hidden;
    min-height: 3.7vh;
}

.tv-toggle-opt {
    flex: 1;
    padding: 0.4vh 0.3vw;
    background: transparent;
    color: #888;
    border: none;
    font-family: Arial, sans-serif;
    font-size: 1.8vh;
    font-weight: 700;
    cursor: pointer;
    text-align: center;
    transition: all 0.15s;
}

.tv-toggle-opt:hover { color: #ccc; }

.tv-toggle-opt.tv-toggle-active {
    background: #4ade80;
    color: #000;
}

.tv-toggle-opt.tv-toggle-active.tv-toggle-green { background: #4ade80; color: #000; }
.tv-toggle-opt.tv-toggle-active.tv-toggle-yellow,
.tv-toggle-opt.tv-toggle-yellow.tv-toggle-active { background: #fbbf24; color: #000; }
.tv-toggle-opt.tv-toggle-active.tv-toggle-orange,
.tv-toggle-opt.tv-toggle-orange.tv-toggle-active { background: #f97316; color: #000; }

/* Trigger bar colors */
.tv-trigger-bar .tv-trigger-points.tv-toggle-active { background: #fbbf24; color: #000; }
.tv-trigger-bar .tv-trigger-area.tv-toggle-active { background: #dc2626; color: #fff; }

/* ── Duration inputs ── */
.tv-duration {
    display: flex;
    gap: 0.5vw;
}

.tv-duration-field {
    flex: 1;
    display: flex;
    align-items: center;
    background: #1a1a1a;
    border: 2px solid #555;
    border-radius: 0.4rem;
    min-height: 3.5vh;
    padding: 0 0.3vw;
}

.tv-duration-input {
    flex: 1;
    background: transparent;
    border: none;
    color: #fff;
    font-family: Arial, sans-serif;
    font-size: 1.8vh;
    font-weight: 600;
    text-align: center;
    outline: none;
    width: 100%;
    min-width: 0;
}

.tv-duration-input::-webkit-inner-spin-button,
.tv-duration-input::-webkit-outer-spin-button {
    -webkit-appearance: none;
}

.tv-duration-unit {
    color: #888;
    font-size: 1.6vh;
    font-weight: 600;
    padding-left: 0.2vw;
}

.tv-divider {
    height: 1px;
    background: #444;
    margin: 0.5vh 0;
}

/* ── Sliders ── */
.tv-slider {
    width: 100%;
    height: 0.5vh;
    -webkit-appearance: none;
    appearance: none;
    background: #333;
    border-radius: 0.3rem;
    outline: none;
    margin: 0.3vh 0;
}

.tv-slider::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 2.5vh;
    height: 2.5vh;
    border-radius: 50%;
    background: #ef4444;
    border: 0.2vh solid #fca5a5;
    cursor: pointer;
}

.tv-slider-red::-webkit-slider-thumb { background: #ef4444; border-color: #fca5a5; }
.tv-slider-blue::-webkit-slider-thumb { background: #3b82f6; border-color: #93c5fd; }
.tv-slider-cyan::-webkit-slider-thumb { background: #06b6d4; border-color: #67e8f9; }
.tv-slider-amber::-webkit-slider-thumb { background: #f59e0b; border-color: #fcd34d; }
.tv-slider-purple::-webkit-slider-thumb { background: #a855f7; border-color: #d8b4fe; }
.tv-slider-orange::-webkit-slider-thumb { background: #f97316; border-color: #fdba74; }

.tv-slider-ticks {
    display: flex;
    justify-content: space-between;
    color: #fbbf24;
    font-size: 1.3vh;
    padding: 0 0.1vw;
}

.tv-time-scan-val { color: #f87171; }
.tv-val-red { color: #ef4444; }
.tv-val-blue { color: #3b82f6; }
.tv-val-cyan { color: #06b6d4; }
.tv-val-amber { color: #f59e0b; }
.tv-val-purple { color: #a855f7; }
.tv-val-orange { color: #f97316; }

/* ── Layer toggles (matched to tv-toggle-bar sizing) ── */
.tv-layers-row {
    display: flex;
    gap: 0.4vw;
    margin-bottom: 0.4vh;
}

.tv-layer {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0.4vh 0.3vw;
    background: #212121;
    border: 2px solid #555;
    border-radius: 0.7rem;
    cursor: pointer;
    font-family: Arial, sans-serif;
    font-size: 1.8vh;
    font-weight: 700;
    color: #888;
    flex: 1;
    min-height: 3.7vh;
    text-align: center;
    transition: all 0.15s;
}

.tv-layer:hover { color: #ccc; }

.tv-layer input[type="checkbox"] { display: none; }

.tv-layer-fuel:has(input:checked) { background: #ef4444; color: #000; border-color: #ef4444; }
.tv-layer-map:has(input:checked) { background: #166534; color: #fff; border-color: #166534; }
.tv-layer-fire:has(input:checked) { background: #fbbf24; color: #000; border-color: #fbbf24; }
.tv-layer-humid:has(input:checked) { background: #3b82f6; color: #fff; border-color: #3b82f6; }
.tv-layer-temp:has(input:checked) { background: #f97316; color: #000; border-color: #f97316; }

.tv-layer-btn {
    flex: 1;
    padding: 0.4vh 0.3vw;
    border: 2px solid #666;
    border-radius: 0.7rem;
    font-family: Arial, sans-serif;
    font-size: 1.8vh;
    font-weight: 700;
    cursor: pointer;
    text-align: center;
    min-height: 3.7vh;
    background: #555;
    color: #ccc;
    transition: all 0.15s;
}

.tv-layer-btn:hover { background: #666; }
.tv-layer-btn.tv-wind-arrows { background: #06b6d4; color: #fff; border-color: #06b6d4; }
.tv-layer-btn.tv-wind-flow { background: #1e40af; color: #fff; border-color: #1e40af; }

/* ── Export button ── */
.tv-export-btn {
    width: 100%;
    padding: 0.8vh 0.5vw;
    background: #3b82f6;
    color: #fff;
    border: none;
    border-radius: 0.4rem;
    font-family: Arial, sans-serif;
    font-size: 1.8vh;
    font-weight: 600;
    cursor: pointer;
    min-height: 3.7vh;
}

.tv-export-btn:hover { background: #2563eb; }

/* ── Historical weather selectors ── */
.tv-hist-options {
    margin-top: 0.4vh;
    display: flex;
    flex-direction: column;
    gap: 0.2vh;
}

.tv-select {
    width: 100%;
    background: #1a1a1a;
    border: 1px solid #444;
    color: #e0e0e0;
    font-family: Arial, sans-serif;
    font-size: 1.5vh;
    padding: 0.4vh 0.3vw;
    border-radius: 0.3rem;
    cursor: pointer;
}

.tv-select:disabled {
    color: #666;
    cursor: not-allowed;
}

.tv-select-sm {
    width: auto;
    min-width: 6vw;
}

.tv-hist-shp {
    margin-top: 0.6vh;
    display: flex;
    flex-direction: column;
    gap: 0.3vh;
}

.tv-label-amber {
    color: #fbbf24;
    font-weight: 600;
    font-size: 1.5vh;
}

.tv-shp-btn {
    text-align: center;
    cursor: pointer;
}
.tv-btn-active {
    background: #00cccc !important;
    color: #000 !important;
    font-weight: bold;
}

.tv-shp-clear {
    padding: 0.2vh 0.6vw;
    background: #444;
    border: 1px solid #555;
    color: #e0e0e0;
    font-family: Arial, sans-serif;
    font-size: 1.4vh;
    border-radius: 0.3rem;
    cursor: pointer;
}

.tv-shp-clear:disabled {
    color: #666;
    cursor: not-allowed;
}

/* ---- MapLibre overrides ---- */
.maplibregl-ctrl-top-right {
    top: calc(7.4 * var(--Vd));
}

.maplibregl-ctrl-bottom-right {
    bottom: 9vh;
}

.maplibregl-ctrl-attrib,
.maplibregl-ctrl-attrib.maplibregl-compact {
    background: rgba(0, 0, 0, 0.55) !important;
    color: #ccc !important;
    border-radius: 4px !important;
}

.maplibregl-ctrl-attrib a {
    color: #aaa !important;
}

.maplibregl-ctrl-attrib-button {
    background-color: rgba(0, 0, 0, 0.55) !important;
    filter: invert(1) opacity(0.6);
}



/* ============================================================
   COMPASS PRO (bearing + pitch indicator)
   ============================================================ */

.maplibregl-ctrl-compass-pro {
    position: absolute;
    top: 11vh;
    left: 13vh;
    z-index: 100;
    width: 17vh;
    height: 17vh;
    pointer-events: auto;
    margin: 0;
    padding: 0;
}

.maplibregl-ctrl-compass-pro-container {
    width: 100%;
    height: 100%;
    position: relative;
    display: flex;
    justify-content: center;
    align-items: center;
}

.compass-bg {
    width: 100%;
    height: 100%;
    position: absolute;
    border-radius: 50%;
    background-color: #000000;
    border: calc(0.3 * var(--Vd)) solid #333333;
    box-shadow: 0 0 calc(0.9 * var(--Vd)) rgba(0, 0, 0, 0.7);
}

.compass-rose {
    width: 100%;
    height: 100%;
    position: absolute;
    border-radius: 50%;
    transform-origin: center;
}

.compass-direction {
    position: absolute;
    font-family: Arial, sans-serif;
    font-weight: bold;
    font-size: 3vh;
    transform-origin: center;
}

.compass-direction-n {
    top: calc(0.2 * var(--Vd));
    left: 50%;
    transform: translateX(-50%);
    color: #FF0000;
}

.compass-direction-e {
    top: 50%;
    right: calc(0.2 * var(--Vd));
    transform: translateY(-50%);
    color: #FFFF00;
}

.compass-direction-s {
    bottom: calc(0.2 * var(--Vd));
    left: 50%;
    transform: translateX(-50%);
    color: #FFFF00;
}

.compass-direction-w {
    top: 50%;
    left: calc(0.2 * var(--Vd));
    transform: translateY(-50%);
    color: #FFFF00;
}

.compass-marker {
    position: absolute;
    width: calc(0.2 * var(--Vd));
    height: 30%;
    background-color: #FF0000;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
}

.compass-bearing-display {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    color: #FFFF00;
    font-family: Arial, sans-serif;
    font-weight: bold;
    font-size: 3vh;
    text-align: center;
    text-shadow: 1px 1px 2px #000000;
    width: 80%;
}

.compass-pitch-indicator {
    position: absolute;
    width: 100%;
    height: 100%;
    background: linear-gradient(to bottom, rgba(255,255,0,0.4) 0%, rgba(0,0,0,0) 50%);
    border-radius: 50%;
    transform-origin: center;
    z-index: 1;
    opacity: 0;
}

/* ============================================================
   WIND COMPASS (mini + popup)
   ============================================================ */

.maplibregl-ctrl-wind-compass {
    position: absolute;
    top: 11vh;
    right: 13vh;
    z-index: 100;
    width: 17vh;
    height: 17vh;
    pointer-events: auto;
    margin: 0;
    padding: 0;
}

.wind-compass-label {
    position: absolute;
    bottom: calc(-2.8 * var(--Vd));
    left: 0;
    width: 100%;
    text-align: center;
    color: white;
    font-family: Arial, sans-serif;
    font-weight: bold;
    font-size: calc(1.9 * var(--Vd));
    text-shadow: 1px 1px 2px #000000;
}

.maplibregl-ctrl-wind-compass-container {
    width: 100%;
    height: 100%;
    position: relative;
    display: flex;
    justify-content: center;
    align-items: center;
}

.wind-compass-bg {
    width: 100%;
    height: 100%;
    position: absolute;
    border-radius: 50%;
    background-color: #0066FF;
    border: calc(0.3 * var(--Vd)) solid #333333;
    box-shadow: 0 0 calc(0.9 * var(--Vd)) rgba(0, 0, 0, 0.7);
}

.wind-compass-rose {
    width: 100%;
    height: 100%;
    position: absolute;
    border-radius: 50%;
    transform-origin: center;
}

.wind-compass-direction {
    position: absolute;
    font-family: Arial, sans-serif;
    font-weight: bold;
    font-size: 3vh;
    transform-origin: center;
}

.wind-compass-direction-n {
    top: calc(0.2 * var(--Vd));
    left: 50%;
    transform: translateX(-50%);
    color: #FF0000;
}

.wind-compass-direction-e {
    top: 50%;
    right: calc(0.2 * var(--Vd));
    transform: translateY(-50%);
    color: #FFFFFF;
}

.wind-compass-direction-s {
    bottom: calc(0.2 * var(--Vd));
    left: 50%;
    transform: translateX(-50%);
    color: #FFFFFF;
}

.wind-compass-direction-w {
    top: 50%;
    left: calc(0.2 * var(--Vd));
    transform: translateY(-50%);
    color: #FFFFFF;
}

.wind-compass-marker {
    position: absolute;
    width: calc(0.4 * var(--Vd));
    height: 47%;
    background-color: #FF0000;
    top: 50%;
    left: 50%;
    transform-origin: center bottom;
    transform: translateY(-100%);
    z-index: 10;
}

.wind-compass-center {
    position: absolute;
    width: calc(4.1 * var(--Vd));
    height: calc(4.1 * var(--Vd));
    border-radius: 50%;
    background-color: #0066FF;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 11;
    border: none;
}

.wind-compass-bearing-display {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    color: #FFFFFF;
    font-family: Arial, sans-serif;
    font-weight: bold;
    font-size: 3vh;
    text-align: center;
    text-shadow: 1px 1px 2px #000000;
    width: 80%;
    z-index: 12;
}

/* Wind Direction Popup Overlay */
.wind-direction-overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.5);
    z-index: 200;
    display: none;
}

/* Wind Compass Popup */
.wind-compass-popup {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: calc(27.8 * var(--Vd));
    height: calc(27.8 * var(--Vd));
    z-index: 201;
    display: none;
}

.wind-compass-popup-container {
    width: 100%;
    height: 100%;
    position: relative;
    display: flex;
    justify-content: center;
    align-items: center;
}

.wind-compass-popup-bg {
    width: 100%;
    height: 100%;
    position: absolute;
    border-radius: 50%;
    background-color: #0066FF;
    border: calc(0.3 * var(--Vd)) solid #333333;
    box-shadow: 0 0 calc(1.4 * var(--Vd)) rgba(0, 0, 0, 0.7);
}

.wind-compass-popup-rose {
    width: 100%;
    height: 100%;
    position: absolute;
    border-radius: 50%;
    transform-origin: center;
}

.wind-compass-popup-direction {
    position: absolute;
    font-family: Arial, sans-serif;
    font-weight: bold;
    font-size: calc(3.3 * var(--Vd));
    transform-origin: center;
}

.wind-compass-popup-direction-n {
    top: calc(0.9 * var(--Vd));
    left: 50%;
    transform: translateX(-50%);
    color: #FF0000;
}

.wind-compass-popup-direction-e {
    top: 50%;
    right: calc(0.9 * var(--Vd));
    transform: translateY(-50%);
    color: #FFFFFF;
}

.wind-compass-popup-direction-s {
    bottom: calc(0.9 * var(--Vd));
    left: 50%;
    transform: translateX(-50%);
    color: #FFFFFF;
}

.wind-compass-popup-direction-w {
    top: 50%;
    left: calc(0.9 * var(--Vd));
    transform: translateY(-50%);
    color: #FFFFFF;
}

.wind-compass-popup-marker {
    position: absolute;
    width: calc(0.4 * var(--Vd));
    height: 50%;
    background-color: #FF0000;
    top: 50%;
    left: 50%;
    transform-origin: center bottom;
    transform: translateY(-100%);
    z-index: 10;
}

.wind-compass-popup-center {
    position: absolute;
    width: calc(7.4 * var(--Vd));
    height: calc(7.4 * var(--Vd));
    border-radius: 50%;
    background-color: #0066FF;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 11;
    border: none;
}

.wind-compass-popup-bearing-display {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    color: #FFFFFF;
    font-family: Arial, sans-serif;
    font-weight: bold;
    font-size: calc(3.3 * var(--Vd));
    text-align: center;
    text-shadow: 1px 1px 2px #000000;
    width: 80%;
    z-index: 12;
}

.wind-compass-popup-label {
    position: absolute;
    bottom: calc(-3.7 * var(--Vd));
    left: 0;
    width: 100%;
    text-align: center;
    color: white;
    font-family: Arial, sans-serif;
    font-weight: bold;
    font-size: calc(2.2 * var(--Vd));
    text-shadow: 1px 1px 2px #000000;
    background-color: rgba(0, 0, 0, 0.6);
    padding: calc(0.5 * var(--Vd));
    border-radius: calc(0.9 * var(--Vd));
}

.wind-compass-ok-button {
    position: absolute;
    top: calc(-1.9 * var(--Vd));
    right: calc(-1.9 * var(--Vd));
    width: calc(6.5 * var(--Vd));
    height: calc(6.5 * var(--Vd));
    border-radius: 50%;
    border: none;
    display: flex;
    justify-content: center;
    align-items: center;
    cursor: pointer;
    font-size: calc(2.2 * var(--Vd));
    font-weight: bold;
    color: white;
    background-color: #00CC00;
    box-shadow: 0 calc(0.5 * var(--Vd)) calc(0.9 * var(--Vd)) rgba(0, 0, 0, 0.5);
    z-index: 202;
}
.wind-compass-cancel-button {
    position: absolute;
    top: calc(-1.9 * var(--Vd));
    left: calc(-1.9 * var(--Vd));
    width: calc(6.5 * var(--Vd));
    height: calc(6.5 * var(--Vd));
    border-radius: 50%;
    border: none;
    display: flex;
    justify-content: center;
    align-items: center;
    cursor: pointer;
    font-size: calc(3 * var(--Vd));
    font-weight: bold;
    color: white;
    background-color: #cc0000;
    box-shadow: 0 calc(0.5 * var(--Vd)) calc(0.9 * var(--Vd)) rgba(0, 0, 0, 0.5);
    z-index: 202;
}

/* ============================================================
   Fire-Propagation Transparency Dial (Action button E)

   Pattern lifted from .wind-compass-popup-* but the dial is a
   linear 0..100 value across an upper semicircle:
     pointer at -90deg = 0%   (left)
     pointer at   0deg = 50%  (top)
     pointer at +90deg = 100% (right)
   The lower 90deg fan is the value-display "wedge" (fixed,
   centred on 6 o'clock); tickmarks sit on the dial perimeter
   every 10%. CSS variables: --Vd is the global vh-derived unit
   already used by every other popup in the app.
   ============================================================ */
.transparency-overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.5);
    z-index: 200;
    display: none;
}

.transparency-popup {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: calc(27.8 * var(--Vd));
    height: calc(27.8 * var(--Vd));
    z-index: 201;
    display: none;
}

.transparency-popup-container {
    width: 100%;
    height: 100%;
    position: relative;
    display: flex;
    justify-content: center;
    align-items: center;
}

.transparency-popup-bg {
    width: 100%;
    height: 100%;
    position: absolute;
    border-radius: 50%;
    /* background-color is set inline from app.rs — alpha mirrors the
       dial value (100% transparency = fully see-through, 0% = fully
       black) so the dial face matches the fire-fill behaviour. */
    border: calc(0.4 * var(--Vd)) solid #cc0000;
    box-shadow: 0 0 calc(1.4 * var(--Vd)) rgba(0, 0, 0, 0.7);
}

/* Pointer rotates from -135deg (0%) through 0deg (50%) to +135deg (100%).
   Anchored at the dial centre, points UP at 50%. Length is the dial's
   inner radius minus the rim border so the tip just touches the red
   inner edge. Container is 27.8 * Vd → radius = 13.9 * Vd; border is
   0.4 * Vd → inner radius ≈ 13.5 * Vd. */
.transparency-popup-pointer {
    position: absolute;
    width: calc(1.0 * var(--Vd));
    height: calc(13.5 * var(--Vd));
    background-color: #ffffff;
    border-radius: calc(0.5 * var(--Vd));
    top: 50%;
    left: 50%;
    transform-origin: center bottom;
    transform: translate(-50%, -100%);
    z-index: 10;
    box-shadow: 0 0 calc(0.5 * var(--Vd)) rgba(0, 0, 0, 0.6);
    pointer-events: none;
}

/* Bottom 90deg fan — fixed, centred on 6 o'clock. Drawn with a
   conic-gradient mask so we get a clean wedge without an SVG. */
.transparency-popup-wedge {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 100%;
    height: 100%;
    transform: translate(-50%, -50%);
    background: conic-gradient(
        from 135deg,
        #1565ff 0deg 90deg,
        transparent 90deg 360deg
    );
    border-radius: 50%;
    z-index: 6;
    pointer-events: none;
}

.transparency-popup-value {
    position: absolute;
    bottom: calc(2.5 * var(--Vd));
    left: 50%;
    transform: translateX(-50%);
    color: #ffffff;
    font-family: Arial, sans-serif;
    font-weight: bold;
    font-size: calc(2.6 * var(--Vd));
    text-shadow: 1px 1px 2px #000000;
    z-index: 12;
    pointer-events: none;
}

.transparency-popup-label {
    position: absolute;
    bottom: calc(-3.7 * var(--Vd));
    left: 0;
    width: 100%;
    text-align: center;
    color: white;
    font-family: Arial, sans-serif;
    font-weight: bold;
    font-size: calc(2.2 * var(--Vd));
    text-shadow: 1px 1px 2px #000000;
    background-color: rgba(0, 0, 0, 0.6);
    padding: calc(0.5 * var(--Vd));
    border-radius: calc(0.9 * var(--Vd));
    letter-spacing: 0.15em;
}

.transparency-popup-ok-button {
    position: absolute;
    top: calc(-1.9 * var(--Vd));
    right: calc(-1.9 * var(--Vd));
    width: calc(6.5 * var(--Vd));
    height: calc(6.5 * var(--Vd));
    border-radius: 50%;
    border: none;
    display: flex;
    justify-content: center;
    align-items: center;
    cursor: pointer;
    font-size: calc(2.2 * var(--Vd));
    font-weight: bold;
    color: white;
    background-color: #00CC00;
    box-shadow: 0 calc(0.5 * var(--Vd)) calc(0.9 * var(--Vd)) rgba(0, 0, 0, 0.5);
    z-index: 202;
}

/* ── Outline-only propagation toggle (hub of the transparency dial) ──
   Green like the share/OK buttons. Sits at the dial centre, ABOVE the pointer
   (which pivots at the same point), and stays interactive while the rest of the
   dial is greyed — otherwise there'd be no way to switch it back off. */
.transparency-popup-hide-button {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: calc(6.5 * var(--Vd));
    height: calc(6.5 * var(--Vd));
    border-radius: 50%;
    border: none;
    display: flex;
    justify-content: center;
    align-items: center;
    cursor: pointer;
    color: white;
    background-color: #00CC00;
    box-shadow: 0 calc(0.5 * var(--Vd)) calc(0.9 * var(--Vd)) rgba(0, 0, 0, 0.5);
    /* Above .transparency-popup-pointer so the needle sweeps underneath. */
    z-index: 203;
}
/* Engaged: brighter green + white ring, so it reads as ON against the greyed dial. */
.transparency-popup-hide-button.transparency-hide-active {
    background-color: #00ff00;
    box-shadow: 0 0 0 calc(0.35 * var(--Vd)) #ffffff,
                0 calc(0.5 * var(--Vd)) calc(0.9 * var(--Vd)) rgba(0, 0, 0, 0.5);
}

/* Dial deactivated while outline-only is on: with no fills left there is nothing
   for the dial to fade. Desaturated + dimmed; pointer-events are killed on the
   dial parts only, never on the hub button above. */
.transparency-popup-container.transparency-disabled .transparency-popup-bg,
.transparency-popup-container.transparency-disabled .transparency-popup-wedge,
.transparency-popup-container.transparency-disabled .transparency-popup-pointer,
.transparency-popup-container.transparency-disabled .transparency-popup-value {
    filter: grayscale(1);
    opacity: 0.35;
    pointer-events: none;
}

/* ── GeoGrid labels (geogrid-maplibre-gl) ── */
.geogrid .geogrid__label { white-space: nowrap; }
.geogrid .geogrid__label--top,
.geogrid .geogrid__label--bottom { transform: translateX(-50%); }
.geogrid .geogrid__label--left  { padding-left: 4px; transform: translateY(-50%); }
.geogrid .geogrid__label--right { padding-right: 4px; transform: translateY(-50%); }

/* ────────────────────────────────────────────────────────────────
   Generating-Report overlay
   Full-screen scrim shown while the Fire Report's map snapshot is
   being prepared (style switch / fitBounds / composite / restore).
   Hides the visible map work from the user.
──────────────────────────────────────────────────────────────── */
.report-overlay {
  position: fixed;
  inset: 0;
  z-index: 99999;
  display: none;              /* toggled to flex by Leptos style:display */
  align-items: center;
  justify-content: center;
  background: rgba(10, 10, 12, 0.85);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
}
.report-overlay-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 28px 40px;
  background: #1a1a1d;
  border: 1.5px solid #c0392b;
  border-radius: 8px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
  min-width: 280px;
  max-width: 420px;
  text-align: center;
}
.report-overlay-spinner {
  width: 36px;
  height: 36px;
  border: 3px solid rgba(192, 57, 43, 0.25);
  border-top-color: #c0392b;
  border-radius: 50%;
  animation: report-spin 0.9s linear infinite;
}
@keyframes report-spin {
  to { transform: rotate(360deg); }
}
.report-overlay-text {
  font: 700 16px/1.2 system-ui, sans-serif;
  color: #f5f5f5;
}
.report-overlay-sub {
  font: 12px/1.4 system-ui, sans-serif;
  color: #aaa;
}
/* Progress bar — mirrors the main loading screen (cyan gradient fill on a
   faint track). Driven by the report_progress signal. */
.report-overlay-bar {
  width: 100%;
  height: 8px;
  margin-top: 4px;
  background: rgba(255, 255, 255, 0.15);
  border-radius: 4px;
  overflow: hidden;
}
.report-overlay-fill {
  height: 100%;
  width: 0%;
  background: linear-gradient(90deg, #4fc3f7, #00e5ff);
  border-radius: 4px;
  transition: width 0.3s ease;
}
.report-overlay-pct {
  font: 12px/1.4 system-ui, sans-serif;
  color: #aaa;
}

/* ────────────────────────────────────────────────────────────────
   Fire-weather button + active-mode label
   Wraps the FW action button so a small label can sit below it
   indicating the active indicator (FFWI/EFWI/IPIF) without
   disturbing the surrounding action-button row.
──────────────────────────────────────────────────────────────── */
.fw-btn-wrap {
  position: relative;
  display: inline-block;
}
.fw-mode-label {
  position: absolute;
  top: calc(100% + 4px);
  left: 50%;
  transform: translateX(-50%);
  background: rgba(0, 0, 0, 0.75);
  color: #fff;
  font: 700 1.1vh/1 system-ui, sans-serif;
  letter-spacing: 0.2px;
  padding: 3px 7px;
  border-radius: 6px;
  white-space: nowrap;
  pointer-events: none;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
  align-items: center;
  justify-content: center;
}
/* Detection-age legend under the satellite-detections caption pill: five
   chips labelled with the upper bound of each time-since-detection band
   (matches _ageColorExpr in map-bridge.js). */
.hs-detect-label {
  flex-direction: column;
  gap: 3px;
}
.hs-age-legend {
  display: flex;
  gap: 2px;
}
.hs-age-chip {
  padding: 1px 4px;
  border-radius: 3px;
  font: 700 0.95vh/1.4 system-ui, sans-serif;
  color: #000;
  border: 1px solid rgba(255, 255, 255, 0.35);
}
.hs-age-dark {
  color: #fff;
}

/* (Removed 2026-05-29) Former IPIF placeholder muted style — IPIF is now
   wired to the AEMET fire-risk NPZ overlay; label is no longer a placeholder. */

/* ── Vectorial map-drag cursors ─────────────────────────────────────────────
   Windows has no native grab/grabbing hand, so Chromium (WebView2) falls back
   to its own LOW-RES bitmap cursors for CSS `grab`/`grabbing` — pixelated on
   any high-DPI display, jarring next to the crisp native `pointer` hand. Ship
   inline-SVG hands (white fill, black outline — legible on light and dark
   basemaps) with the stock keywords as fallback. The explicit width/height on
   the SVG is REQUIRED: Chromium ignores cursor SVGs without intrinsic size. */
.maplibregl-canvas-container.maplibregl-interactive {
  cursor: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'><path d='M8.5 12V5.7a1.4 1.4 0 0 1 2.8 0V11 4.6a1.4 1.4 0 0 1 2.8 0V11 5.7a1.4 1.4 0 0 1 2.8 0V12 7.6a1.35 1.35 0 0 1 2.7 0v7.1c0 4-2.6 6.6-6.4 6.6-3 0-4.6-1.3-6-3.6L4.7 13.7c-.6-1-.5-2 .3-2.6.8-.6 1.9-.4 2.6.6l.9 1.3z' fill='white' stroke='black' stroke-width='1.3' stroke-linejoin='round'/></svg>") 12 12, grab;
}
.maplibregl-canvas-container.maplibregl-interactive:active {
  cursor: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'><path d='M7.2 12.7V11a1.35 1.35 0 0 1 2.7 0v-.9a1.35 1.35 0 0 1 2.7 0V10a1.35 1.35 0 0 1 2.7 0v.4a1.3 1.3 0 0 1 2.6 0v4.3c0 3.9-2.5 6.4-6.2 6.4-2.9 0-4.4-1.2-5.7-3.4l-1.6-2.8c-.6-1-.4-2 .4-2.5.8-.5 1.8-.3 2.4.7z' fill='white' stroke='black' stroke-width='1.3' stroke-linejoin='round'/></svg>") 12 12, grabbing;
}

/* Operators on weak hardware (or who ask the OS for it) shed every infinite
   pulse in one stroke. Functional states stay readable — the static lit
   faces above carry the meaning; only the breathing stops. */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
    }
}

/* ══════════════════════════════════════════════════════════════════
   HELP MODE
   The veil sits above every other z-index in the app and is
   pointer-events:none on purpose: the catcher in help_mode.rs reads
   event.target to find the control under the cursor, which only works
   if the veil is invisible to hit testing. Blocking the rest of the
   GUI is the catcher's job, not the veil's.
   ══════════════════════════════════════════════════════════════════ */
/* A DISABLED control must still be able to explain itself.
   .action-disabled carries `pointer-events: none !important`, so without this
   the browser hit tests straight past a greyed-out button to the map, the
   catcher's closest("[data-help]") finds nothing, and the tool silently has
   no help card. Re-enabling hit testing here is safe: the capture-phase
   catcher in help_mode.rs still swallows the event, so no handler can run. */
body.help-active [data-help] {
    pointer-events: auto !important;
}

.help-veil {
    position: fixed;
    inset: 0;
    z-index: 9000;
    pointer-events: none;
}

.help-veil svg {
    display: block;
    width: 100%;
    height: 100%;
}

.help-toggle-btn {
    position: absolute;
    bottom: 9vh;
    /* Sits just right of the centred license bar. The bar is nowrap and
       centred on 50%, so anchoring the button to a fixed offset from the
       centre keeps the two together at any license-name length. */
    left: 50%;
    margin-left: var(--help-license-offset, 19vw);
    width: 3.4vh;
    height: 3.4vh;
    border-radius: 50%;
    border: 0.25vh solid #000;
    background: #ffdd00;
    color: #000;
    font-family: Arial, sans-serif;
    font-weight: bold;
    font-size: 2.2vh;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    z-index: 9100;
    padding: 0;
    box-shadow: 0 0 0.6vh rgba(0, 0, 0, 0.6);
    -webkit-user-select: none;
    user-select: none;
    touch-action: manipulation;
}

.help-toggle-btn.help-on {
    background: #00FFFF;
}

.help-box {
    position: fixed;
    z-index: 9100;
    background: #000;
    color: #fff;
    border-radius: 0.8vh;
    padding: 2vh 2.4vh;
    font-family: Arial, sans-serif;
    box-shadow: 0 0 2vh rgba(0, 0, 0, 0.55);
    /* Deliberately NOT pointer-events:none. The card must be scrollable: its
       instructions can exceed the height cap on a short screen, and an inert
       card cannot be scrolled. The catcher in help_mode.rs lets wheel and
       touch-drag through over the card and swallows everything else, so this
       cannot reach any control underneath. */
    pointer-events: auto;
    /* max-height is set inline by place_box, sized to the space actually
       available for the chosen side, so the card can never cross a margin. */
    /* overflow-y:auto ALONE makes the browser compute overflow-x to auto too
       (CSS overflow spec: a non-visible value on one axis forces the visible
       other axis to auto). That paints a phantom horizontal scrollbar on any
       sub-pixel width overflow, on every card. Pin overflow-x:hidden so only
       the vertical axis can ever scroll, and only when a card is genuinely
       taller than the screen; text wraps, so nothing is clipped. */
    overflow-x: hidden;
    overflow-y: auto;
    overflow-wrap: break-word;
    overscroll-behavior: contain;
}

.help-box-name {
    color: #00FFFF;
    font-size: 2.6vh;
    font-weight: bold;
    line-height: 1.15;
    margin-bottom: 1.2vh;
}

.help-box-what {
    font-size: 2vh;
    font-weight: bold;
    line-height: 1.35;
}

.help-box-act {
    font-size: 2vh;
    font-weight: bold;
    line-height: 1.35;
    margin-top: 1.4vh;
}
/* First action line carries the divider from the description above. */
.help-box-act:first-of-type {
    padding-top: 1.2vh;
    border-top: 0.15vh solid #333;
}
/* Yellow = the action the operator performs (Press, Long-Press, ...). White =
   what happens when they do it. The label reads as a lead-in to the sentence. */
.help-box-verb {
    color: #ffdd00;
}
.help-box-verb::after {
    content: " ";
}
.help-box-exp {
    color: #fff;
}

/* Tail. A rotated square tucked half outside the box, so it inherits the
   black background and needs no separate colour. --help-tail is written by
   place_box for every side and always points back at the hole's centre; the
   card may have slid along the screen edge to fit whole, so the tail is not
   necessarily in the middle of its edge. */
.help-box::after {
    content: "";
    position: absolute;
    width: 1.8vh;
    height: 1.8vh;
    background: #000;
    transform: rotate(45deg);
}

.help-box--right::after {
    left: -0.9vh;
    top: var(--help-tail, 50%);
    margin-top: -0.9vh;
}

.help-box--left::after {
    right: -0.9vh;
    top: var(--help-tail, 50%);
    margin-top: -0.9vh;
}

.help-box--below::after {
    top: -0.9vh;
    left: var(--help-tail, 50%);
    margin-left: -0.9vh;
}

.help-box--above::after {
    bottom: -0.9vh;
    left: var(--help-tail, 50%);
    margin-left: -0.9vh;
}
