html {
    transition: opacity 0.1s ease;
}

@keyframes halo-bounce {
    0%, 100% {
        transform: translateY(0);
    }
    50% {
        transform: translateY(-10px);
    }
}

/* The bounce animates the pin's inner SVG, NEVER the marker element itself — maplibre positions
   markers via an inline transform on that element (translate(-50%,-100%) translate(x,y)), and any
   animation transform on it would clobber the position (pin snaps to the map's top-left corner).
   Zone pins are structurally excluded so they can never bounce (issue #223). */
.halo-pin:not(.halo-pin--zone).halo-bounce svg {
    animation: halo-bounce 1s ease-in-out infinite;
}

/* MapLibre map container + marker chrome (ADR 0018) */

.halo-map {
    width: 100%;
    height: 100%;
}

/* /map page (Step 2 — the map moved off Home): the page root owns the viewport-relative
   height (subtracting the shared --halo-shell-chrome token: mobile strip + gutters below md,
   gutters only from md), PageHeader takes its natural height, and the rounded content panel
   below takes the remainder. Keeps the MapLibre container at a definite height with no
   page-level overflow at 390/768/1024/1440.
   Batch L: the chain additionally subtracts a breakpoint-aware footer reserve so the
   global footer sits below the fold-free viewport (wrapped multi-row footer + margin
   below lg, single-row footer + margin at lg+). The root keeps only its bottom gutter
   (pb-4), so the reserve also absorbs the removed 1rem top padding — border-box keeps
   the visible map from growing. */
.map-page {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    height: calc(100vh - var(--halo-shell-chrome) - 7rem);
    height: calc(100dvh - var(--halo-shell-chrome) - 7rem);
    min-height: 320px;
}

@media (min-width: 64rem) {
    .map-page {
        gap: 1rem;
        height: calc(100vh - var(--halo-shell-chrome) - 3.5rem);
        height: calc(100dvh - var(--halo-shell-chrome) - 3.5rem);
    }
}

/* Map wrapper (data-test="zone-map") — now the e.html rounded content panel: radius comes
   from `.panel`, the border/surface/clipping sit here, and the flex remainder under
   PageHeader defines its height. The flex child keeps `.halo-map`'s old percentage
   height from depending on cross-container resolution. */
.home-map {
    position: relative;
    display: flex;
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
    border: 1px solid var(--border);
    background: var(--card);
}

.home-map > .halo-map {
    flex: 1 1 auto;
    height: auto;
    min-width: 0;
}

.halo-pin {
    cursor: pointer;
    transform-origin: bottom center;
}

/* Zone markers (issue #223): a distinct, NON-animated indigo pin identifying each condition zone on
   the Subject map, so it is obvious why the camera fit to subject + zones (especially a tiny zone
   far from the subject). The bounce rule excludes `.halo-pin--zone` structurally
   (`.halo-pin:not(.halo-pin--zone).halo-bounce`), so a zone pin can never bounce even if the class
   were ever applied. `position: relative` + an absolutely-positioned label keeps the SVG's tip at the
   anchor point (maplibre's `anchor: "bottom"`), so the pin points at the zone centroid rather than at
   the label's baseline. */
.halo-pin--zone {
    position: relative;
}

.halo-pin--zone .halo-zone-label {
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translateX(-50%);
    max-width: 140px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 11px;
    line-height: 1.2;
    font-weight: 600;
    color: #4338ca;
    text-shadow: 0 0 2px #fff, 0 0 2px #fff, 0 0 2px #fff;
    pointer-events: none;
}

.halo-cluster {
    min-width: 28px;
    height: 28px;
    border-radius: 50%;
    background: #1a73e8;
    color: #fff;
    font: 600 12px/28px ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    text-align: center;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
    cursor: pointer;
}

.halo-vertex {
    pointer-events: none;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}

.halo-edit-handle {
    border-radius: 50%;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
    cursor: grab;
}

.halo-edit-handle:active {
    cursor: grabbing;
}

.halo-edit-vertex {
    width: 12px;
    height: 12px;
    background: #ffffff;
    border: 2px solid #479108;
}

.halo-edit-midpoint {
    width: 8px;
    height: 8px;
    background: #ffffff;
    border: 2px solid #1a73e8;
}

.halo-edit-overlay {
    position: absolute;
    bottom: 28px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    align-items: center;
    gap: 8px;
    max-width: calc(100% - 24px);
    background: rgba(33, 37, 41, 0.92);
    color: #fff;
    padding: 6px 10px;
    border-radius: 4px;
    font: 500 12px/1.4 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    z-index: 1001;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
    /* The bar must never block the map underneath it (vertex handles sit below the bar band) —
       only its interactive children intercept pointer events. */
    pointer-events: none;
}

.halo-edit-instructions,
.halo-edit-overlay button {
    pointer-events: auto;
}

.halo-edit-instructions {
    cursor: pointer;
    user-select: none;
}

.halo-edit-overlay button {
    font: 600 12px/1 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    border: none;
    border-radius: 3px;
    padding: 5px 10px;
    cursor: pointer;
    white-space: nowrap;
}

.halo-edit-save {
    background: #479108;
    color: #fff;
}

.halo-edit-save:disabled {
    background: #6c757d;
    cursor: not-allowed;
}

.halo-edit-cancel {
    background: #dc3545;
    color: #fff;
}

.halo-edit-overlay-invalid {
    background: rgba(211, 47, 47, 0.92);
}

/* MapLibre popup content rides the dashboard theme; keep the default close affordance visible. */
.maplibre-popup-content a {
    text-decoration: underline;
}

body.theme-switching {
    opacity: 0;
    transition: opacity 0.15s ease;
}

.profileThumbnail {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    object-fit: cover;
}

/* Font Awesome 6 Free font CSS */
.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."
}


.components-loading-container {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1rem;
}

.components-loading-animation {
    position: relative;
    width: 80px;
    height: 80px;
}

.components-loading-animation div {
    position: absolute;
    border: 3px solid #512BD4;
    opacity: 1;
    border-radius: 50%;
    animation: components-loading-animation 1.5s cubic-bezier(0, 0.2, 0.8, 1) infinite;
}

.components-loading-animation div:nth-child(2) {
    animation-delay: -0.5s;
}

@keyframes components-loading-animation {
    0% {
        top: 40px;
        left: 40px;
        width: 0;
        height: 0;
        opacity: 0;
    }

    4.9% {
        top: 40px;
        left: 40px;
        width: 0;
        height: 0;
        opacity: 0;
    }

    5% {
        top: 40px;
        left: 40px;
        width: 0;
        height: 0;
        opacity: 1;
    }

    100% {
        top: 0px;
        left: 0px;
        width: 80px;
        height: 80px;
        opacity: 0;
    }
}

.presence-wrap {
    display: inline-flex;
    align-items: center;
    gap: .45rem;
    margin: .45rem;
}

.presence-dot {
    width: 10px;
    height: 10px;
    border-radius: 999px;
    display: inline-block;
}

.presence-dot.online {
    background: #22c55e; /* green */
    box-shadow: 0 0 0 0 rgba(34, 197, 94, .65);
    animation: pulse 1.5s infinite;
}

.presence-dot.offline {
    background: #f59e0b; /* amber (static) */
}

.presence-dot.degraded {
    background: #f59e0b; /* amber (static) — online but no bracelet enrolled */
    outline: 2px dashed #f59e0b;
    outline-offset: 2px;
}

.presence-dot.awol {
    background: #ef4444; /* red */
    box-shadow: 0 0 0 0 rgba(244, 67, 54, 1);
    animation: pulse-red 1.2s infinite;
}

.presence-dot.lostcontact {
    background: #ef4444; /* red */
    animation: pulse-red 1.2s infinite;
}

/* §7 five-state presence indicator (BLE_PRESENCE_FINAL_PLAN.md §7). */
.presence-dot.green-solid {
    background: #22c55e; /* green — no Potential/Confirmed, contact recency unknown */
}

.presence-dot.green-pulse {
    background: #22c55e; /* green — in range with recent phone contact */
    box-shadow: 0 0 0 0 rgba(34, 197, 94, .65);
    animation: pulse 1.5s infinite;
}

.presence-dot.grey {
    background: #6b7280; /* grey — no recent contact, radio off, or phone silent */
}

.presence-dot.amber {
    background: #f59e0b; /* amber (static) — Potential, none Confirmed */
}

.presence-dot.red {
    background: #ef4444; /* red — Confirmed (Active) */
    animation: pulse-red 1.2s infinite;
}

@keyframes pulse-amber {
    0% {
        box-shadow: 0 0 0 0 rgba(245, 158, 11, .7);
    }
    70% {
        box-shadow: 0 0 0 8px rgba(245, 158, 11, 0);
    }
    100% {
        box-shadow: 0 0 0 0 rgba(245, 158, 11, 0);
    }
}

@keyframes pulse {
    0% {
        box-shadow: 0 0 0 0 rgba(34, 197, 94, .65);
    }
    70% {
        box-shadow: 0 0 0 8px rgba(34, 197, 94, 0);
    }
    100% {
        box-shadow: 0 0 0 0 rgba(34, 197, 94, 0);
    }
}

.zone-badge-presence {
    box-shadow: 0 0 0 0 rgba(34, 197, 94, .65);
    animation: pulse 1.5s infinite;
}

.zone-badge-breach {
    box-shadow: 0 0 0 0 rgba(244, 67, 54, 1);
    animation: pulse-red 1.5s infinite;
}

.zone-badge-potential {
    box-shadow: 0 0 0 0 rgba(245, 158, 11, .8);
    animation: pulse-amber 1.2s infinite;
}

@keyframes pulse-red {
    0% {
        box-shadow: 0 0 0 0 rgba(244, 67, 54, 1);
    }
    70% {
        box-shadow: 0 0 0 8px rgba(244, 67, 54, 0);
    }
    100% {
        box-shadow: 0 0 0 0 rgba(244, 67, 54, 0);
    }
}

@media (prefers-reduced-motion: reduce) {
    .presence-dot.online, .presence-dot.awol, .presence-dot.lostcontact,
    .presence-dot.green-pulse, .presence-dot.red, .zone-badge-breach {
        animation: none;
    }
}

.cell-link {
    display: block;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    color: inherit;
    text-decoration: none;
    cursor: pointer;
}


.device-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem;
    margin-top: 0.2rem;
    font-size: 0.8rem;
    color: var(--muted-foreground);
}

.device-meta-item {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    white-space: nowrap;
}

/* Midline-dot (U+00B7) separators between metadata segments. */
.device-meta-item + .device-meta-item::before {
    content: "·";
    margin-right: 0.4rem;
}

.device-meta-icon {
    font-size: 1rem;
}

.device-tab-content {
    position: relative;
    display: flex;
    flex-direction: row;
    flex: 1 1 auto;
    min-height: 0;
}

.device-map-region {
    position: relative;
    flex: 1 1 auto;
    min-width: 0;
    min-height: 0;
}

/* The map stays MOUNTED across tab switches — hidden with display:none on non-map tabs and
   re-shown on the Map tab (the page triggers halo.mapLibre.resize when it becomes
   visible again). */
.device-map-region .device-map {
    position: absolute;
    inset: 0;
    display: none;
}

.device-map-region .device-map.is-map-tab {
    display: block;
}

.device-map-region .device-panel-scroll {
    position: absolute;
    inset: 0;
    overflow-y: auto;
}

/* Desktop filter panel: an in-flow column on the LEFT of the map — expanding it narrows the map
   rather than overlaying it. Collapses to a thin vertical strip (just the expand button). */
.device-map-controls {
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    border-right: 1px solid var(--border);
    background: var(--card);
    z-index: 5;
    overflow: hidden;
}

.device-map-controls.is-expanded {
    width: 20rem;
}

.device-map-controls.is-collapsed {
    width: 2.5rem;
}

.device-map-controls.is-collapsed .device-map-controls-header {
    flex-direction: column;
    justify-content: center;
    padding: 0.25rem;
    height: 100%;
    border-bottom: none;
}

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

/* The expanded panel's scrollable content: date filters first, then the "View Options" section. */
.device-map-controls-body {
    overflow-y: auto;
    flex: 1 1 auto;
    min-height: 0;
}

.device-map-controls-view-header {
    padding: 0.5rem 1rem;
    border-top: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
    background: rgba(0, 0, 0, 0.03);
}

/* Collapsed strip: a full-height clickable button showing the expand chevron + "Filter" rotated
   -90 degrees, both stacked at the TOP of the strip. */
.device-map-controls-collapsed-toggle {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    gap: 0.5rem;
    width: 100%;
    height: 100%;
    border: none;
    background: transparent;
    padding: 0.5rem 0.2rem;
    cursor: pointer;
    color: var(--muted-foreground);
}

.device-map-controls-collapsed-label {
    writing-mode: vertical-rl;
    transform: rotate(180deg);
    font-size: 0.85rem;
    font-weight: 600;
    letter-spacing: 0.15em;
    text-transform: uppercase;
}

.device-map-controls-collapsed-chevron {
    display: flex;
    align-items: center;
    justify-content: center;
}

/* Mobile/tablet: the map-controls drawer toggle floats bottom-right (the drawer opens right). */
.device-map-controls-toggle {
    position: fixed;
    bottom: 1.5rem;
    right: 1rem;
    z-index: 50;
}

/* Hand-rolled right-side drawer (plain CSS panel: 320px, scrim-dismissable, non-modal). */
.device-map-controls-scrim {
    position: fixed;
    inset: 0;
    z-index: 60;
    background: rgba(0, 0, 0, 0.35);
}

.device-map-controls-drawer {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    z-index: 61;
    width: 320px;
    max-width: 85vw;
    overflow-y: auto;
    background: var(--card);
    border-left: 1px solid var(--border);
    box-shadow: -4px 0 12px rgba(0, 0, 0, 0.15);
}

/* Subject page shell — normal-flow flex column so PANEL tabs grow the page and
   the footer follows content (short pages: .halo-footer-slot margin-top:auto
   floats the footer to the viewport bottom with zero page scroll).
   Migration note: BbSidebarInset provides no definite height context (RadzenBody did), so
   height:100% alone collapses to auto — harmless here. There is deliberately NO
   viewport-relative min-height on the base rule: forcing even short panel content
   to fill the viewport pushed the footer below the fold (the original bug). The
   map tab's fixed viewport height lives in the :has(...) rule below. */
.subject-page {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 320px;
}

.subject-header {
    flex-shrink: 0;
}

.subject-tab-content {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

.subject-tab-content.is-map-tab {
    overflow: hidden;
    /* The subject page root has no p-4 wrapper (the header carries its own p-4), so
       this is the map tab's page-gutter contribution to the global footer's 1rem gap. */
    padding-bottom: 1rem;
}

.subject-tab-content.is-panel-tab {
    overflow-y: auto;
    /* No container L/R inset: every inner tab wraps its content in another BbCard
       p-4, so container padding doubled the gutter vs list pages. Top stays 0 (the
       header carries its own pb-4); bottom keeps 1rem for the footer gap. */
    padding: 0 0 1rem 0;
    /* Layout containment: nested grid scroll containers (BbDataGrid's inner
       .overflow-auto wrapper + sticky thead) leak scrollable overflow past the
       tab to the page at narrow viewports (~100-160px of page scroll at 390px
       with the footer already visible). Containing the tab's interior layout
       keeps the page fold-free; with no height bound the tab does not scroll —
       the page is the scroller by design. No paint/size containment, so
       sticky/flex sizing are untouched and there is no fixed-position content
       in panel tabs to re-anchor (dialogs/toasts portal to body). */
    contain: layout;
}

/* Batch N: bound the subject tabs row so BbTabsList's Responsive overflow detector
   (collapses tabs into a Select when they overflow their container) can fire on
   narrow viewports instead of the 8-tab row forcing page-level horizontal overflow.
   overflow-x: clip contains the spill without creating a scroll container (sticky
   headers keep working); the dropdown popover portals to body and is unaffected. */
.subject-tabs {
    max-width: 100%;
    min-width: 0;
    overflow-x: clip;
}

/* Protect person tabs: the same bound as .subject-tabs (Batch N) so BbTabsList's
   Responsive overflow detector can collapse the row into a Select on narrow
   viewports instead of the tab row forcing page-level horizontal overflow. */
.protect-tabs {
    max-width: 100%;
    min-width: 0;
    overflow-x: clip;
}

/* Tenant settings tabs: the same bound as .subject-tabs / .protect-tabs so BbTabsList's Responsive
   overflow detector can collapse the six-tab row into a Select on narrow viewports instead of the
   row forcing page-level horizontal overflow. */
.tenant-tabs {
    max-width: 100%;
    min-width: 0;
    overflow-x: clip;
}

.subject-map {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

/* Subject map tab panel (Batch N): local panel treatment mirroring the /map page's
   rounded content panel WITHOUT touching /map's .home-map rules — border, surface,
   clipping and 1rem radius live here. The flex:1/min-height:0 chain stays intact.
   Issue #111: the former hard `min-height: 320px` floor was removed — it defeated
   min-height:0 on short/mobile viewports so the map could not shrink to the available
   card height, and the map host chain below now reports its real measured size to
   MapLibre via the shared ResizeObserver. The .subject-page :has(min-height:320px)
   floor still prevents tiny-viewport clipping/scroll. */
.subject-map-panel {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    border: 1px solid var(--border);
    background: var(--card);
    border-radius: 1rem;
}

/* Subject page MAP TAB ONLY (:has scopes the fixed viewport height to the map
   tab — panel tabs stay in normal flow, grow the page, and let the footer follow
   content). Mirrors the .map-page footer reserve (wrapped multi-row footer +
   margin below lg, single-row footer + margin at lg+), so the global footer stays
   visible without page scroll on the map tab. Panel tabs (.is-panel-tab) scroll
   with the page. Map tab values unchanged. Measured budget at 1440x900:
   header ~231px + tab gutter 16px + footer ~40px + margins. */
.subject-page:has(> .subject-tab-content.is-map-tab) {
    height: calc(100vh - var(--halo-shell-chrome) - 7rem);
    height: calc(100dvh - var(--halo-shell-chrome) - 7rem);
    /* Override the base min-height (320px floor): the 320px floor mirrors
       .map-page (tiny viewports scroll instead of clipping). */
    min-height: 320px;
}

@media (min-width: 64rem) {
    .subject-page:has(> .subject-tab-content.is-map-tab) {
        height: calc(100vh - var(--halo-shell-chrome) - 3.5rem);
        height: calc(100dvh - var(--halo-shell-chrome) - 3.5rem);
    }
}

.cursor-help {
    cursor: pointer;
    color: var(--muted-foreground);
    text-decoration: underline dotted transparent;
    text-underline-offset: 2px;
    transition: color 120ms ease, text-decoration-color 120ms ease;
}

.cursor-help:hover,
.cursor-help:focus-visible {
    color: var(--foreground);
    text-decoration-color: var(--primary);
}

.cursor-help:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
    border-radius: 2px;
}

/* HumanizeDateTimeOffset's popup opens from the component's own host span, never from an
   ancestor Tailwind `group`. Scoping the rule here (unlayered, so it outranks
   blazorblueprint.css's @layer utilities `opacity-0`) makes the tooltip immune to any
   ancestor `group` — BlazorBlueprint sidebar/menu items carry one, and a descendant
   `group-hover` would otherwise reveal every timestamp tooltip at once. */
.halo-tooltip-host:hover > .halo-tooltip-popup {
    opacity: 1;
}

/* MapLibre popup chrome (ADR 0026) — rounded corners + subtle shadow, light/dark via
   CSS vars, WCAG-compliant contrast. The info-window bubble is clamped to the map container. */
.maplibregl-popup {
    border-radius: 10px;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
    font: 400 13px/1.45 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

/* Popup chrome is scoped under .maplibregl-popup on purpose: the vendored
   maplibre-gl.css <link> comes AFTER app.css in App.razor, so same-specificity
   MapLibre rules (white popup background, transparent close button, white tips)
   would otherwise win and break dark mode. The extra class beats them always. */
.maplibregl-popup .maplibregl-popup-content {
    background: var(--card);
    background-color: var(--card);
    color: var(--foreground);
    border-radius: 10px;
    padding: 0;
    overflow: hidden;
    /* Tall enough for the expanded <details> telemetry (~580px) so no inner
       scrollbar appears; viewport-relative cap keeps huge popups on-screen. */
    max-height: min(640px, 70vh);
    overflow-y: auto;
    border: 1px solid var(--border);
}

.maplibregl-popup-content a:not(.halo-iw-action) {
    color: var(--primary);
}

/* Close button sits INSIDE the popup corner (never outside it): the content box
   clips overflow, so the old -10px offset cut the button off, and the circle only
   renders when every paint property below wins — hence explicit background-color. */
.maplibregl-popup .maplibregl-popup-content > button.maplibregl-popup-close-button {
    color: var(--muted-foreground);
    font-size: 15px;
    line-height: 1;
    width: 26px;
    height: 26px;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    top: 8px;
    right: 8px;
    z-index: 3;
    border-radius: 50%;
    border: 1px solid var(--border);
    background: var(--card);
    background-color: var(--card);
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
}

.maplibregl-popup .maplibregl-popup-content > button.maplibregl-popup-close-button:hover {
    color: var(--foreground);
    background: var(--muted);
}

.maplibregl-popup.maplibregl-popup-anchor-top .maplibregl-popup-tip,
.maplibregl-popup.maplibregl-popup-anchor-top-left .maplibregl-popup-tip,
.maplibregl-popup.maplibregl-popup-anchor-top-right .maplibregl-popup-tip {
    border-bottom-color: var(--card);
}

.maplibregl-popup.maplibregl-popup-anchor-bottom .maplibregl-popup-tip,
.maplibregl-popup.maplibregl-popup-anchor-bottom-left .maplibregl-popup-tip,
.maplibregl-popup.maplibregl-popup-anchor-bottom-right .maplibregl-popup-tip {
    border-top-color: var(--card);
}

.maplibregl-popup.maplibregl-popup-anchor-left .maplibregl-popup-tip {
    border-right-color: var(--card);
}

.maplibregl-popup.maplibregl-popup-anchor-right .maplibregl-popup-tip {
    border-left-color: var(--card);
}

/* Unified info-window card (ADR 0026, Phase 2) — one layout shared by both maps:
   inset grade accent bar (stops short of the close-button clear space), name/timestamp
   header with relative pill, grade + BLE badges, hero stat band, Device/Network rows,
   native <details> telemetry expander, condensed footnote, Home-only action links. */
.halo-iw {
    min-width: 260px;
    max-width: 340px;
    padding: 12px 14px 13px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    font-size: 13px;
}

.halo-iw-accent {
    height: 5px;
    border-radius: 3px;
    margin: 2px 30px 0 2px;
}

.halo-iw-accent-gps {
    background: #34A853;
}

.halo-iw-accent-degraded {
    background: #FB8C00;
}

.halo-iw-accent-network {
    background: #4285F4;
}

.halo-iw-accent-invalid {
    background: #EA4335;
}

.halo-iw-accent-unknown {
    background: var(--muted-foreground);
}

.halo-iw-header {
    display: flex;
    flex-direction: column;
    gap: 3px;
    padding-right: 22px;
}

.halo-iw-title-row {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}

.halo-iw-title {
    font-weight: 700;
    font-size: 16px;
    color: var(--foreground);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.halo-iw-title--wrap {
    white-space: normal;
    overflow: visible;
    font-variant-numeric: tabular-nums;
}

.halo-iw-subtitle {
    color: var(--muted-foreground);
    font-size: 12px;
    font-variant-numeric: tabular-nums;
}

.halo-iw-relative {
    flex: none;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.02em;
    color: var(--primary);
    background: color-mix(in srgb, var(--primary) 12%, transparent);
    border: 1px solid color-mix(in srgb, var(--primary) 35%, transparent);
    padding: 2px 9px;
    border-radius: 999px;
    cursor: default;
}

.halo-iw-badges {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
}

.halo-iw-badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 10px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.03em;
    color: #fff;
}

.halo-iw-badge .dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: currentColor;
    box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.35);
}

.halo-iw-badge-gps {
    background: #157347;
}

.halo-iw-badge-degraded {
    background: #9a5200;
}

.halo-iw-badge-network {
    background: #2f6edb;
}

.halo-iw-badge-invalid {
    background: #b3261e;
}

.halo-iw-badge-unknown {
    background: #5b6472;
}

.halo-iw-badge-ok {
    background: #157347;
}

.halo-iw-hero {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    background: color-mix(in srgb, var(--primary) 8%, transparent);
    border: 1px solid var(--border);
    border-radius: 8px;
    overflow: hidden;
}

.halo-iw-hero > div {
    padding: 8px 6px 7px;
    text-align: center;
    min-width: 0;
}

.halo-iw-hero > div + div {
    border-left: 1px solid var(--border);
}

.halo-iw-hero .h {
    display: block;
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--muted-foreground);
    margin-bottom: 2px;
}

.halo-iw-hero .v {
    display: block;
    font-size: 14px;
    font-weight: 700;
    color: var(--foreground);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.halo-iw-hero .halo-iw-battery-value {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 2px;
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
}

.halo-iw-keyvals {
    display: flex;
    flex-direction: column;
    gap: 5px;
}

.halo-iw-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 12px;
    min-width: 0;
}

.halo-iw-label {
    flex: none;
    font-size: 12px;
    color: var(--muted-foreground);
}

.halo-iw-value {
    min-width: 0;
    font-size: 13px;
    color: var(--foreground);
    text-align: right;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    font-variant-numeric: tabular-nums;
}

.halo-iw-more {
    border: 1px solid var(--border);
    border-radius: 8px;
}

.halo-iw-more summary {
    cursor: pointer;
    list-style: none;
    padding: 7px 10px;
    font-size: 12px;
    font-weight: 600;
    color: var(--primary);
    display: flex;
    align-items: center;
    gap: 6px;
}

.halo-iw-more summary::-webkit-details-marker {
    display: none;
}

.halo-iw-more summary::before {
    content: "\25B8";
    font-size: 11px;
}

.halo-iw-more[open] summary::before {
    content: "\25BE";
}

.halo-iw-more .halo-iw-keyvals {
    padding: 2px 10px 9px;
}

.halo-iw-footnote {
    display: flex;
    flex-direction: column;
    gap: 2px;
    font-size: 11px;
    color: var(--muted-foreground);
}

.halo-iw-footnote-label {
    margin-right: 8px;
}

.halo-iw-mono {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 10.5px;
    color: var(--muted-foreground);
}

.halo-iw-actions {
    display: flex;
    gap: 8px;
    margin-top: 2px;
}

.halo-iw-action {
    flex: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 8px 10px;
    border-radius: 8px;
    font-size: 13px;
    font-weight: 600;
    text-decoration: none;
    border: 1px solid transparent;
}

.halo-iw-action-primary {
    background: var(--primary);
    color: var(--primary-foreground);
}

.halo-iw-action-primary:hover {
    filter: brightness(1.08);
}

.halo-iw-action-secondary {
    background: transparent;
    color: var(--primary);
    border-color: color-mix(in srgb, var(--primary) 45%, transparent);
}

.halo-iw-action-secondary:hover {
    background: color-mix(in srgb, var(--primary) 10%, transparent);
}

/* Info-window icons are inline Lucide SVGs (popups render raw HTML strings so the LucideIcon
   component cannot mount inside). Sized to sit inline with popup text. */
.halo-iw svg {
    width: 1rem;
    height: 1rem;
    vertical-align: -0.18em;
}

.halo-iw-action svg.halo-iw-action-icon {
    width: 1.15rem;
    height: 1.15rem;
}

svg.halo-iw-battery-opt-icon {
    width: 1rem;
    height: 1rem;
    vertical-align: -0.18em;
}

.halo-iw-battery-opt-icon {
    color: #eab308;
}

/* Zone info window (issue #205) — reuses the unified card classes above; only the
   zone-specific accent/badge colours and the per-condition section list are new. */
.halo-iw-accent-zone {
    background: #7c3aed;
}

.halo-iw-badge-zone {
    background: #5b21b6;
}

.halo-iw-zone-conditions {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding-top: 2px;
}

.halo-iw-zone-condition {
    display: flex;
    flex-direction: column;
    gap: 8px;
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 9px 10px;
}

.halo-iw-zone-condition-title {
    font-weight: 700;
    font-size: 13px;
    color: var(--foreground);
    white-space: normal;
    overflow: visible;
}

/* Map search (postcodes.io, device page) — floating box top-left beside the filter button,
   theme-aware via halo.css tokens. */
.device-map-filter-button {
    position: absolute;
    top: 12px;
    left: 12px;
    z-index: 900;
    width: 34px;
    height: 34px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--card);
    color: var(--foreground);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
    cursor: pointer;
    font-size: 0;
}

.device-map-filter-button:hover {
    background: var(--muted);
}

/* Hidden on non-map tabs WITHOUT removing it from the DOM — a conditional before the map would
   re-mount the HaloMap on tab switch (blanking the map). */
.device-map-filter-button.is-hidden {
    display: none;
}

.halo-map-search {
    position: absolute;
    top: 12px;
    left: 12px;
    z-index: 900;
    width: 280px;
    max-width: calc(100% - 66px);
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 6px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
    font: 400 13px/1.4 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

.halo-map-search-row {
    position: relative;
}

.halo-map-search-input {
    width: 100%;
    box-sizing: border-box;
    border: none;
    background: transparent;
    color: var(--foreground);
    padding: 7px 30px 7px 10px;
    font: inherit;
    outline: none;
}

.halo-map-search-input::placeholder {
    color: var(--muted-foreground);
}

.halo-map-search-clear {
    position: absolute;
    top: 50%;
    right: 6px;
    transform: translateY(-50%);
    display: none;
    border: none;
    background: transparent;
    color: var(--muted-foreground);
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
    padding: 2px 4px;
}

.halo-map-search-results {
    border-top: 1px solid var(--border);
    max-height: 280px;
    overflow-y: auto;
}

.halo-map-search-result {
    display: block;
    width: 100%;
    text-align: left;
    border: none;
    background: transparent;
    color: var(--foreground);
    padding: 7px 10px;
    font: inherit;
    cursor: pointer;
}

.halo-map-search-result:hover {
    background: rgba(26, 115, 232, 0.12);
}

/* Keyboard navigation (Up/Down) highlights the focused result. */
.halo-map-search-result.halo-map-search-result-active {
    background: rgba(26, 115, 232, 0.22);
    outline: none;
}

/* The search result's label rides a Maplibre Popup (a Google-Maps-style info window anchored to
   the point on the ground) — style only the label text inside the popup bubble. */
.halo-search-marker {
    font: 600 13px/1.4 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    white-space: nowrap;
}

/* Basemap layer switcher (Standard / OS Road / ... / Aerial Hybrid) — top-right of the map */
.halo-map-layer-control {
    position: absolute;
    top: 12px;
    right: 12px;
    z-index: 900;
    display: flex;
    align-items: center;
    gap: 8px;
}

/* Heading / zoom / tilt readout — sits to the LEFT of the layers button. */
.halo-map-camera-readout {
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 6px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
    color: var(--foreground);
    font: 500 12px/34px ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    padding: 0 10px;
    white-space: nowrap;
    pointer-events: none;
}

/* Issue #237: the heading / zoom / tilt readout only fits from the Tailwind md
   breakpoint up. Below HaloBreakpoints.Mobile it is hidden; the layer toggle
   stays. px (not rem) deliberately mirrors HaloBreakpoints.Mobile. */
@media (max-width: 767.98px) {
    .halo-map-camera-readout {
        display: none;
    }
}

.halo-map-layer-toggle {
    width: 34px;
    height: 34px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--card);
    color: var(--foreground);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
    cursor: pointer;
}

.halo-map-layer-toggle:disabled {
    opacity: 0.5;
    cursor: default;
}

.halo-map-layer-menu {
    position: absolute;
    top: 42px;
    right: 0;
    min-width: 150px;
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 6px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
    overflow: hidden;
}

.halo-map-layer-divider {
    width: 100%;
    height: 1px;
    background: var(--border);
    margin: 4px 0;
}

.halo-map-layer-option {
    display: block;
    width: 100%;
    text-align: left;
    border: none;
    background: transparent;
    color: var(--foreground);
    padding: 8px 12px;
    font: 400 13px/1.4 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    cursor: pointer;
}

.halo-map-layer-option:hover {
    background: rgba(26, 115, 232, 0.12);
}

.halo-map-layer-option.is-active {
    background: rgba(26, 115, 232, 0.22);
    font-weight: 600;
}

/* Live draw-time zone area readout — sits directly under the filter/search row (top-left). */
.halo-zone-draw-area {
    position: absolute;
    top: 54px;
    left: 12px;
    z-index: 1001;
    background: rgba(33, 37, 41, 0.92);
    color: #fff;
    padding: 5px 10px;
    border-radius: 4px;
    font: 500 12px/1.4 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    pointer-events: none;
    display: none;
}

.halo-zone-draw-area-buffer {
    opacity: 0.8;
}

/* Responsive utilities come from blazorblueprint.css, which ships its own @media blocks
   for the Tailwind sm/md/lg scale (40/48/64rem). Removed 2026-09: hand-written shims for
   sm:block and lg:grid-cols-12 / lg:col-span-5 / lg:col-span-7 (the package never shipped
   those classes); markup now uses only shipped utilities plus ResponsiveView tier switches. */


