/* ═══════════════════════════════════════════════════════════════════
    URUS — Main application stylesheet
    Loaded by base_page.html (authenticated pages) and
    _auth_base.html (login, password reset, logged out).
    ═══════════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════
    DUNE RISE — Display font for URUS branding wordmark only.
    Free for personal + commercial use. Source: dafont.com/dune-rise
    ═══════════════════════════════════════════════════════════════════ */

@font-face {
    font-family: 'Dune Rise';
    font-style: normal;
    font-display: swap;
    font-weight: 400;
    src: url("../vendor/dune-rise/dune-rise.efa61f3c96af.woff2") format('woff2');
}

/* ═══════════════════════════════════════════════════════════════════
    NOTO NASKH ARABIC — Jawi branding script only.
    OFL license. Source: fontsource (Google Fonts).
    Arabic subset only — Latin glyphs fall through to JetBrains Mono.
    ═══════════════════════════════════════════════════════════════════ */

@font-face {
    font-family: 'Noto Naskh Arabic';
    font-style: normal;
    font-display: swap;
    font-weight: 400;
    src: url("../vendor/noto-naskh-arabic@5.2.11/noto-naskh-arabic-arabic-400-normal.9ebecc332fb3.woff2")
        format('woff2');
    unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0897-08E1,
        U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FE74,
        U+FE76-FEFC;
}

/* ═══════════════════════════════════════════════════════════════════
    JETBRAINS MONO — Primary body font.
    Variable weight (100-800), Latin subset.
    Vendored from @fontsource-variable/jetbrains-mono via npm.
    ═══════════════════════════════════════════════════════════════════ */

/* latin */
@font-face {
    font-family: 'JetBrains Mono';
    font-style: normal;
    font-display: swap;
    font-weight: 100 800;
    src: url("../vendor/jetbrains-mono@2.304/jetbrains-mono-latin-wght-normal.b058178d7f30.woff2") format('woff2');
}

/* ═══════════════════════════════════════════════════════════════════
    OPENTYPE FEATURES
    Font family + size now set in SCSS (static/scss/urus-bootstrap.scss).
    These OpenType features are CSS-only, not SASS-configurable.
    ═══════════════════════════════════════════════════════════════════ */
:root {
    /* tabular-nums for fixed-width digits (column alignment in tables). */
    font-variant-numeric: tabular-nums;
}

/* ═══════════════════════════════════════════════════════════════════
    LAYOUT — Column-based flexbox layout
    Body is a horizontal flex container: [sidebar][content-wrapper]
    ═══════════════════════════════════════════════════════════════════ */
:root {
    --sidebar-width: 240px;

    /* Component tokens */
    --urus-legend-dot-size: 10px;
    --urus-legend-dot-size-lg: 12px;
    --urus-legend-line-width: 12px;
    --urus-legend-panel-width: 200px;

    --urus-thumbnail-xs: 60px;
    --urus-thumbnail-sm: 100px;
    --urus-thumbnail-md: 180px;

    --urus-chart-height: 300px;
    --urus-sticky-offset: 1rem;
    --urus-map-z-index: 1000;
    --urus-map-inset: 0.5rem;
    --urus-font-size-s: 0.8125rem;
    --urus-font-size-xs: 0.6875rem;

    /* Map layer colors — single source of truth for templates + JS */
    --urus-map-completed: var(--bs-success);
    --urus-map-in-progress: var(--bs-warning);
    --urus-map-overdue: var(--bs-danger);
    --urus-map-approved: var(--bs-primary);
    --urus-map-reported: var(--bs-secondary);
    --urus-map-level-1: var(--bs-info);
    --urus-map-level-2: var(--bs-primary);
    --urus-map-level-2-fill: rgba(var(--bs-primary-rgb), 0.1);
    --urus-map-roads: var(--bs-warning);
    --urus-map-palette-0: var(--bs-warning);
    --urus-map-palette-1: var(--bs-danger);
    --urus-map-palette-2: var(--bs-success);
    --urus-map-palette-3: var(--bs-info);
    --urus-map-palette-4: var(--bs-primary);
    --urus-map-palette-5: var(--bs-purple);
    --urus-map-palette-6: var(--bs-orange);
    --urus-map-palette-7: var(--bs-teal);
}

.urus-sidebar {
    width: var(--sidebar-width);
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    background: var(--bs-secondary-bg);
    border-right: 1px solid var(--bs-border-color);
    transition: margin-left 0.3s ease;
}

.urus-content-wrapper {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

/* Mobile: sidebar becomes fixed overlay */
@media (max-width: 991.98px) {
    .urus-sidebar {
        position: fixed;
        top: 0;
        left: 0;
        /* 100dvh accounts for mobile browser URL bar; 100vh fallback for
           engines without dvh support (< Chrome 108 / Firefox 101 / Safari
           15.4). Without this, #sidebar-utilities (bell + profile) clips
           below the address bar on Android Chrome + Firefox. */
        height: 100vh;
        height: 100dvh;
        z-index: 1040;
        transform: translateX(-100%);
    }
    .urus-sidebar.show {
        transform: translateX(0);
    }
    #sidebar-backdrop {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: rgba(0, 0, 0, 0.5);
        z-index: 1035;
        display: none;
    }
    #sidebar-backdrop.show {
        display: block;
    }
}

/* Desktop: collapse via negative margin */
@media (min-width: 992px) {
    .urus-sidebar.collapsed {
        margin-left: calc(-1 * var(--sidebar-width));
    }
}

/* Content area */
.urus-content-area {
    flex: 1;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

/* Scrollable region — contains #main + offcanvas */
.urus-content-scroll {
    flex: 1;
    display: flex;
    flex-direction: column;
    position: relative;
    overflow: hidden;
}

/* Offcanvas within scrollable region (native <aside>, Bootstrap-free) */
.urus-offcanvas {
    --bs-offcanvas-padding-x: 1rem;
    --bs-offcanvas-padding-y: 1rem;
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    z-index: 1045;
    width: min(400px, 100vw);
    background-color: var(--bs-secondary-bg);
    color: var(--bs-body-color);
    border-left: 1px solid var(--bs-border-color-translucent);
    display: flex;
    flex-direction: column;
    overflow-y: auto;
    transform: translateX(100%);
    visibility: hidden;
    transition: transform 0.3s ease-in-out, visibility 0s linear 0.3s;
}

.urus-offcanvas.show {
    transform: translateX(0);
    visibility: visible;
    transition: transform 0.3s ease-in-out, visibility 0s linear 0s;
}

/* Modal uses Secondary surface */
.modal-content {
    --bs-modal-bg: var(--bs-secondary-bg);
}

/* --- Tree Picker --- */

.tree-children {
    margin-left: 0;
}

/* Picker trees (<c-tree-group-row>) indent children by nesting — those rows
   carry no per-row level padding, unlike the catalog tree's <c-sor-heading-row>.
   Scoped to this modifier so the catalog tree stays flat (margin 0) and keeps
   its full-width row borders — the reason .tree-children itself is 0 (fce0df15).
   Picker rows have no border-bottom, so the margin is safe here. */
.tree-children-indent {
    margin-left: 1rem;
}

/* Tree-picker scroll regions. The 70vh cap was written for a FLOATING modal,
   where it stops a long tree from growing past the viewport. Below md the
   modal is fullscreen (see the fullscreen-modal block), so the same cap leaves
   dead space instead — measured 172–340px unused on a 390×844 screen, ~23% of
   the phone. There, the region fills the modal body and that body's own
   height governs. Was two inline styles in cotton/tree_picker.html. */
.tree-picker-scroll {
    max-height: 70vh;
    overflow-y: auto;
}
.tree-picker-split {
    min-height: 400px;
}
@media (max-width: 767.98px) {
    .tree-picker-scroll {
        max-height: none;
        height: 100%;
    }
    .tree-picker-split {
        min-height: 0;
        height: 100%;
    }
}

.tree-item-active {
    background-color: var(--bs-primary-bg-subtle) !important;
}

.tree-item:hover {
    background-color: var(--bs-tertiary-bg);
}

/* SoR tree item rows: Bil.-style code column — right-aligned minimum width so
   1 / 10 / 154 share a right edge and names start at a uniform x (mirrors the
   JKR PDF's Bil. column); color pairs with the heading-row code links. */
.sor-item-code {
    min-width: 2.5rem;
    text-align: right;
}

.tree-detail-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    height: 100%;
    color: var(--bs-secondary-color);
}

/* Sidebar utilities bottom bar */
#sidebar-utilities {
    min-height: 57px;
    align-items: center;
}

/* Scrollable areas */
.sidebar-nav-scroll {
    flex: 1;
    overflow-y: auto;
}
#main { overflow-y: auto; }
#subnav { scrollbar-width: none; }
#subnav::-webkit-scrollbar { display: none; }

/* Horizontal layout contract for pages rendered through base_page.html's
   non-full_bleed wrapper: EVERYTHING GETS THE GUTTER, and the SURFACES —
   cards and alerts — opt out and render edge-to-edge below md. Text, headings
   and control rows keep the gutter.

   The gutter and the breakout share --urus-gutter so they cannot drift apart. */
.urus-content-gutter {
    --urus-gutter: 1rem;
    padding-left: var(--urus-gutter);
    padding-right: var(--urus-gutter);
}

@media (max-width: 767.98px) {
    /* Every card breaks out to the screen edge — 17 of the dashboard's 19 sit
       inside grid columns, and they are full-bleed on mobile like the rest.

       One rule serves both because Bootstrap already puts every column's
       CONTENT edge at the wrapper's content edge: row margin (-half gutter) +
       column padding (+half gutter) cancel, whatever the gutter size. So a
       grid card and a direct-child card start from the same 16px, and the same
       -1rem lands both at 0. Nothing here touches the grid itself — the row's
       overhang is harmless now that the wrapper has padding to absorb it, and
       collapsing gutters would strip the space between the bare col-6 / col-4
       pairs that stay side by side on mobile. */
    .urus-content-gutter .card {
        margin-left: calc(-1 * var(--urus-gutter));
        margin-right: calc(-1 * var(--urus-gutter));
        border-radius: 0;
        border-left: 0;
        border-right: 0;
    }

    /* Alerts are surfaces too, and take the same treatment — including the
       bulk-actions bar, which IS an .alert.alert-secondary. Chosen 2026-07-31
       by flipping both variants live at 390px rather than comparing stills:
       full-bleed reads as coherent with the card stack, and the alert keeps
       its "the system is telling you something" weight from its icon, tint and
       dismiss button rather than from its outline. The alert's own 16px
       padding is untouched, so its TEXT stays on the same line as everything
       else — only the tinted band widens, buying ~10% text width on a 390px
       screen (358px vs 326px), which tells most on the wordiest alerts. */
    .urus-content-gutter .alert {
        margin-left: calc(-1 * var(--urus-gutter));
        margin-right: calc(-1 * var(--urus-gutter));
        border-radius: 0;
        border-left-width: 0;
        border-right-width: 0;
    }

    /* django messages live in #alert-container, a sibling of #main that keeps
       its own px-0 px-md-3 — already flush here, so they need the square edges
       only, not the breakout. */
    #alert-container > .alert {
        border-radius: 0;
        border-left-width: 0;
        border-right-width: 0;
    }

    /* The page rule must not reach INTO a dialog. A dialog that happens to sit
       inside the wrapper is still a separate layout context — its surfaces are
       handled by the fullscreen-modal block, which cancels the MODAL BODY's
       padding. Without this, a surface that is not a direct child of
       `.modal-body` falls through to the rule above and cancels
       `--urus-gutter` against the modal's padding, landing flush only because
       the two values happen to be equal — and a dialog outside the wrapper
       (`#modal`) would render the same partial differently. Latent today (no
       template ships that shape) but it is the coupling this rule set exists
       to prevent. */
    .urus-content-gutter dialog .card,
    .urus-content-gutter dialog .alert {
        margin-left: 0;
        margin-right: 0;
    }

    /* ...and never twice. A surface inside another surface is already at its
       container's edge; a second breakout would compound and push it outside. */
    .urus-content-gutter .card .card {
        margin-left: 0;
        margin-right: 0;
    }
    .urus-content-gutter .card .alert {
        margin-left: 0;
        margin-right: 0;
        border-radius: var(--bs-alert-border-radius);
        border-left-width: var(--bs-border-width);
        border-right-width: var(--bs-border-width);
    }

    /* Subnav right-edge fade: hints at horizontal scroll when tabs overflow
       the viewport. Template sets d-flex flex-nowrap overflow-auto — this
       adds the visual affordance so the swipe is discoverable. mask-image on
       the subnav itself fades actual tab content regardless of parent
       padding; invisible when no overflow (no content to fade). */
    #subnav {
        mask-image: linear-gradient(to right, #000 calc(100% - 2rem), transparent 100%);
        -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 2rem), transparent 100%);
    }

    /* Map cards on mobile: 60vh + 400px min-height dominates the iPhone 14
       viewport (664px) with asset/WO metadata pushed below the fold. Shrink
       the large variant so both the map AND the Information card are visible
       without scrolling. */
    .map-container-lg {
        min-height: 240px;
        max-height: 40vh;
    }
}

/* Small utilities */
.urus-avatar { width: 48px; height: 48px; }
.urus-avatar-lg { width: 64px; height: 64px; font-size: 1.25rem; }
.fs-xxs { font-size: 0.7rem; }

/* Detail page key-value tables: label column + row number column */
.detail-label-cell { width: 40%; }
.row-number-cell { width: 60px; }

/* Timeline/history component (user_detail_history) */
.timeline-rail { width: 2rem; }
.timeline-badge { width: 1.75rem; height: 1.75rem; }
.timeline-connector { margin-top: 0.25rem; }

/* Photo comparison preview */
.photo-preview-constrained { max-height: 200px; max-width: 100%; }

/* Map popup content (executive-map, nearby-assets) */
.map-popup { min-width: 150px; }
.map-popup-lg { min-width: 180px; }
.map-popup-title { font-weight: 600; margin-bottom: 0.25rem; }
.map-popup-meta { font-size: var(--urus-font-size-s); color: var(--bs-secondary-color); margin-bottom: 0.25rem; }
.map-popup-meta:last-child { margin-bottom: 0; }
.map-popup-link { display: block; font-size: var(--urus-font-size-s); margin-top: 0.375rem; }

/* Notification badge pulse (SSE updates) */
@keyframes badge-pulse {
    0% { box-shadow: 0 0 0 0 rgba(var(--bs-danger-rgb), 0.7); }
    70% { box-shadow: 0 0 0 6px rgba(var(--bs-danger-rgb), 0); }
    100% { box-shadow: 0 0 0 0 rgba(var(--bs-danger-rgb), 0); }
}
.badge-pulse { animation: badge-pulse 0.6s ease-out; }

/* Legend indicators (executive map) */
.legend-dot {
    display: inline-block;
    width: var(--urus-legend-dot-size);
    height: var(--urus-legend-dot-size);
    padding: 0;
    border-radius: 50%;
}
.legend-dot-lg {
    width: var(--urus-legend-dot-size-lg);
    height: var(--urus-legend-dot-size-lg);
}
.legend-square {
    display: inline-block;
    width: var(--urus-legend-dot-size);
    height: var(--urus-legend-dot-size);
    padding: 0;
    border-radius: 2px;
}
.legend-square-lg {
    width: var(--urus-legend-dot-size-lg);
    height: var(--urus-legend-dot-size-lg);
}
.legend-line {
    display: inline-block;
    width: var(--urus-legend-line-width);
    height: 3px;
    padding: 0;
}

/* Image thumbnails */
.thumbnail-xs {
    width: var(--urus-thumbnail-xs);
    height: var(--urus-thumbnail-xs);
    object-fit: cover;
}
.thumbnail-sm { height: var(--urus-thumbnail-sm); object-fit: cover; }
.thumbnail-md { height: var(--urus-thumbnail-md); object-fit: cover; }

/* Chart.js containers */
.chart-container { height: var(--urus-chart-height); }



/* ═══════════════════════════════════════════════════════════════════
    SIDEBAR NAVIGATION
    ═══════════════════════════════════════════════════════════════════ */
.sidebar-heading {
    font-size: var(--urus-font-size-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--bs-secondary-color);
    padding: 0.75rem 0.75rem 0.25rem;
    margin-top: 1rem;
    margin-bottom: 0;
}
.sidebar-heading:first-child { margin-top: 0; }

/* Match branding height to topbar first row */
#sidebar-brand {
    min-height: 61px;
    display: flex;
    align-items: center;
    background-color: var(--bs-primary);
    border-bottom: 1px solid var(--bs-border-color);
}

/* Sidebar primary nav — left-border active indicator (mirrors subnav underline) */
#sidebar-nav .nav-link {
    color: var(--bs-body-color);
    border-left: 3px solid transparent;
    border-radius: 0;
    padding-left: 0.75rem;
    padding-right: 0.75rem;
}
#sidebar-nav .nav-link.active {
    color: var(--bs-link-color);
    font-weight: 600;
    border-left-color: currentcolor;
}
#sidebar-nav .nav-link:hover:not(.active) {
    background: var(--bs-tertiary-bg);
}

/* Sidebar utilities — pills (notification, profile) */
#sidebar-utilities .nav-link {
    color: var(--bs-body-color);
}
#sidebar-utilities .nav-link.active {
    color: var(--bs-white);
}
#sidebar-utilities .nav-link:hover:not(.active) {
    background: var(--bs-tertiary-bg);
}

/* ═══════════════════════════════════════════════════════════════════
    TOPBAR — Two-row elevated command bar
    Row 1: hamburger + page title + action buttons
    Row 2: subnav tabs (conditional)
    ═══════════════════════════════════════════════════════════════════ */
#topbar {
    background: var(--bs-secondary-bg);
    flex-shrink: 0;
}
#topbar-row1 {
    display: flex;
    align-items: center;
    min-height: 61px;
    border-bottom: 1px solid var(--bs-border-color);
    padding: 0 1rem;
    gap: 0.5rem;
}
#topbar-row1 > h1 {
    min-width: 0; /* Allow flex truncation — text-truncate needs this */
}
#topbar-row2 {
    padding: 0 1rem;
}

/* nav-underline global: primary = selected (matches sidebar convention) */
.nav-underline {
    --bs-nav-underline-link-active-color: var(--bs-link-color);
}
.nav-underline .nav-link {
    color: var(--bs-body-color);
}
.nav-underline .nav-link:hover:not(.active) {
    background: var(--bs-tertiary-bg);
    border-bottom-color: var(--bs-border-color);
}

/* Subnav layout overrides */
#subnav {
    --bs-nav-underline-border-width: 3px;
}
.nav-underline .nav-link {
    padding: 0.75rem 1rem;
}
#subnav .nav-link.active {
    font-weight: 600;
}


/* Table visual hierarchy (inside cards on secondary-bg):
   thead (tertiary-bg) > group header (subtle tint) > body rows (card bg)
   Column headers don't wrap — long headers scroll via .table-responsive. */
.table > thead {
    --bs-table-bg: var(--bs-tertiary-bg);
}
.table > thead th {
    white-space: nowrap;
}

/* Group header rows — subtle tint, less prominent than thead.
   Uses emphasis-color-rgb (black in light, white in dark) for auto-adaptation.
   Bootstrap's compile-time .table-secondary doesn't adapt to dark mode
   (Bootstrap issue #39272), so we override for both modes. */
.table-secondary,
.table-light {
    --bs-table-color: var(--bs-body-color);
    --bs-table-bg: rgba(var(--bs-emphasis-color-rgb), 0.06);
    --bs-table-border-color: var(--bs-border-color);
    --bs-table-hover-color: var(--bs-body-color);
    --bs-table-hover-bg: rgba(var(--bs-emphasis-color-rgb), 0.08);
}

/* Tables in cards: horizontal breathing room for first/last cells */
.card .table th:first-child,
.card .table td:first-child {
    padding-left: 1rem;
}
.card .table th:last-child,
.card .table td:last-child {
    padding-right: 1rem;
}

/* Tables in cards: prevent double bottom border when no card-footer */
.card.overflow-hidden .table-responsive:last-child .table > :last-child > tr:last-child > * {
    border-bottom-width: 0;
}

/* Card header as last visible child: remove bottom border to avoid double-border */
.card.overflow-hidden > .card-header:has(+ .d-none) {
    border-bottom-width: 0;
}

/*
 * Stat-card footers align across a row (docs/specs/dashboard.md §6.4).
 *
 * `h-100` equalises card BOTTOMS, not footers: a one-line definition and a
 * two-line one produce footers of different heights, so the divider rules land
 * at different heights across a row — the reported "box footer not same
 * height". Reserving two lines makes the divider level however each definition
 * wraps. Below `sm` the cards stack, so there is no row to align within and the
 * reservation would only add dead space.
 */
@media (min-width: 576px) {
    .stat-card-footer {
        min-height: calc(2 * 1.5em + 0.5rem);
    }
}

/* Table header — sortable columns use body color, not primary */
#table thead a {
    color: var(--bs-body-color);
}
#table thead a:hover {
    color: var(--bs-body-color);
}

/* ═══════════════════════════════════════════════════════════════════
    HTMX INDICATORS
    ═══════════════════════════════════════════════════════════════════ */
#table-wrapper { position: relative; }
.htmx-indicator { display: none; }
.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator { display: inline-block; }
.htmx-indicator-overlay {
    display: none;
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(var(--bs-body-bg-rgb), 0.85);
    z-index: 10;
}
.htmx-request .htmx-indicator-overlay {
    display: block;
}
.htmx-indicator-overlay > .htmx-indicator-content {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    padding: 0.5rem 1.25rem;
    background: var(--bs-body-bg);
    border: 1px solid var(--bs-border-color);
    border-radius: 999px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    color: var(--bs-body-color);
    font-weight: 600;
    animation: htmx-indicator-pulse 1.4s ease-in-out infinite;
}
@keyframes htmx-indicator-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.55; }
}

/* ═══════════════════════════════════════════════════════════════════
    GLOBAL HTMX PROGRESS BAR (#htmx-progress)
    A thin top-of-page bar (YouTube/NProgress style) shown during HTMX
    page requests — the consistent "something is happening" feedback that
    makes navigation feel responsive instead of dead-timed. Created and
    driven by initProgressBar() in urus-ui.js (htmx:beforeRequest → trickle,
    htmx:afterRequest → complete + fade), with a ~120ms delay-to-show so
    fast swaps stay flicker-free.

    Replaces the old `.htmx-request #wrapper::before` bar, which only matched
    when the *requesting element* was #wrapper or an ancestor — so sidebar /
    subnav / cross-app (#body) navigation never lit it (the "inconsistent
    loading" complaint). The JS version fires for every navigation regardless
    of which element triggered it.
    ═══════════════════════════════════════════════════════════════════ */
#htmx-progress {
    position: fixed;
    top: 0;
    left: 0;
    width: 0;
    height: 3px;
    /* Orange (not brand teal): the bar overlays the teal sidebar at the top-left,
       so a teal bar would be invisible there. Orange contrasts with both the dark
       teal sidebar and the light content area. One-line knob — swap for
       var(--bs-danger) for a literal YouTube-red, or any --bs-* color. */
    background: var(--bs-orange, #fd7e14);
    box-shadow: 0 0 8px var(--bs-orange, #fd7e14);
    z-index: 9999;
    opacity: 0;
    pointer-events: none;
    transition: width 0.2s ease;
}
#htmx-progress.htmx-progress-active {
    opacity: 1;
}
#htmx-progress.htmx-progress-done {
    opacity: 0;
    transition: width 0.2s ease, opacity 0.3s ease 0.1s;
}

/* Row fade-out for HTMX inline delete (swap:500ms delay) */
tr.htmx-swapping td {
    opacity: 0;
    transition: opacity 500ms ease-out;
}

/* ═══════════════════════════════════════════════════════════════════
    MAPLIBRE THEME SUPPORT
    Uses Bootstrap CSS variables to respect data-bs-theme (dark/light).
    MapLibre's vendor CSS hard-codes white backgrounds; override here.
    ═══════════════════════════════════════════════════════════════════ */

/* Control wrappers (zoom, layers, attribution, custom IControls).
 * overflow: hidden so child buttons clip to the container's border-radius —
 * MapLibre's vendor CSS hard-codes the per-button radius at 4px while
 * Bootstrap's --bs-border-radius is 6px, so without clipping the corners
 * would show a 2px artifact line at top/bottom.
 */
.maplibregl-ctrl-group {
    background-color: var(--bs-body-bg) !important;
    border: 1px solid var(--bs-border-color) !important;
    border-radius: var(--bs-border-radius) !important;
    box-shadow: var(--bs-box-shadow-sm) !important;
    overflow: hidden !important;
}
.maplibregl-ctrl-group button {
    background-color: var(--bs-body-bg) !important;
    color: var(--bs-body-color) !important;
}
.maplibregl-ctrl-group button:hover {
    background-color: var(--bs-secondary-bg) !important;
}
.maplibregl-ctrl-group button + button {
    border-top: 1px solid var(--bs-border-color) !important;
}

/*
 * Control icon contrast in URUS dark mode.
 * MapLibre's vendor CSS swaps the inline-SVG fill via @media (prefers-
 * color-scheme), which follows the OS, not URUS's data-bs-theme. When
 * the OS is light but the app is dark, default-fill #333 icons render
 * invisible on the dark button background. Re-deliver the white-fill
 * SVGs whenever data-bs-theme="dark" so the URUS theme drives the icon.
 */
[data-bs-theme="dark"] .maplibregl-ctrl button.maplibregl-ctrl-zoom-in .maplibregl-ctrl-icon {
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='29' height='29' fill='%23fff' viewBox='0 0 29 29'%3E%3Cpath d='M14.5 8.5c-.75 0-1.5.75-1.5 1.5v3h-3c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h3v3c0 .75.75 1.5 1.5 1.5S16 19.75 16 19v-3h3c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-3v-3c0-.75-.75-1.5-1.5-1.5'/%3E%3C/svg%3E") !important;
}
[data-bs-theme="dark"] .maplibregl-ctrl button.maplibregl-ctrl-zoom-out .maplibregl-ctrl-icon {
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='29' height='29' fill='%23fff' viewBox='0 0 29 29'%3E%3Cpath d='M10 13c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h9c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13z'/%3E%3C/svg%3E") !important;
}
[data-bs-theme="dark"] .maplibregl-ctrl button.maplibregl-ctrl-fullscreen .maplibregl-ctrl-icon {
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='29' height='29' fill='%23fff' viewBox='0 0 29 29'%3E%3Cpath d='M24 16v5.5c0 1.75-.75 2.5-2.5 2.5H16v-1l3-1.5-4-5.5 1-1 5.5 4 1.5-3zM6 16l1.5 3 5.5-4 1 1-4 5.5 3 1.5v1H7.5C5.75 24 5 23.25 5 21.5V16zm7-11v1l-3 1.5 4 5.5-1 1-5.5-4L6 13H5V7.5C5 5.75 5.75 5 7.5 5zm11 2.5c0-1.75-.75-2.5-2.5-2.5H16v1l3 1.5-4 5.5 1 1 5.5-4 1.5 3h1z'/%3E%3C/svg%3E") !important;
}

/* Compact attribution chip — keep visible (zero-CDN attribution requirement) */
.maplibregl-ctrl-attrib {
    background-color: var(--bs-body-bg) !important;
    color: var(--bs-body-color) !important;
    font-size: var(--urus-font-size-s);
}
.maplibregl-ctrl-attrib a {
    color: var(--bs-link-color) !important;
}
.maplibregl-ctrl-attrib-button {
    background-color: var(--bs-body-bg) !important;
}

/* Popups */
.maplibregl-popup-content {
    background-color: var(--bs-body-bg) !important;
    color: var(--bs-body-color) !important;
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
    box-shadow: var(--bs-box-shadow);
}
.maplibregl-popup-tip {
    border-top-color: var(--bs-body-bg) !important;
    border-bottom-color: var(--bs-body-bg) !important;
}
.maplibregl-popup-close-button {
    color: var(--bs-body-color) !important;
}

/* Map loading overlay */
.map-loading-overlay {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: var(--bs-body-bg);
    opacity: 0.9;
    z-index: var(--urus-map-z-index);
    display: flex;
    align-items: center;
    justify-content: center;
}
.map-loading-overlay.d-none {
    display: none !important;
}

/* ═══════════════════════════════════════════════════════════════════
    MAP CONTAINER UTILITIES
    ═══════════════════════════════════════════════════════════════════ */
.map-container {
    height: 50vh;
    min-height: 350px;
    width: 100%;
}
.map-container-lg {
    height: 60vh;
    min-height: 400px;
    width: 100%;
}
.map-container-sm {
    height: 300px;
    width: 100%;
}

/* Map inside card — match card's bottom border-radius to prevent corner clipping */
.card > .map-container:last-child,
.card > .map-container-lg:last-child,
.card > .map-container-sm:last-child {
    border-radius: 0 0 var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius);
}

/* ═══════════════════════════════════════════════════════════════════
    EXECUTIVE MAP
    ═══════════════════════════════════════════════════════════════════ */
.executive-map-container {
    position: relative;
}
#executive-map {
    height: 100%;
    width: 100%;
    flex: 1;
}
.legend-panel {
    z-index: var(--urus-map-z-index);
    width: var(--urus-legend-panel-width);
}

.asset-type-options {
    margin-left: 1.5rem;
    padding-left: 0.5rem;
    border-left: 2px solid var(--bs-border-color);
}

/* ═══════════════════════════════════════════════════════════════════
    PHOTO CAPTURE
    ═══════════════════════════════════════════════════════════════════ */
.photo-capture-container {
    border: 2px dashed var(--bs-border-color);
    border-radius: var(--bs-border-radius-lg);
    padding: 1rem;
    text-align: center;
    transition: border-color 0.15s, background-color 0.15s;
}
.photo-capture-container:hover {
    border-color: var(--bs-primary);
    background-color: rgba(var(--bs-primary-rgb), 0.05);
}
.photo-capture-container.drag-over {
    border-color: var(--bs-primary);
    background-color: rgba(var(--bs-primary-rgb), 0.1);
}
.photo-capture-container.has-photos {
    border-style: solid;
}
.photo-capture-buttons {
    display: flex;
    gap: 0.5rem;
    justify-content: center;
    margin-bottom: 0.75rem;
}
.photo-capture-preview {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-top: 0.75rem;
}
.photo-capture-item {
    position: relative;
    width: 80px;
    height: 80px;
    border-radius: var(--bs-border-radius);
    overflow: hidden;
    background: var(--bs-body-bg);
    border: 1px solid var(--bs-border-color);
}
.photo-capture-item img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.photo-capture-item .remove-btn {
    position: absolute;
    top: 2px;
    right: 2px;
    width: 20px;
    height: 20px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: rgba(var(--bs-danger-rgb), 0.9);
    color: var(--bs-white);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--bs-body-font-size);
    line-height: 1;
}
.photo-capture-item .remove-btn:hover {
    background: var(--bs-danger);
}
.photo-capture-counter {
    font-size: var(--bs-body-font-size);
    color: var(--bs-secondary-color);
    margin-top: 0.5rem;
}
.camera-modal-video {
    width: 100%;
    max-height: 60vh;
    background: var(--bs-black);
    border-radius: var(--bs-border-radius);
}

/* ═══════════════════════════════════════════════════════════════════
    MAP PICKER
    ═══════════════════════════════════════════════════════════════════ */
.map-picker-controls {
    position: absolute;
    top: var(--urus-map-inset);
    right: var(--urus-map-inset);
    z-index: var(--urus-map-z-index);
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.map-picker-btn {
    width: 36px;
    height: 36px;
    background: var(--bs-body-bg);
    border: 2px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius-sm);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: var(--bs-box-shadow-sm);
}
.map-picker-btn:hover { background: var(--bs-secondary-bg); }
.map-picker-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.map-picker-btn svg { width: 20px; height: 20px; }
.map-picker-manual-input {
    position: absolute;
    bottom: var(--urus-map-inset);
    left: var(--urus-map-inset);
    right: var(--urus-map-inset);
    z-index: var(--urus-map-z-index);
    background: var(--bs-body-bg);
    border-radius: var(--bs-border-radius-sm);
    padding: 0.75rem;
    box-shadow: var(--bs-box-shadow);
    display: none;
}
.map-picker-manual-input.show { display: block; }
.map-picker-manual-input .row { display: flex; gap: 0.5rem; margin-bottom: 0.5rem; }
.map-picker-manual-input .col { flex: 1; }
.map-picker-manual-input input {
    width: 100%;
    padding: 0.5rem;
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius-sm);
    font-size: var(--bs-body-font-size);
}
.map-picker-manual-input label {
    display: block;
    font-size: var(--urus-font-size-s);
    color: var(--bs-secondary-color);
    margin-bottom: 0.25rem;
}
.map-picker-manual-input .btn-row {
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
}
.map-picker-coords {
    position: absolute;
    bottom: var(--urus-map-inset);
    left: var(--urus-map-inset);
    z-index: var(--urus-map-z-index);
    background: var(--bs-body-bg);
    padding: 0.5rem;
    border-radius: var(--bs-border-radius-sm);
    font-size: var(--urus-font-size-s);
    box-shadow: var(--bs-box-shadow-sm);
}

/* ═══════════════════════════════════════════════════════════════════
    WORK ORDER CREATE MAP
    ═══════════════════════════════════════════════════════════════════ */
#wo-map-container { position: relative; height: 600px; }
#wo-map { position: absolute; top: 0; left: 0; right: 0; bottom: 0; }
.map-toolbar { z-index: var(--urus-map-z-index); }
.manual-input-panel { display: none; }
.manual-input-panel.show { display: block; }
.coords-display { z-index: var(--urus-map-z-index); }
.nearby-loading { z-index: var(--urus-map-z-index); }
.assets-count-badge { z-index: var(--urus-map-z-index); }

/* Date inputs use native <input type="date"> — no JS datepicker library. */

/* ═══════════════════════════════════════════════════════════════════
    GLOBAL SEARCH — topbar search input + results dropdown
    ═══════════════════════════════════════════════════════════════════ */

/* --- Wrapper: positions the icon, spinner, and close button over the input --- */
.global-search-wrapper {
    width: 260px;
}

/* --- Input: padded left for icon, right for spinner --- */
.global-search-input {
    padding-left: 2.25rem !important;
    padding-right: 2rem !important;
    background-color: var(--bs-body-bg) !important;
}
/* Remove the browser-native clear (×) button on type="search" */
.global-search-input::-webkit-search-cancel-button {
    -webkit-appearance: none;
}

/* --- Magnifying glass icon (left) --- */
.global-search-icon {
    position: absolute;
    left: 0.75rem;
    top: 50%;
    transform: translateY(-50%);
    color: var(--bs-secondary-color);
    font-size: 0.875rem;
    pointer-events: none;
    z-index: 4;
}

/* --- HTMX spinner (right) — uses HTMX's native opacity toggle --- */
.global-search-spinner {
    position: absolute;
    right: 0.5rem;
    top: 50%;
    transform: translateY(-50%);
    z-index: 4;
}
.global-search-spinner .spinner-border {
    width: 0.75rem;
    height: 0.75rem;
}

/* --- Mobile close button (right, after spinner position) --- */
.global-search-close {
    position: absolute;
    right: 0.25rem;
    top: 50%;
    transform: translateY(-50%);
    z-index: 4;
    background: none;
    border: none;
    padding: 0.25rem;
    color: var(--bs-secondary-color);
    line-height: 1;
    cursor: pointer;
}
.global-search-close:hover {
    color: var(--bs-body-color);
}

/* --- Results dropdown --- */
.global-search-dropdown {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    margin-top: 0.25rem;
    background: var(--bs-body-bg);
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
    box-shadow: var(--bs-box-shadow-sm);
    max-height: 400px;
    overflow-y: auto;
    z-index: 1050;
    scrollbar-width: thin;
}

.global-search-group-header {
    font-size: 0.7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    padding: 0.5rem 0.75rem 0.25rem;
    color: var(--bs-secondary-color);
    border-bottom: 1px solid var(--bs-border-color-translucent);
}

.global-search-item {
    display: flex;
    flex-direction: column;
    padding: 0.5rem 0.75rem;
    text-decoration: none;
    color: var(--bs-body-color);
    border-bottom: 1px solid var(--bs-border-color-translucent);
}
.global-search-item:last-child {
    border-bottom: none;
}
.global-search-item:hover {
    background: var(--bs-tertiary-bg);
    color: var(--bs-body-color);
}

/* Overdue row indicator — subtle left border on work order list rows.
   Uses ::before pseudo-element to avoid shifting cell content (border-left
   adds 3px to the box model, misaligning with the header). */
.table tr.overdue-row > td:first-child {
    position: relative;
}
.table tr.overdue-row > td:first-child::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 2px;
    background: var(--bs-danger);
}

/* ── Hierarchy map two-way binding ── */
.hierarchy-row-active {
    background: var(--bs-tertiary-bg);
}
.hierarchy-marker-self {
    background: transparent;
}

/* ═══════════════════════════════════════════════════════════════════
    COLLAPSIBLE CARDS — <details> + <summary>
    Native replacement for Bootstrap Collapse. Used in:
    - workorder/workorder/create.html (#browse-by-location-details)
    - billing/invoice/work_records.html (#batch-add-details)
    ═══════════════════════════════════════════════════════════════════ */
details > summary {
    list-style: none;
    cursor: pointer;
}
details > summary::-webkit-details-marker {
    display: none;
}
/* Card-styled <details>: suppress card-header border-bottom when closed */
details.card:not([open]) > summary.card-header {
    border-bottom: 0;
}

/* ═══════════════════════════════════════════════════════════════════
    MODAL DIALOGS — <dialog class="urus-modal">
    Native <dialog> replaces Bootstrap Modal JS (backlog #107 phase 2).
    Outer <dialog> is transparent; Bootstrap's .modal-dialog /
    .modal-content / .modal-header / etc. classes still style the inner
    content. Browser handles focus trap, Escape, and top-layer stacking.

    Bootstrap's .modal-header/.modal-body/.modal-footer read --bs-modal-*
    custom properties that are ONLY defined inside `.modal`. Since we use
    <dialog>, we redeclare the same vars here so the inner padding /
    border / radius render correctly.
    ═══════════════════════════════════════════════════════════════════ */
dialog.urus-modal {
    padding: 0;
    border: 0;
    background: transparent;
    color: inherit;
    max-width: 100vw;
    max-height: 100vh;
    margin: auto;
    overflow: visible;

    --bs-modal-zindex: 1055;
    --bs-modal-width: 500px;
    --bs-modal-padding: 1rem;
    --bs-modal-margin: 0.5rem;
    --bs-modal-color: var(--bs-body-color);
    --bs-modal-bg: var(--bs-body-bg);
    --bs-modal-border-color: var(--bs-border-color-translucent);
    --bs-modal-border-width: var(--bs-border-width);
    --bs-modal-border-radius: var(--bs-border-radius-lg);
    --bs-modal-box-shadow: var(--bs-box-shadow-sm);
    --bs-modal-inner-border-radius: calc(var(--bs-border-radius-lg) - (var(--bs-border-width)));
    --bs-modal-header-padding-x: 1rem;
    --bs-modal-header-padding-y: 1rem;
    --bs-modal-header-padding: 1rem 1rem;
    --bs-modal-header-border-color: var(--bs-border-color);
    --bs-modal-header-border-width: var(--bs-border-width);
    --bs-modal-title-line-height: 1.5;
    --bs-modal-footer-gap: 0.5rem;
    --bs-modal-footer-bg: ;
    --bs-modal-footer-border-color: var(--bs-border-color);
    --bs-modal-footer-border-width: var(--bs-border-width);
}
dialog.urus-modal::backdrop {
    background: rgba(0, 0, 0, 0.5);
    backdrop-filter: blur(2px);
}

/* Mobile: modals go FULLSCREEN, the same breakpoint at which cards and alerts
   go edge-to-edge. Below md a dialog is not a card floating on the page — it
   IS the page, so it takes the page's treatment.

   Done here rather than with Bootstrap's per-dialog `.modal-fullscreen-md-down`
   opt-in class: that would need adding to all 12 dialogs and any 13th would
   silently miss it, which is exactly how the nine `px-3 px-md-0` band-aids
   happened. A rule on the shared class cannot drift. */
@media (max-width: 767.98px) {
    dialog.urus-modal {
        width: 100vw;
        max-width: none;
        /* 100vh fallback for engines without dvh, matching .urus-sidebar */
        height: 100vh;
        height: 100dvh;
        max-height: none;
        margin: 0;
    }
    dialog.urus-modal .modal-dialog {
        width: 100%;
        max-width: none;
        height: 100%;
        margin: 0;
    }
    dialog.urus-modal .modal-content {
        height: 100%;
        border: 0;
        border-radius: 0;
    }

    /* Most URUS modals wrap header/body/footer in a <form>, which is
       display:block — so Bootstrap's `.modal-body { flex: 1 1 auto }` never
       fires, the body does not grow, and the footer floats mid-screen with
       dead space beneath it. Make the wrapper part of the flex column. */
    dialog.urus-modal .modal-content > form {
        display: flex;
        flex-direction: column;
        flex: 1 1 auto;
        min-height: 0;
    }
    dialog.urus-modal .modal-body {
        overflow-y: auto;
        min-height: 0;
    }

    /* Surfaces inside a fullscreen modal break out of the MODAL BODY's padding
       exactly as they break out of the page gutter — same contract, different
       container, so each rule cancels its own container's padding rather than
       sharing a value that could drift. More specific than the page-level
       `.urus-content-gutter .alert`, so it wins for the 11 dialogs that sit
       inside the wrapper, and it stands alone for #modal, which does not. */
    dialog.urus-modal .modal-body > .alert,
    dialog.urus-modal .modal-body > .card {
        margin-left: calc(-1 * var(--bs-modal-padding));
        margin-right: calc(-1 * var(--bs-modal-padding));
        border-radius: 0;
        border-left-width: 0;
        border-right-width: 0;
    }
}

/* ═══════════════════════════════════════════════════════════════════
    INFO TIP — <c-info-tip> metric-definition hover icon (ⓘ).
    Surfaces scorecard / dashboard / report metric definitions inline.
    Slightly smaller than the header text so it reads as an adornment.
    ═══════════════════════════════════════════════════════════════════ */
.urus-info-tip {
    cursor: help;
    font-size: 0.8em;
}
