/* Lock the page to the viewport — every scrollable region (sidebar, main,
   side panels) handles its own overflow internally, so the outer document
   never shows a scrollbar. Default body margin would otherwise push 100vh
   layouts past the viewport edge. */
html, body { margin: 0; padding: 0; height: 100%; overflow: hidden; }

h1:focus {
    outline: none;
}

/* Clicking a KMZ or flight line focuses its SVG <path>, and a browser draws the
   focus ring around a path's BOUNDING BOX rather than around the stroke — so
   clicking a survey line that runs twenty miles north puts a black rectangle
   across half the map. It reads as a selection artifact, and it is: the line
   answers a click with the info card on the map pages, and the open line is
   redrawn heavier (see selectImportedFeature in leaflet-interop.js), so the ring
   carries nothing the page isn't already showing. The map CONTAINER's own focus
   ring is untouched — that one marks real keyboard navigation.

   Deliberately broad (the <svg> and anything inside it) rather than `path` alone:
   which element ends up focused differs by browser, and every one of them is a
   Leaflet-drawn vector carrying no tabindex, so none is keyboard-reachable and
   none loses a real focus indicator here. */
.leaflet-container svg:focus,
.leaflet-container svg *:focus {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid #e50000;
}

.validation-message {
    color: #e50000;
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}

/* Live Map — helicopter marker + system-name label */
.heli-icon {
    background: transparent;
    border: none;
    filter: drop-shadow(0 2px 2px rgba(0, 0, 0, 0.35));
}
/* Inner wrapper the interop rotates to the GPS heading — Leaflet owns the
   icon root's transform for positioning, so rotation can't live there. */
.heli-icon .veh-rot {
    width: 100%;
    height: 100%;
    transition: transform 0.5s ease;
    will-change: transform;
}
.heli-icon .veh-rot svg { display: block; }
.leaflet-tooltip.heli-label {
    background: rgba(15, 23, 42, 0.85);
    color: #f8fafc;
    border: none;
    border-radius: 6px;
    padding: 2px 8px;
    font-weight: 600;
    font-size: 0.8rem;
    letter-spacing: 0.02em;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
}
.leaflet-tooltip.heli-label.heli-label--online {
    background: #bbf7d0;
    color: #0f172a;
}
.leaflet-tooltip.heli-label.heli-label--offline {
    background: #fef08a;
    color: #0f172a;
}
.leaflet-tooltip.heli-label::before { display: none; }

/* Live Map — marker popup: Leaflet forces white on .leaflet-popup-content-wrapper and .leaflet-popup-tip */
.leaflet-popup:has(.heli-popup--online) .leaflet-popup-content-wrapper,
.leaflet-popup:has(.heli-popup--online) .leaflet-popup-tip {
    background: #bbf7d0 !important;
}
.leaflet-popup:has(.heli-popup--offline) .leaflet-popup-content-wrapper,
.leaflet-popup:has(.heli-popup--offline) .leaflet-popup-tip {
    background: #fef08a !important;
}
.leaflet-popup-content:has(.heli-popup-inner) {
    margin: 0 !important;
}

/* Per-feed status banner — shown on each system subpage (DAS, POS, Power, Cameras, Drives). */
.feed-status-banner {
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: .55rem .9rem;
    border-radius: 8px;
    margin: 0 0 .85rem 0;
    font-size: .9rem;
    line-height: 1.3;
}
.feed-status-banner.is-online {
    background: #ecfdf5;
    color: #065f46;
    border: 1px solid #a7f3d0;
}
.feed-status-banner.is-offline {
    background: #fef3c7;
    color: #78350f;
    border: 1px solid #fcd34d;
    box-shadow: 0 1px 2px rgba(0, 0, 0, .04);
}
.feed-status-banner.is-waiting {
    background: #f1f5f9;
    color: #475569;
    border: 1px solid #cbd5e1;
}
.feed-status-banner .feed-status-dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    flex: 0 0 auto;
    box-shadow: 0 0 0 2px rgba(255, 255, 255, .65);
}
.feed-status-banner .feed-status-dot.is-online { background: #10b981; }
.feed-status-banner .feed-status-dot.is-offline { background: #d97706; }
.feed-status-banner .feed-status-dot.is-waiting { background: #94a3b8; }
.feed-status-banner.is-offline .feed-status-dot {
    animation: feed-status-pulse 1.6s ease-in-out infinite;
}
@keyframes feed-status-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.35; }
}
.feed-status-banner .feed-status-text { flex: 1; }

/* Cellular signal icon (SignalBars control) — rising bars + a network-generation
   tag (5G/4G). Global (not CSS-isolated) to match the other shared controls. */
.sigbar { display: inline-flex; align-items: center; gap: .28rem; vertical-align: middle; line-height: 1; }
.sigbar-track { display: inline-flex; align-items: flex-end; gap: 2px; height: 14px; }
.sigbar.lg .sigbar-track { height: 30px; gap: 3px; }
.sigbar-bar { width: 5px; background: #e2e8f0; border-radius: 1px; }
.sigbar.lg .sigbar-bar { width: 8px; border-radius: 2px; }
.sigbar-bar.lvl1 { height: 35%; }
.sigbar-bar.lvl2 { height: 55%; }
.sigbar-bar.lvl3 { height: 78%; }
.sigbar-bar.lvl4 { height: 100%; }
.sigbar-bar.on { background: #22c55e; }
.sigbar-track.weak .sigbar-bar.on { background: #f59e0b; }
.sigbar-track.bad .sigbar-bar.on { background: #ef4444; }
.sigbar-net { font-size: .62rem; font-weight: 700; letter-spacing: .02em; color: #475569; }
.sigbar.lg .sigbar-net { font-size: .8rem; }
/* Ortho coverage mask — the raster of ground that was inside at least one frame,
   drawn as a Leaflet image overlay (leafletInterop.addCoverageMask). Nearest-neighbour
   scaling is deliberate: the browser would otherwise smooth the edge of the swath into
   a soft gradient, and the edge is exactly what an operator reads to decide whether the
   ground beside a line was covered. Global rather than page-scoped because the element
   is created by Leaflet inside the map pane, where CSS isolation does not reach. */
.coverage-mask { image-rendering: pixelated; image-rendering: crisp-edges; }
/* Live Map weather box — Radar / Rain / Wind gusts drawn over the basemap from OpenWeather
   (leafletInterop.addWeatherBox). Global rather than page-scoped for the same reason
   .coverage-mask is: Leaflet builds the control inside the map container, where Blazor's
   CSS isolation does not reach. */
/* Sized and shaped against .leaflet-control-layers, not .leaflet-bar, so the cloud icon and
   the basemap-stack icon are the same square: 36px, and 44px once Leaflet decides the device
   is touch and adds .leaflet-touch. The numbers are copied rather than the class reused, so
   the layers control's own rules cannot reach in here. */
.weather-box { background: #fff; border-radius: 5px; box-shadow: 0 1px 5px rgba(0,0,0,.4); }
.weather-box-toggle { position: relative; display: block; width: 36px; height: 36px; border-radius: 5px; background-position: 50% 50%; background-repeat: no-repeat; background-size: 26px 26px; background-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2026%2026'%3E%3Cg%20fill='%23333333'%3E%3Ccircle%20cx='10'%20cy='10.5'%20r='4.6'/%3E%3Ccircle%20cx='16.4'%20cy='12.1'%20r='3.6'/%3E%3Crect%20x='6'%20y='11.4'%20width='13.2'%20height='4.3'%20rx='2.15'/%3E%3C/g%3E%3Cg%20stroke='%23333333'%20stroke-width='1.7'%20stroke-linecap='round'%3E%3Cpath%20d='M9.2%2018.2L8%2020.8'/%3E%3Cpath%20d='M13.1%2018.2L11.9%2020.8'/%3E%3Cpath%20d='M17%2018.2L15.8%2020.8'/%3E%3C/g%3E%3C/svg%3E"); }
.leaflet-touch .weather-box-toggle { width: 44px; height: 44px; }
/* Leaflet swaps the shadow for a border on touch, and only for its own two control
   classes. Without the same swap here the weather square renders 44px beside a 48px
   layers square, which is the whole reason the two icons looked mismatched. */
.leaflet-touch .weather-box { box-shadow: none; border: 2px solid rgba(0,0,0,.2); background-clip: padding-box; }
.weather-box-toggle:hover { background-color: #f4f4f4; }
/* A layer is drawing. Without this the only evidence of what is switched on is the weather
   itself, which is the thing the operator is trying to read. */
.weather-box-toggle.is-active { background-color: #eef2ff; }
/* Something failed. The panel is closed most of the time, so this dot is what stops a dead
   layer from passing as clear skies until somebody happens to hover. */
.weather-box-toggle.has-issue::after { content: ""; position: absolute; top: 5px; right: 5px; width: 7px; height: 7px; border-radius: 50%; background: #dc2626; box-shadow: 0 0 0 2px #fff; }
/* Hover swaps the icon for the panel, exactly as the layers control does. */
.weather-box-body { display: none; width: 15rem; padding: .5rem .6rem .55rem; font-size: .8rem; color: #334155; }
.weather-box.is-expanded .weather-box-toggle { display: none; }
.weather-box.is-expanded .weather-box-body { display: block; }
.weather-box-head { margin-bottom: .35rem; padding-bottom: .3rem; border-bottom: 1px solid #e2e8f0; color: #0f172a; }
.weather-row { display: flex; align-items: flex-start; gap: .45rem; padding: .22rem 0; cursor: pointer; }
.weather-row:has(.weather-row-check:disabled) { cursor: default; opacity: .55; }
.weather-row-check { margin-top: .18rem; flex: 0 0 auto; cursor: pointer; }
.weather-row-text { display: flex; flex-direction: column; min-width: 0; }
.weather-row-name { color: #0f172a; font-weight: 600; }
.weather-row-hint { color: #64748b; font-size: .7rem; line-height: 1.25; }
/* The gust reading, which is a different measurement from the raster above it and reads as
   one only if it is set apart from the rows. */
.weather-gusts { margin: .35rem 0 .1rem; padding: .3rem .4rem; background: #f1f5f9; border-radius: 4px; color: #0f172a; font-size: .74rem; line-height: 1.3; font-variant-numeric: tabular-nums; }
/* The particles sub-toggle sits under the row it qualifies, indented to read as part of it
   without pretending to be a fourth layer. */
.weather-row--sub { margin-left: 1.15rem; padding-top: 0; }
/* Wind is not the chosen layer, so its overlay has nothing to sit on. */
.weather-row--sub.is-unavailable { opacity: .45; cursor: default; }
.weather-row--sub .weather-row-name { font-weight: 500; }
/* How many samples the direction field rests on. Not decoration: the particles interpolate
   just as smoothly over two readings as over twenty. */
.weather-note { margin: .1rem 0 .1rem 1.15rem; color: #64748b; font-size: .68rem; line-height: 1.3; font-variant-numeric: tabular-nums; }
/* The speed ramp the particles are coloured by. */
.wind-legend { margin: .3rem 0 .15rem 1.15rem; }
.wind-legend-bar { height: 6px; border-radius: 3px; border: 1px solid rgba(15,23,42,.15); }
.wind-legend-scale { display: flex; justify-content: space-between; margin-top: .1rem; color: #64748b; font-size: .62rem; font-variant-numeric: tabular-nums; }
/* Wind under the pointer, after a second of stillness. Never a click target itself — it sits
   over the map and would otherwise swallow the clicks meant for whatever is under it. */
.wind-hover-chip { position: absolute; z-index: 1000; pointer-events: none; background: rgba(15,23,42,.92); color: #f8fafc; border-radius: 5px; padding: .3rem .45rem; font-size: .74rem; line-height: 1.25; box-shadow: 0 2px 8px rgba(15,23,42,.35); white-space: nowrap; }
.wind-hover-main { font-weight: 700; font-variant-numeric: tabular-nums; }
.wind-hover-sub { color: #cbd5e1; font-size: .66rem; }
/* Colour separation for the rasters, which OpenWeather ships deliberately subtle. This cannot
   raise a tile's baked-in alpha — that is why wind carries its intensity in the particles
   instead — but it does pull rain apart from the ground it falls on. */
.leaflet-weatherParticles-pane { pointer-events: none; }
.leaflet-weather-pane { filter: saturate(1.7) contrast(1.15); }
/* Only while something is drawn over it, so a map with no weather on looks untouched. */
.leaflet-container.weather-active .leaflet-tile-pane { filter: grayscale(.6) contrast(.92) brightness(1.04); }
.weather-opacity { display: flex; align-items: center; gap: .4rem; margin-top: .45rem; padding-top: .4rem; border-top: 1px solid #e2e8f0; }
.weather-opacity-label { color: #64748b; font-size: .72rem; }
.weather-opacity-range { flex: 1; min-width: 0; cursor: pointer; }
.weather-opacity-value { flex: 0 0 auto; width: 2.4rem; text-align: right; color: #475569; font-size: .72rem; font-variant-numeric: tabular-nums; }
/* Red, and never display:none while it holds text: every way this box can fail ends here,
   because a weather layer drawing nothing is indistinguishable from clear skies. */
.weather-status { margin-top: .4rem; color: #b91c1c; font-size: .72rem; line-height: 1.3; }
.weather-credit { margin-top: .35rem; color: #94a3b8; font-size: .66rem; }

