/* ═══════════════════════════════════════════════════════════════════
    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;
}

/* LT-001: an empty #filter-reset must not consume a flex gap (list-toolbar.md § 2) */
.d-contents {
    display: contents;
}

/* 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 — surfaces included.
   Cards and alerts keep the gutter, their radius and their borders at every
   width (the stock Bootstrap look).

   The mobile SURFACE BREAKOUT that used to live here (cards/alerts pulled to
   the screen edge below md via negative --urus-gutter margins, plus the
   nested-surface cancellations, the dialog guards and the #alert-container
   square-edge rules that existed only to serve it) was retired 2026-09-01 —
   server#203, owner-decided. Do not reintroduce any of it; the record is in
   .claude/rules/templates.md § Horizontal layout contract. */
.urus-content-gutter {
    --urus-gutter: 1rem;
    padding-left: var(--urus-gutter);
    padding-right: var(--urus-gutter);
}

@media (max-width: 767.98px) {
    /* 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;
    }
}

/* ═══════════════════════════════════════════════════════════════════
   THE FOUR-COLUMN GRID (docs/specs/ui-grid-convention.md, server#262)
   ═══════════════════════════════════════════════════════════════════
   Every page is laid out on ONE grid of four equal columns across the content
   area. A card states its width as `span` (1-4) on <c-card>; an element states
   its own on <c-field> — and a span is measured against the SCREEN, not against
   the card it sits in. The arithmetic happens HERE, never in a template.

   Two inherited custom properties do it, exactly the shape of Bootstrap's own
   `.col-*`:
     --urus-cols   how many columns the CONTAINER has: 4 for a <c-card-row>,
                   the card's own span for a <c-field-grid>.
     --urus-span   this item's width in those columns.
   <c-card>'s wrapper sets --urus-span; <c-field-grid> inherits it and re-states
   it as --urus-cols, so a span-1 field inside a span-3 card is one THIRD of that
   card with nothing passed twice and no page doing arithmetic.

   ⛔ The rows are Bootstrap's own `.row` with Bootstrap gutters — do not zero
   `.row` margins or `.row > *` padding (§ Horizontal layout contract above). */

.urus-col {
    flex: 0 0 auto;
    width: calc(100% * var(--urus-span, 4) / var(--urus-cols, 4));
}

/* Cards sharing a row share a bottom edge — the wrapper is stretched by the
   row, and the card fills it (keeping its own mb-4). */
.urus-card-col {
    display: flex;
    flex-direction: column;
}

.urus-card-col > .card {
    flex: 1 0 auto;
}

/* A card whose content IS one picture — the Operator's logo (owner-ruled
   2026-09-19, server#262): the header stays, and the picture fills the whole
   card body edge to edge. `cover`, centred: it is zoomed, proportions
   untouched, until no side shows empty space, and whatever falls outside the
   body is clipped. Same family as the map-in-a-card treatment
   (.claude/rules/templates.md § Maps in cards) — a single element owns the body,
   so the body's padding goes.

   ⚠ `cover` CROPS, which is only acceptable because the whole mark is one
   click away: the filling element is the lightbox ANCHOR, and clicking it opens
   the picture uncropped (docs/specs/ui-grid-convention.md § 6). The two halves
   are ONE ruling — do not keep `cover` here and drop the anchor there.
   ⚠ And the anchor, not the image, must be the body's DIRECT child: the
   `:has(> .urus-card-fill)` selectors below select on exactly that. */
.card-body:has(> .urus-card-fill) {
    position: relative;
    padding: 0;
    min-height: 8rem;
}

/* ⚠ ABSOLUTE, so the picture contributes NO height of its own. In flow it sized
   the body to the image's natural height at the card's width, which made the
   logo the TALLEST card in its row and left a hole under its sibling's fields.
   Out of flow, the row height still comes from the sibling and the picture fills
   whatever box that gives it. `min-height` is the floor for a card standing
   alone (tablet and phone, where the row collapses). */
.urus-card-fill {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* When the filling element is an ANCHOR, the picture inside it fills the
   anchor and takes the crop. `display: block` kills the inline baseline gap
   that would otherwise leave a sliver of card showing along the bottom edge. */
a.urus-card-fill {
    cursor: zoom-in;
    overflow: hidden;
}

.urus-card-fill > img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* The picture reaches the card's bottom corners, so the card must clip them —
   templates.md § Card `overflow-hidden`, applied by rule rather than by a class
   the page would have to remember. */
.card:has(> .card-body > .urus-card-fill) {
    overflow: hidden;
}

/* One <c-field-grid> is ONE ROW, and a card may hold several. Two adjacent
   Bootstrap `.row.g-3` butt together with NO gap, because the row's
   `margin-top: calc(-1 * gutter-y)` cancels its own children's
   `margin-top: gutter-y`. Dropping it on every row after the first leaves the
   children's margin standing as the gap — once, here, so no page writes a
   spacer div. */
.urus-field-grid + .urus-field-grid {
    margin-top: 0;
}

/* Tablet (md, 768-991px) — the grid keeps its rule and loses columns: 2.
   A span-1 item is half, anything wider is the whole row; inside a card the
   same mapping applies to the card's own column count. Rows stay FULL — an item
   that would end its wrapped row short GROWS to fill it, which is what
   flex-grow does with no per-page class. */
@media (max-width: 991.98px) {
    .urus-col {
        --urus-cols-md: min(var(--urus-cols, 4), 2);
        flex: 1 1 auto;
        width: calc(100% * min(var(--urus-span, 4), var(--urus-cols-md)) / var(--urus-cols-md));
    }
}

/* Phone (below md) — one column; everything is full width and stacks. */
@media (max-width: 767.98px) {
    .urus-col {
        flex: 0 0 auto;
        width: 100%;
    }
}

/* 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); }
.asset-finder-loading { z-index: var(--urus-map-z-index); }
.asset-finder-map-count { z-index: var(--urus-map-z-index); }

/* server#195 slice 2 — <c-asset-finder layout="split">, the modal host
   (docs/specs/shared/asset-finder.md § 3.1). Both columns share one height,
   sized like the tree picker's split so the two pickers read as one family;
   the list scrolls inside its column, the map fills its column.

   ⚠ Every box in the chain needs a DEFINITE height or the list never scrolls:
   a flex row's cross-axis stretch never shrinks a column below its content,
   and `min-height: 0` only bites under a definite parent. Measured
   2026-08-28: without `height: 100%` on the column, 1,829 rows made the
   column 108,025 px tall, the dialog overflowed the page and MapLibre clamped
   the canvas to 25 × 4096. */
.asset-finder-split { height: 70vh; min-height: 400px; flex-wrap: nowrap; }
.asset-finder-split .asset-finder-results-col { height: 100%; min-height: 0; }
.asset-finder-split .asset-finder-map-wrap { height: 100%; }
.asset-finder-split .asset-finder-results-flush { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.asset-finder-split .asset-finder-scroll { overflow-y: auto; min-height: 0; }
.asset-finder-split .asset-finder-map { position: absolute; inset: 0; }
/* Below md the dialog is fullscreen (modal.md § 4): the two columns stack as a
   real column — results first at up to 40vh, the map takes the rest. */
@media (max-width: 767.98px) {
    .asset-finder[data-layout="split"] { height: 100%; }
    .asset-finder-split { height: 100%; min-height: 0; flex-direction: column; }
    .asset-finder-split .asset-finder-results-col { height: auto; max-height: 40vh; flex: 0 0 auto; }
    .asset-finder-split .asset-finder-map-wrap { height: auto; flex: 1 1 auto; min-height: 240px; }
}
}

/* 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/claim/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);
}
/* MD-002 (docs/specs/shared/modal.md § 3, server#194): a native <dialog> is
   width: fit-content, so Bootstrap's max-width-only sizing let every modal
   shrink to its content and grow as rows loaded. Size by class, cap to the
   viewport; .modal-sm/-lg/-xl keep setting --bs-modal-width per breakpoint. */
@media (min-width: 576px) {
    dialog.urus-modal .modal-dialog {
        width: var(--bs-modal-width);
        max-width: calc(100vw - 2 * var(--bs-modal-margin));
    }
}
dialog.urus-modal::backdrop {
    background: rgba(0, 0, 0, 0.5);
    backdrop-filter: blur(2px);
}

/* server#70 (docs/specs/shared/modal.md §§ 4.1–4.2). Two rules that were
   mobile-only until 2026-08-30 and now hold at EVERY width, plus a desktop cap.

   5 of the 13 dialogs wrap header/body/footer in a <form> (display:block), so
   Bootstrap's `.modal-body { flex: 1 1 auto }` never fires: the body does not
   grow and the footer floats (fullscreen) or, once the box is capped, the
   block-level wrapper overflows it and the footer is unreachable (desktop).
   `:has(> .modal-body)` rather than `> form`, deliberately — it names the
   INTENT ("whatever wraps the body joins the column"), so a <div>/<fieldset>
   wrapper cannot silently regress the footer, and it does not match a form
   that lives inside the footer or the picker's own .modal-dialog. */
dialog.urus-modal .modal-content > :has(> .modal-body) {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}
dialog.urus-modal .modal-body {
    overflow-y: auto;
    min-height: 0;
}
/* Desktop: a modal is never taller than the viewport — the body scrolls,
   header and footer stay pinned. Before this, dialog.urus-modal capped itself
   at 100vh with overflow visible, so bulkCancelModal at 1280×400 put its
   buttons at 394..465 with nothing to scroll (measured 2026-07-31). On the
   shared class, never Bootstrap's per-dialog .modal-dialog-scrollable opt-in. */
@media (min-width: 768px) {
    dialog.urus-modal .modal-content {
        max-height: calc(100vh - 2 * var(--bs-modal-margin));
    }
}

/* Mobile: modals go FULLSCREEN below md. A dialog there is not a card
   floating on the page — it IS the page. (Untouched by server#203's surface
   retirement: the owner's ruling names the surfaces, not the modal.)

   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;
    }
    /* The body-wrapper flex rule and the scrolling body used to live here,
       mobile-only; since server#70 they hold at every width (see above).
       The modal-body surface breakout (.modal-body > .card/.alert cancelling
       --bs-modal-padding) also lived here — retired with the page-side
       breakout at server#203, 2026-09-01: a surface inside the body keeps the
       body's padding, exactly as a surface on the page keeps the gutter. */
}

/* ═══════════════════════════════════════════════════════════════════
    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;
}

/* ═══════════════════════════════════════════════════════════════════
   Work-order timeline strip — the dashboard legend above Operator queues
   (server#221, docs/specs/dashboard.md §6.5 "The timeline strip").
   An 18-column grid whose columns are the HALF-spans between nine dots:
   stop i occupies columns 2i+1..2i+2, so its dot centres on grid line
   2i+2; a bracket from dot i to dot j spans lines 2i+2 → 2j+2 through the
   .wo-tl-from-N / .wo-tl-to-N classes. Row 1 labels, row 2 the line and
   the dots (overlapping the same cells), rows 3–5 the brackets. Theme
   colours come from Bootstrap's variables; below its minimum width the
   strip scrolls sideways rather than wrapping (nine labels do not fit a
   phone, and a wrapped timeline is not a timeline).
   ═══════════════════════════════════════════════════════════════════ */
.wo-timeline {
    display: grid;
    grid-template-columns: repeat(18, minmax(0, 1fr));
    grid-template-rows: auto 1rem auto auto auto;
    row-gap: 0.4rem;
    min-width: 44rem;
}
.card-body:has(> .wo-timeline) {
    overflow-x: auto;
}
.wo-timeline-label {
    grid-row: 1;
    text-align: center;
    font-size: 0.8125rem;
    color: var(--bs-body-color);
    white-space: nowrap;
}
.wo-timeline-line {
    grid-column: 2 / 18;
    grid-row: 2;
    align-self: center;
    height: 3px;
    background: var(--bs-body-color);
    opacity: 0.6;
}
.wo-timeline-dot {
    grid-row: 2;
    justify-self: center;
    align-self: center;
    position: relative;
    z-index: 1;
    width: 0.9rem;
    height: 0.9rem;
    border-radius: 50%;
    border: 3px solid var(--bs-primary);
    background: var(--bs-body-bg);
}
.wo-timeline-label:nth-child(1), .wo-timeline-dot:nth-child(11) { grid-column: 1 / 3; }
.wo-timeline-label:nth-child(2), .wo-timeline-dot:nth-child(12) { grid-column: 3 / 5; }
.wo-timeline-label:nth-child(3), .wo-timeline-dot:nth-child(13) { grid-column: 5 / 7; }
.wo-timeline-label:nth-child(4), .wo-timeline-dot:nth-child(14) { grid-column: 7 / 9; }
.wo-timeline-label:nth-child(5), .wo-timeline-dot:nth-child(15) { grid-column: 9 / 11; }
.wo-timeline-label:nth-child(6), .wo-timeline-dot:nth-child(16) { grid-column: 11 / 13; }
.wo-timeline-label:nth-child(7), .wo-timeline-dot:nth-child(17) { grid-column: 13 / 15; }
.wo-timeline-label:nth-child(8), .wo-timeline-dot:nth-child(18) { grid-column: 15 / 17; }
.wo-timeline-label:nth-child(9), .wo-timeline-dot:nth-child(19) { grid-column: 17 / 19; }
.wo-timeline-bracket {
    position: relative;
    margin: 0 1px;
    padding-top: 0.3rem;
    border-top: 2px solid var(--bs-secondary-color);
    text-align: center;
    font-size: 0.75rem;
    color: var(--bs-body-color);
    white-space: nowrap;
}
.wo-timeline-bracket::before,
.wo-timeline-bracket::after {
    content: "";
    position: absolute;
    top: -5px;
    width: 2px;
    height: 8px;
    background: var(--bs-secondary-color);
}
.wo-timeline-bracket::before { left: 0; }
.wo-timeline-bracket::after { right: 0; }
.wo-tl-row-1 { grid-row: 3; }
.wo-tl-row-2 { grid-row: 4; }
.wo-tl-row-3 { grid-row: 5; }
.wo-tl-from-0 { grid-column-start: 2; }
.wo-tl-from-1 { grid-column-start: 4; }
.wo-tl-from-2 { grid-column-start: 6; }
.wo-tl-from-3 { grid-column-start: 8; }
.wo-tl-from-4 { grid-column-start: 10; }
.wo-tl-from-5 { grid-column-start: 12; }
.wo-tl-from-6 { grid-column-start: 14; }
.wo-tl-from-7 { grid-column-start: 16; }
.wo-tl-to-1 { grid-column-end: 4; }
.wo-tl-to-2 { grid-column-end: 6; }
.wo-tl-to-3 { grid-column-end: 8; }
.wo-tl-to-4 { grid-column-end: 10; }
.wo-tl-to-5 { grid-column-end: 12; }
.wo-tl-to-6 { grid-column-end: 14; }
.wo-tl-to-7 { grid-column-end: 16; }
.wo-tl-to-8 { grid-column-end: 18; }

/* server#226 — a reveal-under-a-switch with no JavaScript. The first
   `.form-check` inside `.urus-reveal` holds the switch; while it is unticked
   the `.urus-reveal-body` sibling is hidden. Used by the Water card on the
   work-scope form (docs/specs/nrw.md § The work-scope form). */
.urus-reveal:has(> .form-check > input:not(:checked)) > .urus-reveal-body {
    display: none;
}

/* ==========================================================================
   QR label — the Record card's code and the printable label sheet
   (docs/specs/entities/asset.md § QR label, server#227 item 3).
   The QR SVG carries a viewBox only, so the container sets its size.
   ========================================================================== */
.asset-qr-code {
    width: 8rem;
    max-width: 100%;
}
.asset-qr-code .urus-qr,
.asset-label-qr .urus-qr {
    display: block;
    width: 100%;
    height: auto;
}

.asset-label-sheet {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
    gap: 0.75rem;
}
.asset-label {
    padding: 0.75rem;
    break-inside: avoid;
    page-break-inside: avoid;
}
.asset-label-images {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
}
.asset-label-qr {
    flex: 0 0 6.5rem;
    width: 6.5rem;
}
.asset-label-cover {
    flex: 1 1 auto;
    min-width: 0;
    height: 6.5rem;
    object-fit: cover;
    border-radius: var(--bs-border-radius-sm);
}
.asset-label-text {
    margin-top: 0.5rem;
    line-height: 1.25;
}
.asset-label-code {
    font-family: var(--bs-font-monospace);
    font-weight: 600;
    font-size: 1.05rem;
    letter-spacing: 0.02em;
    overflow-wrap: anywhere;
}
.asset-label-name {
    font-size: 0.9rem;
}
.asset-label-type {
    font-size: 0.8rem;
    color: var(--bs-secondary-color);
}

/* What prints is the sheet alone: the chrome goes, the grid becomes a fixed
   four-across on A4 with the labels' borders drawn for cutting. */
@media print {
    @page {
        size: A4;
        margin: 10mm;
    }
    #sidebar,
    #sidebar-backdrop,
    #topbar,
    #alert-container,
    .urus-offcanvas,
    main > footer {
        display: none !important;
    }
    /* The app shell is a viewport-high, overflow-hidden flex column (body
       `vh-100 overflow-hidden` → .urus-content-wrapper → -area → -scroll →
       #main), so a printer would get exactly ONE page — the first viewport
       — and nothing else. Measured 2026-09-09: 182 labels printed as 20.
       Every level of the shell becomes a plain block for print. */
    html,
    body,
    .urus-content-wrapper,
    .urus-content-area,
    .urus-content-scroll,
    #main {
        display: block !important;
        overflow: visible !important;
        height: auto !important;
        min-height: 0 !important;
        background: #fff !important;
    }
    .urus-content-gutter {
        padding: 0 !important;
    }
    .asset-label-sheet {
        grid-template-columns: repeat(4, 1fr);
        gap: 4mm;
    }
    .asset-label {
        border: 0.3mm solid #000 !important;
        border-radius: 0 !important;
        padding: 3mm;
        color: #000 !important;
        background: #fff !important;
    }
    .asset-label-type {
        color: #000 !important;
    }
    /* A4 four-across leaves ~38 mm inside a label: the QR takes 22 mm (its
       ~37 modules print at ~0.6 mm each, comfortably scannable) so the cover
       keeps a readable width beside it instead of a sliver. */
    .asset-label-qr {
        flex-basis: 22mm;
        width: 22mm;
    }
    .asset-label-cover {
        height: 22mm;
    }
}
