/* ========== THEME VARIABLES ========== */
:root {
    /* Light theme (default) */
    --bg-primary: #f5f7fb;
    --bg-secondary: #eef1f8;
    --bg-tertiary: #e3e8f2;
    --bg-card: #ffffff;
    --bg-input: #ffffff;
    --bg-hover: #eef2fb;

    /* Grid rows. The band is a token of its own so the hover colours can be mixed from it without
       either theme having to restate them.

       Hover is that mix rather than one fixed colour, because a banded grid has two row colours and
       a single hover colour cannot differ from both - it lands clear of one band and on top of the
       other, so the highlight appears to work on only half the rows. Mixing the same ink into
       whichever colour the row already has gives the same step on both.

       A colour and not a translucent overlay: grid cells are background-color:inherit, which copies
       the row's colour and would paint straight over any layer laid on the row behind them. */
    --grid-row-band: var(--bg-secondary);
    --grid-row-veil-ink: #131826;
    --grid-row-veil-amount: 5.5%;

    --text-primary: #131826;
    --text-secondary: #5b6577;
    /* Muted text — darkened to meet WCAG 2.2 AA (4.5:1) on every surface incl. --bg-tertiary.
       (Was #8a93a6 = 2.7–3.1:1 which failed as body text.) */
    --text-muted: #616776;

    --border-color: #e0dfdf;
    --border-light: #eceff5;
    /* Interactive-boundary border — meets 1.4.11 (3:1) for input/control outlines that need a visible
       edge (the plain --border-color hairline is decorative separation between distinct surfaces). */
    --border-interactive: #767d8c;
    /* The .amx-field shell's resting edge, deliberately its own token rather than
       --border-interactive. Form controls want a stronger edge than the rest of the app, and
       tying them to a shared token means every panel, rail and control outline moves with them.
       A step darker than --border-interactive here; see the dark block for why it goes the other
       way there. */
    --field-border: #5f6673;
    /* The same field-only edge in view mode: quieter than --field-border, because nothing on a
       view-mode form is editable and a page of input-looking boxes would say otherwise - but
       still well clear of --border-color, which is the app's decorative hairline between
       surfaces and is far too faint to bound a control. */
    --field-border-view: #aeb3bd;
    /* The field's own surfaces. Pages re-point --bg-card/--bg-secondary/--text-primary to their own
       palette, and a page without a dark override left a view-mode or read-only field light on a
       dark page. Fields read these instead, which no page redefines. */
    --field-bg-view: #ffffff;
    --field-bg-readonly: #eef1f8;
    --field-text: #131826;
    --field-text-readonly: #5b6577;

    --shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.05);
    --shadow-md: 0 6px 16px rgba(16, 24, 40, 0.08);
    --shadow-lg: 0 18px 40px rgba(16, 24, 40, 0.12);

    /* The map's controls - the toolbar, its menus and the navigation cluster (mapToolbar.js) -
       are drawn on the app's own surface tokens. These are the state colours, which are not
       re-pointed for dark: "on" and "stop" mean the same thing in both themes, and a control that
       changed colour with the theme would be saying something it does not mean. */
    --map-control-on: #198754;
    --map-control-stop: #dc3545;
    --map-control-state-fg: #ffffff;
    /* The map's bottom bar (the dock in mapToolbar.js): its height, the height of a tool on it, and
       how far down anything placed at the top of the map starts - the map's own 8px inset, since
       nothing sits across the top any more. mapToolbar.js publishes the real values on the map
       element (--map-toolbar-offset, --map-bottom-offset); these are what applies before it has. */
    --map-dock-height: 56px;
    --map-toolbar-height: 42px;
    --map-toolbar-offset: 8px;

    --accent-primary: #2563eb;
    --accent-primary-rgb: 37, 99, 235;
    --accent-success: #16a34a;
    --accent-warning: #f59e0b;
    --accent-danger: #ef4444;
    /* Accessible TEXT variants of the status/link accents (the vivid --accent-* above are fill/icon
       colours that fail as body text). Cleared ≥ 4.5:1 on every light surface incl. --bg-tertiary.
       Use these whenever an accent is rendered as TEXT (links, status messages, inline emphasis). */
    --accent-warning-text: #a4490a;
    --accent-danger-text: #c4201f;
    --accent-success-text: #116a33;
    --accent-link: #1d4ed8; /* accessible link/primary text (the vivid --accent-primary #2563eb = 4.2:1 fails as body text) */
    /* Solid accent FILLS that carry WHITE text (buttons/badges). The vivid --accent-success #16a34a
       (3.3:1) and --accent-danger #ef4444 (3.76:1) fail with white text — use these darker fills. */
    --accent-success-fill: #15803d;
    --accent-danger-fill: #dc2626;
    /* The yellow-amber fill (.dbd-warning's, a touch yellower than the Archive ink) for a cautionary
       button. Bright in both themes, so it always carries --text-on-bright (10:1), never white. */
    --accent-warning-fill: #f5b312;
    /* The primary blue as a fill under white text (5.17:1). The same value as --accent-primary here,
       but kept as its own token because dark mode brightens --accent-primary to #3b82f6, which
       carries white at only 3.68:1 - a filled control reads this instead (the top bar's Go Home). */
    --accent-primary-fill: #2563eb;

    /* Ink for text sitting ON a bright fill (an amber button, a green chip). Deliberately NOT
       re-pointed in dark mode: the fill stays bright in both themes, so the text on it must stay
       dark — --text-primary would flip to near-white and drop the pair below AA. */
    --text-on-bright: #1a1a2e;
    /* The other half of that pair, for a fill dark enough that dark ink would disappear into it.
       Also NOT re-pointed in dark mode, and for the same reason: which of the two a fill needs is
       decided by the fill, not by the theme the page happens to be in. Used where the fill colour
       is data rather than design — report row colours, chart bands — and is picked between by
       ColoursHelpers.UseDarkTextColours. */
    --text-on-dark: #f7f8fb;

    /* AMX Blue: the fill of everything that offers Ask AMX (the record header's button; AmxChat keeps
       its own copy for the panel). The same in both themes - white on it is 6.4:1. */
    --amx-blue: #3160B6;

    /* Rounding scale */
    --radius-sm: 8px;
    --radius-md: 12px;
    --radius-lg: 16px;

    /* Gradient icon-tile palette (shared across themes) */
    --tile-blue: linear-gradient(135deg, #3b82f6, #2563eb);
    --tile-purple: linear-gradient(135deg, #8b5cf6, #7c3aed);
    --tile-green: linear-gradient(135deg, #22c55e, #16a34a);
    --tile-amber: linear-gradient(135deg, #f59e0b, #d97706);
    --tile-red: linear-gradient(135deg, #f87171, #ef4444);
    --tile-slate: linear-gradient(135deg, #64748b, #475569);

    /* Soft accent wash used for hero banners / active states */
    --accent-wash: rgba(37, 99, 235, 0.10);

    /* Diagram line colours (the Data Model page colours the lines into its three busiest entities).
       Validated together as a categorical set against --bg-secondary: distinguishable for every pair
       under colour-blindness, and at least 3:1 against the background in each theme. Change them
       together and re-validate. */
    --diagram-line-1: #256abf;
    --diagram-line-2: #c94f1a;
    --diagram-line-3: #0f855a;
}

:root {
    /* ── Shared aliases ───────────────────────────────────────────────
       Older components (the report search box, grid, colour picker, location
       control, designer) are written against these names. They live here on
       :root rather than in a layout so every layout gets them - defined only in
       AuthMainLayout, they were undefined throughout the settings area, where
       the components using them fell back to unstyled borders and lost their
       focus colours. */
    --amx-primary: var(--accent-primary);
    --amx-primary-rgb: var(--accent-primary-rgb);
    --amx-bg: var(--bg-primary);
    --amx-panel: var(--bg-card);
    --amx-border: var(--border-color);
    --amx-text: var(--text-primary);
    --amx-muted: var(--text-secondary);
    --amx-shadow: var(--shadow-sm);
    --amx-radius: 14px;

    /* Default control font size; used by DataView controls */
    --amx-control-font-size: 0.92rem;
}

[data-font-size="large"] {
    --amx-control-font-size: calc(0.92rem * 1.125);
}

[data-theme="dark"] {
    /* Dark theme — deep navy, matches AMX Companion */
    --bg-primary: #0a0e1a;
    --bg-secondary: #0f1524;
    --bg-tertiary: #1a2136;
    --bg-card: #111726;
    --bg-input: #161d30;
    --bg-hover: #1a2338;

    /* The dark counterpart. The ink has to be lighter than the rows, not darker, and the band is
       --bg-tertiary here. This is what --bg-hover could not do: it sits 2/255 from that band
       (#1a2338 against #1a2136), so hovering a banded row changed nothing visible. */
    --grid-row-band: var(--bg-tertiary);
    --grid-row-veil-ink: #e6e9f2;
    --grid-row-veil-amount: 7%;

    --text-primary: #e6e9f2;
    --text-secondary: #98a2b8;
    /* Muted text — brightened to meet AA (4.5:1) on every dark surface incl. --bg-tertiary.
       (Was #5f6b82 = ~3.3:1 which failed as body text.) */
    --text-muted: #8e98ad;

    --border-color: #202840;
    --border-light: #1a2032;
    /* Interactive-boundary border for dark (≥ 3:1 on dark surfaces). */
    --border-interactive: #66729a;
    /* The same one step of extra definition as the light --field-border, which on a dark surface
       means LIGHTER, not darker: taking this toward black would sink the field edge into the card
       behind it and undo the very thing the token exists for. */
    --field-border: #7a86ad;
    /* Quieter than --field-border, but well above the near-invisible --border-color: on a dark
       card a #202840 edge reads as no edge at all. */
    --field-border-view: #3d4869;
    --field-bg-view: #111726;
    --field-bg-readonly: #0f1524;
    --field-text: #e6e9f2;
    --field-text-readonly: #98a2b8;

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow-md: 0 8px 20px rgba(0, 0, 0, 0.45);
    --shadow-lg: 0 20px 44px rgba(0, 0, 0, 0.55);


    --accent-primary: #3b82f6;
    --accent-primary-rgb: 59, 130, 246;
    --accent-success: #22c55e;
    --accent-warning: #f59e0b;
    --accent-danger: #f87171;
    /* On dark surfaces the vivid warning/danger/success already clear 4.5:1 as text; link needs a
       brighter blue than --accent-primary #3b82f6 (4.34:1). */
    --accent-warning-text: #f59e0b;
    --accent-danger-text: #f87171;
    --accent-success-text: #22c55e;
    --accent-link: #60a5fa;
    /* Solid fills carrying WHITE text on dark (the vivid --accent-success #22c55e / --accent-danger
       #f87171 give white text only ~2.3:1 — these darker fills clear 4.5:1 with white). */
    --accent-success-fill: #15803d;
    --accent-danger-fill: #dc2626;
    --accent-warning-fill: #f5b312;
    --accent-primary-fill: #2563eb;

    --accent-wash: rgba(59, 130, 246, 0.14);
    --diagram-line-1: #3987e5;
    --diagram-line-2: #d95926;
    --diagram-line-3: #199e70;
}

/* The customer's primary colour kept out of dark mode (Display settings > Brand Colours, "Use the
   primary colour in dark mode" off). Every element that publishes the colour (PrimaryColourStyle:
   .amx-shell, the record view's tab strips, the side panel headings) carries data-primary-dark="off"
   then, and in dark mode its fills take the dark theme's own blue instead - the fill filled controls
   use here, under --text-on-dark (4.9:1), darkened on hover so that only adds contrast. !important
   because the colour itself arrives as an inline style on the same element. */
[data-theme="dark"] [data-primary-dark="off"] {
    --app-primary: var(--accent-primary-fill) !important;
    --app-primary-ink: var(--text-on-dark) !important;
    --app-primary-hover: color-mix(in srgb, var(--accent-primary-fill) 88%, var(--text-on-bright)) !important;
    --tab-accent-primary: var(--accent-primary-fill) !important;
    --tab-accent-primary-ink: var(--text-on-dark) !important;
    --tab-accent-primary-hover: color-mix(in srgb, var(--accent-primary-fill) 88%, var(--text-on-bright)) !important;
}

/* Grid headers - DataGrid, InlineGridEditor, ChartDataGrid and the pivot table (AmxGrid): the band
   is the customer's primary colour in both themes, written in the ink published with it
   (PrimaryColourStyle on .data-grid-container, or on the pivot's .data-grid-wrapper), and in dark
   mode takes the standard fill above when Display settings keeps the colour out of dark mode.
   Without a published colour the band falls back to the theme's own header tones. !important: the
   grids' scoped stylesheets load after this one and set the band per theme; the header's buttons
   need it against the dark theme's button catch-all. A column being dropped on keeps its tint, and
   an active filter keeps its amber badge. */
.data-grid-container :is(.grid-header-row, .grid-header-cell:not(.col-drop-target)),
.data-grid-wrapper .data-grid-table > thead > tr > th {
    background-color: var(--app-primary, var(--bg-secondary)) !important;
    color: var(--app-primary-ink, var(--text-primary)) !important;
}

[data-theme="dark"] :is(.data-grid-container :is(.grid-header-row, .grid-header-cell:not(.col-drop-target)),
    .data-grid-wrapper .data-grid-table > thead > tr > th) {
    background-color: var(--app-primary, var(--bg-tertiary)) !important;
}

.data-grid-container .grid-header-cell-template :is(.header-text, .column-filters:not(.column-filters-active)) {
    color: var(--app-primary-ink, var(--text-primary)) !important;
}

.data-grid-container .grid-header-cell-template :is(.header-text, .column-filters, .column-filters-active):focus-visible {
    outline-color: var(--app-primary-ink, var(--border-focus, var(--accent-primary)));
}

/* Apply theme variables to common elements */
[data-theme="dark"] body {
    background-color: var(--bg-primary);
    color: var(--text-primary);
}

/* ============================================================
   MODERN UI KIT — reusable classes (AMX Companion look & feel)
   ============================================================ */

/* Surface card */
.amx-surface {
    background: var(--bg-card);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
}

/* Small uppercase section label with leading icon */
.amx-section-label {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-muted);
    margin: 0 0 0.85rem;
}

    .amx-section-label i {
        color: var(--accent-primary);
        opacity: 0.85;
    }

/* Gradient icon tile */
.amx-icon-tile {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: 12px;
    color: #fff;
    font-size: 1.05rem;
    flex: 0 0 auto;
    background: var(--tile-blue);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
}

    .amx-icon-tile.tile-lg { width: 56px; height: 56px; border-radius: 14px; font-size: 1.35rem; }
    .amx-icon-tile.blue   { background: var(--tile-blue); }
    .amx-icon-tile.purple { background: var(--tile-purple); }
    .amx-icon-tile.green  { background: var(--tile-green); }
    .amx-icon-tile.amber  { background: var(--tile-amber); }
    .amx-icon-tile.red    { background: var(--tile-red); }
    .amx-icon-tile.slate  { background: var(--tile-slate); }

/* Stat card */
.amx-stat-card {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    padding: 1.1rem 1.25rem;
    background: var(--bg-card);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    transition: border-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}

    .amx-stat-card:hover {
        border-color: rgba(var(--accent-primary-rgb), 0.5);
        transform: translateY(-2px);
        box-shadow: var(--shadow-md);
    }

    .amx-stat-card .amx-stat-value {
        font-size: 1.6rem;
        font-weight: 700;
        line-height: 1;
        color: var(--text-primary);
    }

    .amx-stat-card .amx-stat-label {
        font-size: 0.85rem;
        color: var(--text-secondary);
        margin-top: 0.15rem;
    }

/* Standalone checkbox / radio option, in a box of its own.

   For a lone option that would otherwise be a bare tick floating against the surface: it gives the
   choice an edge to sit in, and makes the whole box the target rather than just the tick and its
   words. Same shape as the save-report dialog's choice cards. Put it on the <label> and wrap the
   input, so no `for` is needed and the box reads as one control. */
.amx-check-option {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin: 0;
    padding: 0.55rem 0.7rem;
    background: var(--bg-secondary);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    cursor: pointer;
    transition: border-color 0.15s ease, background-color 0.15s ease;
}

.amx-check-option:hover {
    border-color: var(--border-interactive);
}

    .amx-check-option .form-check-input {
        flex: 0 0 auto;
        margin: 0;
    }

    .amx-check-option .amx-check-option-text {
        min-width: 0;
        font-size: 0.85rem;
        color: var(--text-primary);
    }

/* The whole box is the control, so the focus ring belongs to it and not to the tick alone. */
.amx-check-option:has(.form-check-input:focus-visible) {
    border-color: var(--accent-primary);
    box-shadow: 0 0 0 3px rgba(var(--accent-primary-rgb), 0.15);
}

/* Bootstrap's fixed pink falls below AA on subtle surfaces and in dark mode. */
code { color: var(--accent-link); }
.alert code { color: inherit; }

/* Named scroll regions must show where keyboard scrolling will happen. */
[role="region"][tabindex="0"]:focus-visible {
    outline: 2px solid var(--accent-success-text);
    outline-offset: -2px;
}

.amx-skip-link {
    position: fixed;
    top: 8px;
    left: 8px;
    z-index: 10000;
    transform: translateY(-200%);
    padding: 10px 16px;
    border: 2px solid var(--border-interactive);
    border-radius: var(--radius-sm);
    background: var(--bg-card);
    color: var(--text-primary);
    font-weight: 600;
}
.amx-skip-link:focus {
    transform: none;
    outline: 2px solid var(--accent-success-text);
    outline-offset: 2px;
    color: var(--text-primary);
}

/* Pill badge */
.amx-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.2rem 0.6rem;
    border-radius: 999px;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.03em;
    background: var(--accent-wash);
    color: var(--accent-link);
    border: 1px solid rgba(var(--accent-primary-rgb), 0.25);
}

    /* Pill text uses the accessible status-text tokens to clear 4.5:1 on the 14% wash (was 1.9:1 / 3.1:1). */
    .amx-pill.amber { background: rgba(245, 158, 11, 0.14); color: var(--accent-warning-text); border-color: rgba(245, 158, 11, 0.3); }
    .amx-pill.red   { background: rgba(239, 68, 68, 0.14); color: var(--accent-danger-text); border-color: rgba(239, 68, 68, 0.3); }
    .amx-pill.green { background: rgba(34, 197, 94, 0.14); color: var(--accent-success-text); border-color: rgba(34, 197, 94, 0.3); }

/* Hero banner */
.amx-hero {
    display: flex;
    align-items: center;
    gap: 1.25rem;
    padding: 1.6rem 1.8rem;
    border-radius: var(--radius-lg);
    border: 1px solid var(--border-color);
    background:
        radial-gradient(1200px 200px at 0% 0%, var(--accent-wash), transparent 60%),
        linear-gradient(135deg, var(--bg-card), var(--bg-secondary));
}

    .amx-hero .amx-hero-title {
        margin: 0;
        font-size: 1.7rem;
        font-weight: 700;
        color: var(--text-primary);
    }

    .amx-hero .amx-hero-sub {
        color: var(--text-secondary);
        margin-top: 0.35rem;
    }

/* Interactive quick-link / list card */
.amx-linkcard {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 1.1rem 1.25rem;
    background: var(--bg-card);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    text-decoration: none;
    color: var(--text-primary);
    transition: border-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}

    .amx-linkcard:hover {
        border-color: rgba(var(--accent-primary-rgb), 0.5);
        transform: translateY(-2px);
        box-shadow: var(--shadow-md);
        color: var(--text-primary);
    }

    .amx-linkcard .amx-linkcard-title { font-weight: 700; }
    .amx-linkcard .amx-linkcard-sub { font-size: 0.85rem; color: var(--text-secondary); }
    .amx-linkcard .amx-linkcard-arrow { margin-left: auto; color: var(--text-muted); }

/* Status pages — the shared shape for whole-page screens the app shows when it has to stop and
   explain itself (not found, unhandled error, no licence seat). One centred flat card so those
   moments have a single voice instead of three near-identical hand-rolled layouts. The page
   supplies the icon colour and anything bespoke below the actions; everything structural is here. */
.amx-status-page {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 80vh;
    padding: 2rem;
}

.amx-status-card {
    max-width: 34rem;
    text-align: center;
    background: var(--bg-card);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
    padding: 2.5rem 2rem;
}

    /* Decorative by contract — pages mark it aria-hidden and put the meaning in the title. */
    .amx-status-card .amx-status-icon {
        font-size: 2.5rem;
        color: var(--text-secondary);
        margin-bottom: 1rem;
    }

    .amx-status-card .amx-status-title {
        margin: 0 0 0.75rem;
        font-size: 1.375rem;
        font-weight: 600;
        color: var(--text-primary);
    }

    .amx-status-card .amx-status-message {
        margin: 0 0 0.75rem;
        font-size: 0.9375rem;
        line-height: 1.5;
        color: var(--text-primary);
    }

    .amx-status-card .amx-status-detail {
        margin: 0 0 1.5rem;
        font-size: 0.8125rem;
        color: var(--text-secondary);
    }

    /* Wraps to stacked full-width buttons on a narrow viewport. */
    .amx-status-card .amx-status-actions {
        display: flex;
        gap: 0.75rem;
        justify-content: center;
        flex-wrap: wrap;
    }

/* Visually hidden, still announced — for live regions on status pages. */
.amx-status-sr {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
}



[data-theme="dark"] .card,
[data-theme="dark"] .modal-content,
[data-theme="dark"] .dropdown-menu {
    background-color: var(--bg-card);
    border-color: var(--border-color);
    color: var(--text-primary);
}

/* Dark mode styles for tools dialog buttons */
[data-theme="dark"] .tools-item-btn {
    background-color: var(--bg-card) !important;
    color: var(--text-primary) !important;
    border-color: var(--border-color) !important;
}

[data-theme="dark"] .tools-item-btn:hover {
    background-color: var(--bg-hover) !important;
    border-color: var(--accent-primary) !important;
}

[data-theme="dark"] .tools-item-btn .tool-icon {
    color: var(--accent-primary) !important;
}

/* NOTE: always set `background-color` on `.form-select`, never the `background`
   shorthand — the shorthand resets background-image, which is where Bootstrap
   draws the dropdown chevron, so the arrow silently disappears. */
[data-theme="dark"] .form-control,
[data-theme="dark"] .form-select {
    background-color: var(--bg-input) !important;
    border-color: var(--border-color) !important;
    color: var(--text-primary) !important;
}

/* Bootstrap bakes a fixed dark-grey (#343a40) chevron into .form-select, which
   all but vanishes against the dark input surface. Same glyph, muted-on-dark. */
[data-theme="dark"] .form-select {
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%2398a2b8' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e") !important;
}

[data-theme="dark"] .form-control:focus,
[data-theme="dark"] .form-select:focus {
    background-color: var(--bg-input) !important;
    border-color: var(--accent-primary) !important;
    color: var(--text-primary) !important;
}

/* Bootstrap's input add-ons were never brought into the dark palette, so a prefix or suffix beside
   a themed input - the currency symbol on a money field, a search icon - stayed a near-white block
   against a dark form. */
[data-theme="dark"] .input-group-text {
    background-color: var(--bg-secondary);
    border-color: var(--border-color);
    color: var(--text-secondary);
}

[data-theme="dark"] .form-control:disabled,
[data-theme="dark"] .form-select:disabled {
    background-color: var(--bg-tertiary);
    color: var(--text-muted);
}

[data-theme="dark"] .table {
    --bs-table-bg: var(--bg-card);
    --bs-table-color: var(--text-primary);
    --bs-table-border-color: var(--border-color);
    --bs-table-striped-bg: var(--bg-secondary);
    --bs-table-hover-bg: var(--bg-hover);
}

/* Additional table dark mode styles */
[data-theme="dark"] table {
    background-color: var(--bg-card);
    color: var(--text-primary);
}

[data-theme="dark"] table thead th {
    background-color: var(--bg-secondary);
    color: var(--text-primary);
    border-color: var(--border-color) !important;
}

[data-theme="dark"] table tbody td {
    background-color: var(--bg-card);
    color: var(--text-primary);
    border-color: var(--border-color);
}

[data-theme="dark"] table tbody tr:nth-child(even) {
    background-color: var(--bg-secondary);
}

[data-theme="dark"] table tbody tr:hover {
    background-color: var(--bg-hover) !important;
    color: #ffffff !important;
}

[data-theme="dark"] table tbody tr:hover td {
    background-color: var(--bg-hover) !important;
    color: #ffffff !important;
}

/* Grid wrapper and container dark mode */
[data-theme="dark"] .amx-grid-wrapper {
    background-color: var(--bg-card);
    color: var(--text-primary);
}

[data-theme="dark"] .data-grid-container {
    background-color: var(--bg-card);
    border-color: var(--border-color);
}

[data-theme="dark"] .data-grid-header {
    background-color: var(--bg-secondary);
    border-color: var(--border-color) !important;
}

[data-theme="dark"] .data-grid-content {
    background-color: var(--bg-card);
}

[data-theme="dark"] .table-container {
    background-color: var(--bg-card);
}

/* Inline grid editor layout.

   The inline grid editor renders the same class names as the report grid (DataGrid) and shares its
   header markup, but DataGrid's styling lives in a scoped stylesheet that does not reach here - so
   these rules are the inline editor's copy of it. They are deliberately kept identical to
   Components/Grid/DataGrid.razor.css so the two grids look the same side by side on a form; keep
   them in step. Form grids use a wrapping title/action row below; standalone report grids keep
   DataGrid's stacked title. */
.amx-grid-wrapper .data-grid-container {
    display: flex;
    flex-direction: column;
    height: 100%;
    overflow: hidden;
    background-color: var(--bg-card, #ffffff);
    border-radius: 8px;
    position: relative;
}

/* Let the title, primary actions and utilities share a row when they fit. Wrap on the grid's
   available width (including narrow form columns), rather than squeezing away the title. */
.amx-grid-wrapper .data-grid-container > .data-grid-header {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: center;
    padding: 0.5rem 0.75rem;
    background-color: var(--bg-card, #ffffff);
    border-bottom: 1px solid var(--border-color, #e5e7eb);
    min-width: 0;
}

.amx-grid-wrapper .data-grid-header .header-controls {
    /* This neutral wrapper carries no semantics. Its groups participate in the header's flex
       row so actions stay beside the title and utilities can wrap independently. */
    display: contents;
}

.amx-grid-wrapper .data-grid-header > span {
    display: flex;
    align-items: center;
    min-width: 0;
    max-width: 100%;
    overflow-wrap: anywhere;
}

.amx-grid-wrapper .data-grid-header .data-view-label {
    width: auto;
    min-width: 0;
    max-width: 100%;
    margin: 0;
    padding: 0 !important;
    overflow-wrap: anywhere;
}

/* Left group: the general tools (add, refresh, and whatever the entity contributes). */
.amx-grid-wrapper .main-toolbar-items {
    display: flex;
    align-items: center;
    min-width: 0;
    max-width: 100%;
}

.amx-grid-wrapper .main-toolbar-items .custom-toolbar-items {
    flex-wrap: wrap;
    height: auto !important;
    overflow: visible !important;
}

.amx-grid-wrapper .main-toolbar-items .btn {
    padding: 0.6rem 0.6rem;
    font-size: 0.8rem;
    font-weight: 500;
    line-height: 1.2;
    border-radius: var(--radius-sm);
    box-shadow: none;
    gap: 0.3rem;
}

.amx-grid-wrapper .main-toolbar-items .btn:hover {
    transform: none;
    box-shadow: var(--shadow-sm);
}

.amx-grid-wrapper .main-toolbar-items .btn i {
    font-size: 0.8rem;
}

/* Right group: the tools that narrow the data, then the record count. */
.amx-grid-wrapper .data-grid-header .selectors-wrapper {
    margin-left: auto;
    display: flex;
    gap: 0.5rem;
    align-items: center;
    justify-content: flex-end;
    flex-wrap: wrap;
    min-width: 0;
    max-width: 100%;
}

/* One fixed height for the search/filter track rather than letting whatever is inside size it.
   30px content + 2px padding + 1px border each side = 36. */
.amx-grid-wrapper .toolbar-btn-group {
    display: flex;
    align-items: center;
    height: 36px;
    background-color: var(--bg-secondary, #f1f5f9);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    padding: 2px;
    gap: 2px;
}

/* CustomToolbarComponent's own wrapper is "d-flex w-100 h-100 gap-2", which fights the track: 8px
   between its buttons against the track's 2px, and a 100% width that leaves its slack as a wider
   gap in front of whatever follows it. In here it is a pass-through - track gap, sized to its
   buttons - so every gap in the track matches. Bootstrap's utilities are !important, so these are
   too. Mirrors the scoped rule in Components/Grid/DataGrid.razor.css. */
.amx-grid-wrapper .toolbar-btn-group .custom-toolbar-items {
    gap: 2px !important;
    width: auto !important;
    overflow: visible !important;
}

/* Buttons inside the track are flat until hovered - the track is the frame, not each button. */
.amx-grid-wrapper .toolbar-btn-group .btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    height: 100%;
    padding: 0 10px;
    min-width: 34px;
    margin: 0 !important;
    border: none;
    border-radius: 6px;
    background: transparent;
    color: var(--text-primary);
    font-size: 0.82rem;
    font-weight: 500;
    line-height: 1;
    white-space: nowrap;
    box-shadow: none;
    filter: none;
    cursor: pointer;
    transition: background-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
}

.amx-grid-wrapper .toolbar-btn-group .btn:hover,
.amx-grid-wrapper .toolbar-btn-group .btn:active {
    background-color: var(--bg-card, #ffffff);
    color: var(--text-primary, #475569);
    /* .dbd-btn:hover:not(:disabled) dims every button, and the .dbd-* fills paint themselves back
       in while the pointer is down. Neither belongs in a flat track. */
    filter: none;
}

.amx-grid-wrapper .toolbar-btn-group .btn:focus-visible {
    outline: 2px solid var(--accent-primary);
    outline-offset: -1px;
}

/* An active filter is a "this one is on" state, kept amber because it is a warning that the grid
   is not showing everything. */
.amx-grid-wrapper .toolbar-btn-group .dbd-warning {
    background-color: var(--bg-card, #ffffff);
    color: var(--accent-warning-text);
    box-shadow: var(--shadow-sm);
}

/* Hover must not take the amber off again: the generic .btn:hover above is the same weight, so
   this has to come after it. */
.amx-grid-wrapper .toolbar-btn-group .dbd-warning:hover {
    background-color: rgba(245, 158, 11, 0.2);
    color: var(--accent-warning-text);
    box-shadow: var(--shadow-sm);
    filter: none;
}

/* Clear search: an amber wash, translucent so it works over both the light and dark card. */
.amx-grid-wrapper .toolbar-btn-group .grid-clear-search-btn {
    box-sizing: border-box;
    background-color: rgba(245, 158, 11, 0.12);
    border: 1px solid var(--accent-warning-text);
    color: var(--accent-warning-text);
    font-weight: 600;
    box-shadow: none;
}

.amx-grid-wrapper .toolbar-btn-group .grid-clear-search-btn:hover {
    /* Deepen the wash only - width, padding and border stay put so nothing shifts. */
    background-color: rgba(245, 158, 11, 0.2);
    color: var(--accent-warning-text);
    box-shadow: var(--shadow-sm);
    /* .dbd-btn:hover:not(:disabled) dims every button; this one carries its own hover. */
    filter: none;
}

.amx-grid-wrapper .toolbar-btn-group .grid-clear-search-btn:active {
    background-color: rgba(245, 158, 11, 0.28);
}

.amx-grid-wrapper .toolbar-btn-group .grid-clear-search-btn:focus-visible {
    outline: 2px solid var(--accent-warning-text);
    outline-offset: 2px;
}

.amx-grid-wrapper .report-total-count {
    font-size: 0.78rem;
    text-wrap: nowrap;
    color: var(--text-primary);
    font-weight: 600;
    background: var(--bg-secondary, #f3f4f6);
    border-radius: 999px;
    padding: 0.25rem 0.7rem;
    border: 1px solid var(--border-color, #eee);
}

.amx-grid-wrapper .data-grid-content {
    flex: 1;
    overflow: auto;
    background-color: var(--bg-card, #ffffff);
    padding: 10px;
}

.amx-grid-wrapper .table-container {
    height: 100%;
    width: 100%;
    overflow: auto;
    display: flex;
    flex-direction: column;
    border-radius: var(--radius-sm);
    scrollbar-gutter: auto;
}

.amx-grid-wrapper .actions-col {
    width: 70px !important;
    text-align: center;
}

.amx-grid-wrapper .actions-cell {
    text-align: center;
}

.amx-grid-wrapper .actions-cell-div {
    display: flex;
    gap: 8px;
    justify-content: center;
    align-items: center;
}

/* Icon-only variant of the app's buttons: a fixed square so the delete and open buttons line up
   down their 50px columns whatever glyph they carry, instead of each sizing to its own icon. */
.amx-grid-wrapper .grid-action-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 30px;
    height: 30px;
    padding: 0;
    line-height: 1;
}

/* top: -1px and the compositing hints, as in DataGrid: at top: 0 a sub-pixel scroll offset lets a
   hairline of the first row show above the sticky header. */
.amx-grid-wrapper .grid-header-row {
    display: grid;
    position: var(--amx-header-position, sticky);
    top: -1px;
    z-index: 10;
    flex: 0 0 auto;
    background-color: var(--bg-secondary, #f9fafb);
    background-clip: padding-box;
    border-bottom: 1px solid var(--border-color, #e5e7eb);
    color: var(--text-primary, inherit);
    will-change: transform;
    transform: translateZ(0);
    backface-visibility: hidden;
}

.amx-grid-wrapper .grid-header-cell {
    padding: 4px 8px;
    text-align: left;
    font-size: 0.875rem;
    font-weight: 600;
    /* Paints the header row's colour rather than letting it show through - see .grid-row. */
    background-color: inherit;
    overflow: hidden;
    text-overflow: ellipsis;
}

.amx-grid-wrapper .grid-header-cell:not(:first-child) {
    border-left: 1px solid color-mix(in srgb, currentColor 12%, transparent);
}

/* The header cell markup comes from DataGridRenderHelper, shared with the report grid - but the
   report grid's styling for it lives in its own scoped stylesheet (DataGrid.razor.css), which does
   not reach here. Without this copy the sort control renders as a raw UA <button>: grey box,
   border, centred small text, nothing like the column title next to it. Mirrors the DataGrid
   rules, so keep the two in step. */
.amx-grid-wrapper .grid-header-cell-template {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    align-content: end;
    width: 100%;
    height: 100%;
}

/* The title is a <button> so sorting is not mouse-only. Strip the UA button chrome so it reads as
   the column heading it is, and give it a visible focus ring since it is in the tab order. */
.amx-grid-wrapper .grid-header-cell-template .header-text {
    grid-area: 1 / 1;
    align-self: end;
    width: 100%;
    overflow: visible;
    /* break-word only where a single word genuinely cannot fit, so short titles are not shredded
       the moment the column is narrow. */
    overflow-wrap: break-word;
    white-space: normal;
    cursor: pointer;
    appearance: none;
    background: none;
    border: 0;
    margin: 0;
    padding: 0;
    font: inherit;
    font-size: calc(1em * var(--amx-header-text-scale, 1));
    line-height: 1.3;
    color: inherit;
    text-align: inherit;
}

/* Keep header controls reachable when the text is compacted or the app UI is scaled. */
.amx-grid-wrapper .grid-header-cell-template button {
    min-height: calc(24px / var(--amx-ui-scale, 1));
    min-width: calc(24px / var(--amx-ui-scale, 1));
}

.amx-grid-wrapper .grid-header-cell-template .header-text:focus-visible {
    outline: 2px solid var(--border-focus, var(--accent-primary));
    outline-offset: 2px;
    border-radius: 3px;
}

.amx-grid-wrapper .grid-header-cell-template .header-text > i {
    padding-right: 5px;
}

/* The sort arrow sits at the right of the title's last line, over the room the invisible spacer at
   the end of the title holds for it - so it only takes a line of its own when that line is full.
   See the DataGrid rules for the whole of it. */
.amx-grid-wrapper .grid-header-cell-template .grid-header-actions {
    grid-area: 1 / 1;
    justify-self: end;
    align-self: end;
    display: flex;
    align-items: center;
    gap: 4px;
    line-height: 1;
}

.amx-grid-wrapper .grid-header-cell-template .grid-header-actions-spacer {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-left: 4px;
    vertical-align: middle;
    visibility: hidden;
    font-size: calc(1em / var(--amx-header-text-scale, 1));
    line-height: 1;
}

.amx-grid-wrapper .grid-header-cell-template :is(.grid-header-actions, .grid-header-actions-spacer) > * {
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.amx-grid-wrapper .grid-header-cell-template .grid-header-actions-spacer > :is(.column-filters, .column-filters-active) {
    min-height: calc(24px / var(--amx-ui-scale, 1));
    min-width: calc(24px / var(--amx-ui-scale, 1));
}

.amx-grid-wrapper .grid-header-cell-template .grid-header-actions-spacer [data-text]::before {
    content: attr(data-text);
}

.amx-grid-wrapper .grid-header-cell-template .sort-indicator {
    flex-shrink: 0;
    font-size: 0.85em;
}

.amx-grid-wrapper .grid-body {
    overflow: visible;
    flex: 1 0 auto;
}

/* Row colours are painted by the cells, which inherit them from here.
   .table-container scrolls sideways and lays its children out in a column flex, so a row is
   stretched to the container's *visible* width - not to the width of its own columns. When the
   columns are wider than that, the cells overflow the row box but a background on the row is
   still only painted across that box, so the highlight stopped short of the right-hand columns
   and drifted further off the further the grid was scrolled. The cells tile the columns exactly
   and have no gaps between them, so painting them instead covers the whole row.
   Mirrors DataGrid.razor.css - keep the two in step. */
.amx-grid-wrapper .grid-row {
    display: grid;
    cursor: pointer;
    transition: background-color 0.15s ease;
    background-color: var(--bg-card, transparent);
    border-bottom: 1px solid transparent;
}

.amx-grid-wrapper .grid-row:hover {
    background-color: var(--bg-hover, #f9fafb);
}

.amx-grid-wrapper .grid-row.row-selected {
    background-color: var(--bg-tertiary, #dbeafe);
}

/* A selected row still has to answer "is the pointer on me?". The plain hover colour cannot -
   it is lighter than the selection tint, so it reads as the selection being lost. Deepen the
   selection tint with the accent instead. Mirrors DataGrid.razor.css - keep the two in step. */
.amx-grid-wrapper .grid-row.row-selected:hover,
.amx-grid-wrapper .grid-row.row-selected:focus-visible {
    background-color: color-mix(in srgb, var(--accent-primary, #2563eb) 14%, var(--bg-tertiary, #dbeafe));
}

.amx-grid-wrapper .grid-row.editing > .grid-cell {
    padding: 7px !important;
    background-color: rgba(102, 126, 234, 0.05);
}

.amx-grid-wrapper .grid-cell {
    padding: 1rem 0.5rem;
    font-size: 14px;
    color: var(--text-primary, #374151);
    overflow: hidden;
    text-overflow: ellipsis;
    /* Inherited, not transparent: see .grid-row. A cell with a colour band of its own sets it
       inline and still wins. */
    background-color: inherit;
    display: flex;
    align-items: center;
    min-height: 32px;
    line-height: 1.4;
    border-bottom: 1px solid color-mix(in srgb, var(--border-color) 55%, transparent);
}

.amx-grid-wrapper .grid-cell:not(:first-child) {
    border-left: 1px solid color-mix(in srgb, var(--border-color) 55%, transparent);
}

.amx-grid-wrapper .grid-cell.checkbox-cell,
.amx-grid-wrapper .grid-cell.status-cell {
    justify-content: center;
    color: #9ca3af;
}

.amx-grid-wrapper .grid-cell.editing .form-check {
    align-items: center;
    justify-content: center;
}

.amx-grid-wrapper .grid-cell .photo-cell-container {
    display: block;
    height: 100px;
    max-height: 70px;
    min-height: 54px;
    width: 100%;
    overflow: hidden;
}

.amx-grid-wrapper .grid-cell .text-span {
    display: block;
    width: 100%;
    overflow: hidden;
    white-space: break-spaces;
    overflow-wrap: break-word;
}

.grid-cell .grid-search-highlight {
    background-color: #f2f4b5;
    color: #666565;
    font-weight: 600;
    padding: 0 2px;
    border-radius: 3px;
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.15);
}

[data-theme="dark"] .data-table {
    background-color: var(--bg-card);
    color: var(--text-primary);
}

[data-theme="dark"] .data-table thead th {
    background-color: var(--bg-secondary);
    color: var(--text-primary);
    border-color: var(--border-color);
}

[data-theme="dark"] .data-table tbody tr {
    background-color: var(--bg-card);
    color: var(--text-primary);
}

[data-theme="dark"] .data-table tbody tr:nth-child(even) {
    background-color: var(--bg-secondary);
}

[data-theme="dark"] .data-table tbody tr:hover {
    background-color: var(--bg-hover) !important;
    color: #ffffff !important;
}

[data-theme="dark"] .data-table tbody tr:hover td {
    background-color: var(--bg-hover) !important;
    color: #ffffff !important;
}

[data-theme="dark"] .data-table td,
[data-theme="dark"] .data-table th {
    border-color: var(--border-color);
}

[data-theme="dark"] .btn-outline-primary,
[data-theme="dark"] .btn-outline-secondary {
    color: var(--text-primary);
    border-color: var(--border-color);
}

[data-theme="dark"] .btn-outline-primary:hover,
[data-theme="dark"] .btn-outline-secondary:hover {
    background-color: var(--bg-hover);
}

[data-theme="dark"] .nav-link {
    color: var(--text-secondary);
}

[data-theme="dark"] .nav-link:hover {
    color: var(--text-primary);
}

[data-theme="dark"] .nav-tabs {
    border-bottom-color: var(--border-color);
}

[data-theme="dark"] .nav-tabs .nav-link.active {
    color: var(--accent-primary);
    background-color: var(--accent-wash);
    border-color: var(--border-color) var(--border-color) var(--accent-primary);
}

[data-theme="dark"] .nav-tabs .nav-link:hover:not(.active),
[data-theme="dark"] .nav-tabs .nav-link:focus:not(.active) {
    border-color: var(--border-color);
    isolation: isolate;
}

[data-theme="dark"] .nav-pills .nav-link.active,
[data-theme="dark"] .nav-pills .show > .nav-link {
    background-color: var(--accent-primary);
    color: #ffffff;
}

/* Site-wide consistent active state for custom pill-style tab selectors
   (report tabs, chart view toggle, chart creator tabs, etc.). Uses theme
   tokens so it stays legible in both light and dark mode. */
.tab-item.active,
.chart-tab.active {
    background: var(--accent-wash) !important;
    color: var(--accent-primary) !important;
    box-shadow: inset 0 0 0 1px rgba(var(--accent-primary-rgb), 0.4) !important;
}

.tab-item.active i,
.tab-item.active span:not(.tab-item-count),
.chart-tab.active i,
.chart-tab.active span {
    color: var(--accent-primary) !important;
}

/* ═══════════════════════════════════════════════════════════════════════
   Split view — two panes, a draggable divider, stacked or side by side

   Shared by the report tabs that show one thing beside its data: Charts
   (chart + chart data) and Map (map + report data). Here rather than in
   either page's style block so the two cannot drift - a page style block is
   global once the page loads, so a second copy of these names would be a
   race, not an override.

   A page supplies: `.amx-view-toolbar` holding its own view toggle and the
   split control; `.amx-split-layout` carrying `is-split` plus
   `is-split--stacked` or `is-split--side`; the two panes tagged
   `.amx-split-primary` (the one the user sizes) and `.amx-split-secondary`
   (which takes what is left); and `.amx-split-resizer` between them. Sizes
   ride on the primary pane as --amx-split-height / --amx-split-width, which
   is also where the drag in blazorHelpers.js writes them.
   ═══════════════════════════════════════════════════════════════════════ */
.amx-view-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 12px;
    margin-bottom: 8px;
}

.amx-view-toggle {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}

/* Set off from the view toggle by a rule: they are two different questions - what is on
   screen, and how it is laid out - and run together they read as one long button group. */
.amx-split-toggle {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    padding-left: 12px;
    border-left: 1px solid var(--border-color);
}

.amx-split-label {
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-secondary);
}

.display-toggle-btn {
    display: inline-flex;
    align-items: center;
    font-size: 0.85rem;
    padding: 4px 12px;
    border: 1px solid var(--border-color);
    background-color: var(--bg-card);
    color: var(--text-primary);
    border-radius: 4px;
    cursor: pointer;
}

.display-toggle-btn:hover {
    background-color: var(--bg-hover);
}

.display-toggle-btn:focus-visible {
    outline: 2px solid var(--accent-primary);
    outline-offset: 2px;
}

.display-toggle-btn.active {
    background-color: var(--accent-primary);
    border-color: var(--accent-primary);
    color: #fff;
}

/* With one pane on screen this is a plain column and that pane fills it. */
.amx-split-layout {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
}

.amx-split-layout.is-split--stacked {
    flex-direction: column;
}

.amx-split-layout.is-split--side {
    flex-direction: row;
}

/* Both panes may shrink below their size - `0 1` rather than `0 0` on the basis - so a window
   narrower than the stored size squeezes the primary pane instead of pushing the other one
   out of the layout entirely. The floors keep each pane usable; below them the layout
   overflows and its wrapper scrolls, which beats a pane collapsed to nothing. */
.amx-split-layout.is-split--stacked > .amx-split-primary {
    flex: 0 1 var(--amx-split-height, 340px);
    width: 100%;
    min-height: 160px;
}

.amx-split-layout.is-split--side > .amx-split-primary {
    flex: 0 1 var(--amx-split-width, 620px);
    width: auto;
    height: auto;
    min-width: 260px;
    min-height: 0;
}

.amx-split-layout.is-split > .amx-split-secondary {
    flex: 1 1 240px;
    height: auto;
    min-height: 140px;
    min-width: 200px;
}

/* A wide hit area around a hairline: the strip is 10px so it is catchable with a mouse, while
   the line the user sees is the 2px drawn in ::after. */
.amx-split-resizer {
    flex: 0 0 auto;
    width: 10px;
    margin: 0 -2px;
    cursor: col-resize;
    position: relative;
    z-index: 1;
    background: transparent;
    touch-action: none;
}

.amx-split-resizer::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 4px;
    width: 2px;
    border-radius: 2px;
    background: var(--border-color);
    transition: background-color 0.15s ease;
}

.amx-split-resizer:hover::after,
.amx-split-resizer:focus-visible::after,
.amx-split-resizer.dragging::after {
    background: var(--accent-primary);
}

.amx-split-resizer:focus-visible {
    outline: 2px solid var(--border-focus, var(--accent-primary));
    outline-offset: -2px;
}

/* The same strip turned through ninety degrees for the stacked split. */
.amx-split-resizer--horizontal {
    width: auto;
    height: 10px;
    margin: -2px 0;
    cursor: row-resize;
}

.amx-split-resizer--horizontal::after {
    top: 4px;
    bottom: auto;
    left: 0;
    right: 0;
    width: auto;
    height: 2px;
}

/* While dragging, the pointer owns the split - no text selection, and nothing under the
   pointer reacting to a hover it did not mean (a chart tooltip or a map feature most of all). */
.amx-split-layout.amx-resizing {
    user-select: none;
}

.amx-split-layout.amx-resizing * {
    pointer-events: none;
}

.amx-split-layout.amx-resizing .amx-split-resizer {
    pointer-events: auto;
}

[data-theme="dark"] .border,
[data-theme="dark"] .border-top,
[data-theme="dark"] .border-bottom,
[data-theme="dark"] .border-start,
[data-theme="dark"] .border-end {
    border-color: var(--border-color) !important;
}

[data-theme="dark"] .bg-white {
    background-color: var(--bg-card) !important;
}

[data-theme="dark"] .bg-light {
    background-color: var(--bg-secondary) !important;
}

[data-theme="dark"] .text-dark {
    color: var(--text-primary) !important;
}

.text-muted {
    color: var(--text-muted) !important;
}

/* Bootstrap dims floating labels after focus or autofill; the token already supplies muted ink. */
.form-floating > label {
    opacity: 1 !important;
}

/* Account forms use Bootstrap rows, whose negative gutters need a padded containing surface. */
.account-manage-layout {
    min-width: 0;
    padding-inline: var(--space-3, .75rem);
}

/* Keep resize targets above 24 rendered pixels when the user applies the app's UI scale. */
.designer-page .cell-resize-btn {
    min-width: calc(25px / var(--amx-ui-scale, 1));
    min-height: calc(25px / var(--amx-ui-scale, 1));
    flex-shrink: 0;
}

[data-theme="dark"] a {
    color: var(--accent-primary);
}

[data-theme="dark"] .dropdown-item {
    color: var(--text-primary);
}

[data-theme="dark"] .dropdown-item:hover {
    background-color: var(--bg-hover);
    color: var(--text-primary);
}

[data-theme="dark"] .list-group-item {
    background-color: var(--bg-card);
    border-color: var(--border-color);
    color: var(--text-primary);
}

/* Layout specific dark mode styles */
[data-theme="dark"] .amx-shell,
[data-theme="dark"] .amx-topbar,
[data-theme="dark"] .amx-left,
[data-theme="dark"] .amx-body {
    background-color: var(--bg-primary);
    color: var(--text-primary);
}

[data-theme="dark"] .settings-shell,
[data-theme="dark"] .settings-sidebar,
[data-theme="dark"] .settings-main,
[data-theme="dark"] .settings-content {
    background-color: var(--bg-primary);
    color: var(--text-primary);
}

[data-theme="dark"] .amdDataContainer {
    background-color: var(--bg-card);
    border-color: var(--border-color);
}

[data-theme="dark"] .login-box {
    background-color: var(--bg-card);
    color: var(--text-primary);
}

/* The sign-in pages' background photo (Account/Shared/SignInBackdrop.razor), faded in by
   amxImageReveal.js once it has arrived. Fixed and sized like the pages' own full-viewport
   containers (see GLOBAL UI SCALE), so it stays put while a page scrolls, as the background-image
   it replaced did. The host is its stacking context, so z-index -1 puts the photo above the
   host's flat background-color but under its content. */
.amx-backdrop-host {
    isolation: isolate;
}

.amx-backdrop {
    position: fixed;
    top: 0;
    left: 0;
    z-index: -1;
    width: 100%;
    height: var(--amx-viewport-h);
    object-fit: cover;
    object-position: center;
    pointer-events: none;
    user-select: none;
}

/* No photo to be had: the flat colour, not a broken-image box. */
.amx-backdrop.is-failed {
    visibility: hidden;
}

[data-theme="dark"] .amx-tile {
    background-color: var(--bg-card);
}

[data-theme="dark"] .amx-tile:hover {
    background-color: var(--bg-hover);
}

[data-theme="dark"] .recent-asset-item {
    background-color: var(--bg-card);
    border-color: var(--border-color) !important;
    color: var(--text-primary);
}

[data-theme="dark"] .recent-asset-item:hover {
    background-color: var(--bg-hover);
}

/* ========== DARK MODE BUTTON STYLES ========== */

/* Modern button variants for dark mode */
[data-theme="dark"] .btn-modern.btn-success {
    background-color: var(--accent-success-fill);
    border-color: var(--accent-success-fill);
    color: #ffffff;
}

[data-theme="dark"] .btn-modern.btn-success:hover {
    background-color: var(--accent-success-fill);
    border-color: var(--accent-success-fill);
}

[data-theme="dark"] .btn-modern.btn-primary {
    background-color: #4d94ff;
    border-color: #4d94ff;
    color: #ffffff;
}

[data-theme="dark"] .btn-modern.btn-primary:hover {
    background-color: #3b82f6;
    border-color: #3b82f6;
}

[data-theme="dark"] .btn-modern.btn-secondary {
    background-color: var(--bg-hover);
    border-color: var(--border-color);
    color: var(--text-primary);
}

[data-theme="dark"] .btn-modern.btn-secondary:hover {
    background-color: var(--bg-hover);
    border-color: #4d4d7b;
    color: #ffffff;
}

[data-theme="dark"] .btn-modern.btn-warning {
    background-color: #f59e0b;
    border-color: #f59e0b;
    color: #ffffff;
}

[data-theme="dark"] .btn-modern.btn-danger {
    background-color: #ef4444;
    border-color: #ef4444;
    color: #ffffff;
}

[data-theme="dark"] .btn-modern.btn-cancel,
[data-theme="dark"] .btn-modern.btn-debug {
    background-color: var(--bg-hover);
    border-color: var(--border-color);
    color: var(--text-primary);
}

[data-theme="dark"] .btn-modern.btn-cancel:hover,
[data-theme="dark"] .btn-modern.btn-debug:hover {
    background-color: var(--bg-hover);
    border-color: #4d4d7b;
    color: #ffffff;
}

/* Default button dark mode */
[data-theme="dark"] .btn-default {
    background-color: var(--bg-hover);
    border-color: var(--border-color);
    color: var(--text-primary);
}

[data-theme="dark"] .btn-default:hover {
    background-color: var(--bg-hover);
    border-color: #4d4d7b;
    color: #ffffff;
}

/* Standard Bootstrap button overrides for dark mode (+ dbd-* equivalents).
   dbd selectors use .dbd-btn.dbd-* (+ [data-theme] = 3 units) so they beat
   the light .dbd-btn.dbd-* rules (2 units) that appear later in the file. */
[data-theme="dark"] .btn-success,
[data-theme="dark"] .dbd-btn.dbd-success {
    background: var(--accent-success-fill);
    border: 1px solid var(--accent-success-fill);
    color: #ffffff;
}

[data-theme="dark"] .btn-primary,
[data-theme="dark"] .dbd-btn.dbd-primary {
    background-color: #4d94ff;
    border: 1px solid #4d94ff;
    color: var(--text-on-bright);
}

[data-theme="dark"] .btn-secondary,
[data-theme="dark"] .dbd-btn.dbd-secondary,
[data-theme="dark"] .dbd-btn.dbd-outline-primary,
[data-theme="dark"] .dbd-btn.dbd-outline-secondary {
    background-color: var(--bg-hover);
    border: 1px solid var(--border-color);
    color: var(--text-primary);
}

[data-theme="dark"] .btn-secondary:hover,
[data-theme="dark"] .dbd-btn.dbd-secondary:hover:not(:disabled),
[data-theme="dark"] .dbd-btn.dbd-outline-primary:hover:not(:disabled),
[data-theme="dark"] .dbd-btn.dbd-outline-secondary:hover:not(:disabled) {
    background-color: var(--bg-hover);
    border: 1px solid #4d4d7b;
    color: #ffffff;
}

[data-theme="dark"] .btn-light,
[data-theme="dark"] .dbd-btn.dbd-light {
    background-color: var(--bg-hover);
    border: 1px solid var(--border-color);
    color: var(--text-primary);
}

[data-theme="dark"] .btn-light:hover,
[data-theme="dark"] .dbd-btn.dbd-light:hover:not(:disabled) {
    background-color: var(--bg-hover);
    color: #ffffff;
}

[data-theme="dark"] .dbd-btn.dbd-success:hover:not(:disabled),
[data-theme="dark"] .dbd-btn.dbd-success:focus:not(:disabled),
[data-theme="dark"] .dbd-btn.dbd-success:focus-visible:not(:disabled),
[data-theme="dark"] .dbd-btn.dbd-success:active:not(:disabled) {
    background: var(--accent-success-fill);
    border-color: var(--accent-success-fill);
    color: #ffffff;
    filter: none;
    box-shadow: none;
    outline: none;
}

[data-theme="dark"] .dbd-btn.dbd-outline-success,
[data-theme="dark"] .dbd-btn.btn-outline-success {
    background-color: transparent;
    border: 1px solid #22c55e;
    color: #4ade80;
}

[data-theme="dark"] .dbd-btn.dbd-outline-success:hover:not(:disabled),
[data-theme="dark"] .dbd-btn.btn-outline-success:hover:not(:disabled) {
    background-color: rgba(34, 197, 94, 0.12);
    border: 1px solid #22c55e;
    color: #86efac;
    filter: none;
}

[data-theme="dark"] .dbd-btn.dbd-warning {
    background-color: #fbbf24;
    border: 1px solid #fbbf24;
    color: var(--text-on-bright);
}

[data-theme="dark"] .dbd-btn.dbd-warning:hover:not(:disabled) {
    background-color: #f5b312;
    border-color: #f5b312;
    color: var(--text-on-bright);
    filter: none;
}

[data-theme="dark"] .dbd-btn.dbd-outline-warning {
    background-color: rgba(245, 158, 11, 0.12);
    border: 1px solid #f59e0b;
    color: #fcd34d;
}

/* Without this the light hover below (#fffbeb fill, #b45309 text) applied in dark mode and the
   button flashed white. Deepens its own wash and brightens the text, like the other outlines. */
[data-theme="dark"] .dbd-btn.dbd-outline-warning:hover:not(:disabled) {
    background-color: rgba(245, 158, 11, 0.22);
    border: 1px solid #fbbf24;
    color: #fde68a;
    filter: none;
}

[data-theme="dark"] .dbd-btn.dbd-info {
    background-color: var(--accent-success-fill);
    border: 1px solid var(--accent-success-fill);
    color: #ffffff;
}

[data-theme="dark"] .dbd-btn.dbd-info:hover:not(:disabled) {
    background-color: var(--accent-success-fill);
    border-color: var(--accent-success-fill);
    color: #ffffff;
    filter: none;
}

[data-theme="dark"] .dbd-btn.dbd-outline-info {
    background-color: rgba(14, 165, 233, 0.12);
    border: 1px solid #0ea5e9;
    color: #7dd3fc;
}

/* Same gap as outline-warning above: no dark hover meant the light one applied. */
[data-theme="dark"] .dbd-btn.dbd-outline-info:hover:not(:disabled) {
    background-color: rgba(14, 165, 233, 0.22);
    border: 1px solid #38bdf8;
    color: #bae6fd;
    filter: none;
}

[data-theme="dark"] .dbd-btn.dbd-danger {
    background-color: #ef4444;
    border: 1px solid #ef4444;
    color: var(--text-on-bright);
}

[data-theme="dark"] .dbd-btn.dbd-danger:hover:not(:disabled) {
    background-color: #dc2626;
    border-color: #dc2626;
    color: #ffffff;
    filter: none;
}

[data-theme="dark"] .dbd-btn.dbd-outline-danger,
[data-theme="dark"] .dbd-btn.btn-outline-danger {
    background-color: transparent;
    border: 1px solid #ef4444;
    color: #f87171;
}

[data-theme="dark"] .dbd-btn.dbd-outline-danger:hover:not(:disabled),
[data-theme="dark"] .dbd-btn.btn-outline-danger:hover:not(:disabled) {
    background-color: rgba(239, 68, 68, 0.12);
    border: 1px solid #ef4444;
    color: #fca5a5;
    filter: none;
}

[data-theme="dark"] .dbd-btn.dbd-primary:hover:not(:disabled) {
    background-color: #6aa8ff;
    border-color: #6aa8ff;
    color: #ffffff;
    filter: none;
}

/* The report grid's search/filter track on dark.

   Written here rather than beside the rest of the track's styling further up the file, and that
   placement is the whole point: `[data-theme="dark"] .dbd-btn.dbd-primary:hover:not(:disabled)`
   just above is five units, exactly what these rules are, so only coming later settles it. Higher
   up they lose, and on a dark grid the flat track fills with solid blue and green pills - which is
   what it did.

   Deliberately identical to Components/Grid/DataGrid.razor.css: the report grid, the data-view
   grid and the inline grid editor draw the same header, and the two copies of these declarations
   exist only because a scoped stylesheet cannot reach the editor's markup. Change one, change the
   other. */
[data-theme="dark"] .amx-grid-wrapper .toolbar-btn-group {
    background-color: var(--bg-tertiary);
    border-color: var(--border-color);
}

[data-theme="dark"] .amx-grid-wrapper .toolbar-btn-group .btn {
    background: transparent;
    border: none;
    box-shadow: none;
    color: var(--text-primary);
}

/* --bg-card, not --bg-hover: on dark, --bg-hover is #1a2338 against the track's #1a2136 and
   cannot be seen. The card colour is darker than the track and reads as the same "lifted out of
   it" move the light theme makes. */
[data-theme="dark"] .amx-grid-wrapper .toolbar-btn-group .btn:hover,
[data-theme="dark"] .amx-grid-wrapper .toolbar-btn-group .btn:active {
    background-color: var(--bg-card);
    color: var(--text-primary);
    filter: none;
}

[data-theme="dark"] .amx-grid-wrapper .toolbar-btn-group .dbd-warning,
[data-theme="dark"] .amx-grid-wrapper .toolbar-btn-group .dbd-warning:hover {
    background-color: rgba(245, 158, 11, 0.14);
    border-color: transparent;
    color: var(--accent-warning-text);
    box-shadow: none;
    filter: none;
}

/* Clear Search keeps its own amber on dark as well - the flat-track rules above are one unit
   heavier than its own and would otherwise strip both the wash and the border off it. */
[data-theme="dark"] .amx-grid-wrapper .toolbar-btn-group .grid-clear-search-btn {
    background-color: rgba(245, 158, 11, 0.12);
    border: 1px solid var(--accent-warning-text);
    color: var(--accent-warning-text);
    box-shadow: none;
}

[data-theme="dark"] .amx-grid-wrapper .toolbar-btn-group .grid-clear-search-btn:hover {
    background-color: rgba(245, 158, 11, 0.2);
    color: var(--accent-warning-text);
    box-shadow: var(--shadow-sm);
    filter: none;
}

[data-theme="dark"] .amx-grid-wrapper .toolbar-btn-group .grid-clear-search-btn:active {
    background-color: rgba(245, 158, 11, 0.28);
    color: var(--accent-warning-text);
    filter: none;
}

/* Icon buttons and toolbar buttons */
[data-theme="dark"] .btn i,
[data-theme="dark"] .btn .fa,
[data-theme="dark"] .btn .fas,
[data-theme="dark"] .btn .far,
[data-theme="dark"] .btn .fal {
    color: inherit;
}

/* Ensure button text/icons are visible.
   Solid .dbd-warning buttons keep their --text-on-bright label: light text on yellow fails contrast.

   The flat-track buttons - .view-btn and .grid-actions-btn in the grid header, .toolbar-btn in the
   report toolbar above it - are excluded because they are the only members of those tracks this
   rule could reach: they are bare <button>s, while the search and filter buttons beside them carry
   .dbd-primary/.dbd-success and were already excluded. So on dark it lit those to --text-primary at
   rest and left their neighbours at --text-secondary - buttons in one track, some brighter than
   others, for no reason a reader could see. All of them set their own colours (--text-primary at
   rest and on hover, in both themes), which is the treatment this catch-all exists to
   guarantee anyway.

   The report template designer (.rtd-shell) is excluded as a whole. Every button in it is themed
   by its own --rtd-* tokens (ReportTemplateDesigner.razor.css), and this rule reaching in painted
   its coloured ones - the danger reds, the accent greens, the assistant purple - in the plain text
   colour, and its palette dock's labels in near-white on the dock's white. */
[data-theme="dark"] button:not(.btn-primary):not(.btn-success):not(.btn-warning):not(.btn-danger):not(.dbd-primary):not(.dbd-success):not(.dbd-info):not(.btn-info):not(.dbd-warning):not(.dbd-danger):not(.view-btn):not(.grid-actions-btn):not(.toolbar-btn):not(.rtd-shell *) {
    color: var(--text-primary);
}

[data-theme="dark"] button:not(.btn-primary):not(.btn-success):not(.btn-warning):not(.btn-danger):not(.dbd-primary):not(.dbd-success):not(.dbd-info):not(.btn-info):not(.dbd-warning):not(.dbd-danger):not(.view-btn):not(.grid-actions-btn):not(.toolbar-btn):not(.rtd-shell *):hover {
    color: #ffffff;
}

/* On a record, the top bar (app and Settings bars) carries its name and parent beside the logo,
   set off from it by a rule (DataViewComponent fills the outlet; empty everywhere else). It gives
   way before the search box does: the name ellipses rather than pushing the actions off. */
.amx-topbar-title {
    display: flex;
    align-items: center;
    min-width: 0;
    height: 32px;
    padding-left: 12px;
    border-left: 1px solid var(--border-color);
}

.amx-topbar-title:not(:has(*)) {
    display: none;
}

/* Shrinks at a quarter of the search box's rate, so the box narrows first and the name only
   ellipses once the box has given up most of its width. */
.amx-topbar-left:has(.amx-topbar-title > *) {
    flex: 0 0.25 auto;
    min-width: 0;
}

/* A phone has no room for the name beside the actions, and no logo to sit next to: the name takes
   a row of its own under them rather than being lost with the logo. */
@media (max-width: 700px) {
    .amx-topbar:has(.amx-topbar-title > *) {
        flex-wrap: wrap;
    }

    .amx-topbar .amx-topbar-left:has(.amx-topbar-title > *) {
        display: flex;
        order: 1;
        flex: 1 0 100%;
    }

    .amx-topbar .amx-topbar-left .amx-brand {
        display: none;
    }

    .amx-topbar .amx-topbar-title {
        max-width: none;
        padding-left: 0;
        border-left: 0;
    }
}

/* The top bar's icon buttons (app and Settings bars) keep their own ink in both themes: muted at
   rest, accent on hover, while open and when the page is bookmarked. !important because the rule
   above outranks anything a component can write and turned them near-white, then white on hover.
   The mobile-mode exit and a listening mic keep their red. */
:is(.amx-topbar, .settings-topbar) :is(
    .menu-icon > .btn:not(.btn-outline-danger):not(.is-listening), .voice-command-btn:not(.is-listening),
    .add-new-btn, .history-btn, .bookmarks-btn, .ai-models-btn, .messages-btn, .copy-link-trigger,
    .user-dropdown > .dropdown-toggle
) {
    color: var(--text-secondary) !important;
}

:is(.amx-topbar, .settings-topbar) :is(
    .menu-icon > .btn:not(.btn-outline-danger):not(.is-listening), .voice-command-btn:not(.is-listening),
    .add-new-btn, .history-btn, .bookmarks-btn, .ai-models-btn, .messages-btn, .copy-link-trigger,
    .user-dropdown > .dropdown-toggle
):is(:hover, [aria-expanded="true"], .on) {
    color: var(--accent-primary) !important;
}

/* The button of an open dropdown is held in its hover fill as well as its accent ink, so it reads
   as active for as long as its panel is up (not .on - a bookmarked page is a state, not an open
   menu). The components write aria-expanded as "true"/"false": Blazor renders a bare true bool as
   an empty attribute, which this never matched. */
:is(.amx-topbar, .settings-topbar) :is(
    .menu-icon > .btn:not(.btn-outline-danger):not(.is-listening), .voice-command-btn:not(.is-listening),
    .add-new-btn, .history-btn, .bookmarks-btn, .ai-models-btn, .messages-btn, .copy-link-trigger,
    .user-dropdown > .dropdown-toggle
)[aria-expanded="true"] {
    background-color: var(--bg-hover) !important;
}

/* Configure Report and similar action buttons */
[data-theme="dark"] .btn-info {
    background-color: var(--accent-success-fill);
    border-color: var(--accent-success-fill);
    color: #ffffff;
}

[data-theme="dark"] .btn-info:hover {
    background-color: var(--accent-success-fill);
    border-color: var(--accent-success-fill);
}

/* Link buttons */
[data-theme="dark"] .btn-link {
    color: #4d94ff;
}

[data-theme="dark"] .btn-link:hover {
    color: #6ba8ff;
}

/* ========== END DARK MODE BUTTON STYLES ==========

/* ========== END THEME VARIABLES ========== */

/* ========== GLOBAL UI SCALE ==========
   The whole app is shrunk so more fits on screen. <body> is transform-scaled, which does NOT
   change layout, so body must be sized UP by the reciprocal of the scale for its scaled result to
   land exactly on the viewport edges.

   The reciprocal MUST be calculated, never hard-coded: 1 / 0.9 is 111.111...%, and the literal
   "111%" this used to carry left 0.1% of the viewport uncovered — a hairline gap of bare canvas
   down the right edge and along the bottom. (The literal was originally written for a 0.8 scale
   and never recomputed when the scale changed, which is exactly the drift calc() prevents.)

   Anything that must span the full visual viewport reads --amx-viewport-h rather than 100vh:
   under the scale a 100vh box only covers 90% of the screen. Prefer height:100% where the element
   is a descendant of body with an unbroken definite-height chain; use the variable for
   position:fixed layers and for elements whose ancestors are not height-constrained. */
:root {
    --amx-ui-scale: 0.9;
    /* Height of the real viewport expressed in the pre-scale coordinate space body lays out in. */
    --amx-viewport-h: calc(100vh / var(--amx-ui-scale));
}

.amx-shell {
    height: var(--amx-viewport-h);
    width: 100%;
    display: flex;
    flex-direction: column;
    background: var(--amx-bg);
    color: var(--amx-text);
    /* clip, not hidden - the shell sizes itself to the viewport and must never scroll. See the
       html/body rule below for why a scrollable-but-scrollbar-less box is the bug, not the fix. */
    overflow: hidden;
    overflow: clip;
}

html {
    height: 100vh;
}

body {
    transform: scale(var(--amx-ui-scale));
    transform-origin: top left;
    /* calc(), not a rounded literal — see GLOBAL UI SCALE above. */
    width: calc(100% / var(--amx-ui-scale));
    height: calc(100% / var(--amx-ui-scale));
    margin: 0;
}

/* ========== SCRIPT-PLACED PANELS ==========
   Context menus and anchored dropdowns cannot be placed in CSS: where they go depends on their own
   measured size, on the cursor or trigger, and on the room left in the viewport. So they render at
   a fixed top-left origin and a script moves them (AmxPositionAtPoint / AmxAnchorDropdown in
   blazorHelpers.js).

   On Blazor Server that script runs a full circuit round trip late. The browser paints the panel at
   that origin, acknowledges the render, the server sends the JS call back, and only then does the
   panel move - so opening one flashed a menu in the top-left corner of the screen before it
   appeared under the pointer. Held hidden here until the positioner reveals it, the panel is
   painted once, where it belongs.

   The positioners reveal it from a finally, so a panel still appears if measuring throws. Anything
   carrying this class must be positioned by one of them, or it will not be seen at all. */
.amx-anchored-panel {
    visibility: hidden;
}

/* ========== VIEW OPTIONS ==========
   How a grid is drawn - display mode, zoom, fit, card expansion, reset columns - as one panel of
   settings over the commands below them. The grid draws these parts (DataGrid.ViewOptions) into
   whichever View menu holds it: the report toolbar's (GridHeaderHost) or, on a record, the grid's
   own (DataGrid.ViewMenu.cs). The report's Sort and Save menus are built from the same parts.
   Global rather than in either component, because both draw them. Button colours carry !important
   because the dark catch-all for buttons above otherwise paints them all in body ink. */

/* How the data is drawn, above a rule; then what can be done to it. */
.rv-settings {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 8px 8px 12px;
    margin-bottom: 6px;
    border-bottom: 1px solid var(--border-color);
}

.rv-settings:last-child {
    margin-bottom: 0;
    padding-bottom: 4px;
    border-bottom: 0;
}

.rv-label {
    display: block;
    margin: 0 0 6px;
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-secondary);
}

.rv-seg {
    display: flex;
    gap: 2px;
    padding: 2px;
    background: var(--bg-secondary);
    border-radius: var(--radius-sm);
}

[data-theme="dark"] .rv-seg {
    background: var(--bg-primary);
}

.rv-seg-btn {
    flex: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 30px;
    padding: 0 8px;
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--text-secondary) !important;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 6px;
    cursor: pointer;
}

.rv-seg-btn:hover {
    color: var(--text-primary) !important;
}

.rv-seg-btn.active {
    color: var(--text-primary) !important;
    background: var(--bg-card);
    border-color: var(--border-color);
    box-shadow: var(--shadow-sm);
}

[data-theme="dark"] .rv-seg-btn.active {
    background: var(--bg-tertiary);
}

.rv-row {
    display: flex;
    align-items: center;
    gap: 6px;
}

.rv-step,
.rv-chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    height: 30px;
    min-width: 30px;
    padding: 0;
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--text-primary) !important;
    background: var(--bg-card);
    border: 1px solid var(--border-interactive);
    border-radius: var(--radius-sm);
    cursor: pointer;
    white-space: nowrap;
}

.rv-chip {
    padding: 0 10px;
    margin-left: auto;
}

.rv-chip-wide {
    width: 100%;
    margin: 6px 0 0;
}

.rv-step:hover:not(:disabled),
.rv-chip:hover {
    background: var(--bg-hover);
}

.rv-chip.active {
    color: var(--accent-success-text) !important;
    background: color-mix(in srgb, var(--accent-success-fill) 12%, var(--bg-card));
    border-color: var(--accent-success-text);
}

.rv-step:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* The percentage is the readout as well as the reset, so it stays at full strength when there
   is nothing to reset. */
.rv-zoom-value {
    height: 30px;
    min-width: 56px;
    padding: 0 6px;
    font-size: 0.84rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--text-primary) !important;
    background: transparent;
    border: 0;
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.rv-zoom-value:hover:not(:disabled) {
    background: var(--bg-hover);
}

.rv-zoom-value:disabled {
    cursor: default;
}

.rv-select {
    height: 30px;
    padding: 0 28px 0 10px;
    font-size: 0.84rem;
    font-weight: 700;
    color: var(--text-primary);
    background-color: var(--bg-input);
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='gray' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M6 9l6 6 6-6'/></svg>");
    background-repeat: no-repeat;
    background-position: calc(100% - 9px) center;
    border: 1px solid var(--field-border);
    border-radius: var(--radius-sm);
    appearance: none;
    -webkit-appearance: none;
    cursor: pointer;
}

.rv-select:focus-visible {
    outline: none;
    border-color: var(--accent-primary);
    box-shadow: 0 0 0 3px rgba(var(--accent-primary-rgb), 0.15);
}

.rv-of {
    font-size: 0.84rem;
    color: var(--text-secondary);
    white-space: nowrap;
}

.rv-item {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    min-height: 34px;
    padding: 0 8px;
    font-size: 0.84rem;
    font-weight: 500;
    text-align: left;
    color: var(--text-primary) !important;
    background: transparent;
    border: 0;
    border-radius: 6px;
    cursor: pointer;
}

.rv-item i {
    width: 16px;
    text-align: center;
    color: var(--text-secondary);
}

.rv-item:hover:not(:disabled) {
    background: var(--bg-hover);
}

.rv-item.danger,
.rv-item.danger i {
    color: var(--accent-danger-text, var(--accent-danger)) !important;
}

.rv-item:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.rv-seg-btn:focus-visible,
.rv-step:focus-visible,
.rv-chip:focus-visible,
.rv-zoom-value:focus-visible,
.rv-item:focus-visible {
    outline: 2px solid var(--border-focus, var(--accent-primary));
    outline-offset: -2px;
}

.rv-empty {
    margin: 0;
    padding: 8px;
    font-size: 0.8125rem;
    color: var(--text-secondary);
}

h1:focus {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid #e50000;
}

.validation-message {
    color: #e50000;
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}

@import url('open-iconic/font/css/open-iconic-bootstrap.min.css');

html, body {
    font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
    /* clip, NOT hidden. `hidden` still makes an element a scroll container - it only removes the
       scrollbar - so the document could be scrolled programmatically with no way for the user to
       scroll it back. That happened routinely here: the scale means the body lays out in a
       coordinate space 1/--amx-ui-scale wider than the screen, so anything appended to the body and
       positioned from getBoundingClientRect() (which reports post-scale visual coordinates) lands
       too far right in layout space and overflows it. The next scrollIntoView() - blazorHelpers.js
       calls it in a dozen places - then scrolled the body across to reveal the overflow, dragging
       .amx-shell off the left edge and leaving bare canvas down the right.

       `clip` clips exactly the same way but does NOT create a scroll container, so there is no
       scroll position to move and the shell is pinned for good. The `hidden` above it is the
       fallback for engines without `clip`; leaving it out would fall back to `visible`. */
    overflow: hidden;
    overflow: clip;
}

h1:focus {
    outline: none;
}

::deep .popover, ::deep .popover-body {
    max-width: max-content !important;
}

.amx-alert {
    z-index: 2000 !important;
}

.amx-alert-backdrop {
    z-index: 1999 !important;
}

/* position:fixed resolves against <body> here (a transformed ancestor becomes the containing
   block), so 100% is the scale-compensated width already; the height needs the same compensation. */
.modal-backdrop {
    width: 100% !important;
    height: var(--amx-viewport-h) !important;
}

.popover {
    max-width: 800px;
}

a, .btn-link {
    color: #2563eb;
}

.modal-fullscreen {
    width: 100% !important;
}

.report-toolbar-wrapper {
    border-bottom: 1px solid #f1f5f9;
}

.red-button-bg {
    color: #fff !important;
    background-color: #dc3545 !important;
    border-color: #dc3545 !important;
}

.form-control {
    transition: none !important;
    appearance: auto !important;
}

/* ── DBD button treatment (namespaced, opt-in) ────────────────────────
   Our custom button language lives under the `.dbd-btn` marker so it sits
   ALONGSIDE Bootstrap without hijacking `.btn`/`.btn-*`. A plain Bootstrap
   button (`btn btn-primary`) is left untouched; opt in by adding
   `dbd-btn` + a `dbd-*` colour modifier (e.g. `btn dbd-btn dbd-primary`).
   Shape/finish: shadcn radius, medium weight, flat (no shadow, no lift). */
.dbd-btn {
    border-radius: 8px;
    box-shadow: none;
    transition: background-color .12s ease, border-color .12s ease, color .12s ease, filter .12s ease, opacity .12s ease;
    font-size: 0.9rem;
}

.dbd-btn:hover {
    transform: none;
}

.dbd-btn:hover:not(:disabled) {
    filter: brightness(0.97);
}

/* Flat ghost: outline-primary / outline-secondary / light.
   Scoped to .dbd-btn.dbd-* (2 classes) so the border reliably beats
   Bootstrap's `.btn { border: 1px solid transparent }` (1 class). */
.dbd-btn.dbd-outline-primary,
.dbd-btn.dbd-outline-secondary,
.dbd-btn.dbd-light {
    background-color: #ffffff;
    border: 1px solid #e4e4e7;
    color: #18181b;
}

.dbd-btn.dbd-outline-primary:hover:not(:disabled),
.dbd-btn.dbd-outline-secondary:hover:not(:disabled),
.dbd-btn.dbd-light:hover:not(:disabled) {
    background-color: #f4f4f5;
    border: 1px solid #d4d4d8;
    color: #18181b;
    filter: none;
}

/* Secondary — matches .multiselect-footer .btn-secondary */
.dbd-btn.dbd-secondary {
    color: #18181b;
    background-color: #ffffff;
    border: 1px solid #d4d4d8;
}

.dbd-btn.dbd-secondary:hover:not(:disabled) {
    background-color: #f4f4f5;
    border: 1px solid #71717a;
    color: #18181b;
}

/* Blue primary; checked primary toggles fill blue. */
.dbd-btn.dbd-primary,
.btn-check:checked + .dbd-outline-primary {
    color: #ffffff;
    background-color: #2563eb;
    border: 1px solid #2563eb;
}

.dbd-btn.dbd-primary:hover:not(:disabled),
.btn-check:checked + .dbd-outline-primary:hover {
    background-color: #1d4ed8;
    border-color: #1d4ed8;
    color: #ffffff;
    filter: none;
}

/* Flat green success; checked success toggles fill green. */
.dbd-btn.dbd-success,
.btn-check:checked + .dbd-outline-success {
    color: #ffffff;
    background: #15803d;
    border: 1px solid transparent;
}

/* Keyboard focus ring for every dbd button.
   Two things were wrong without this. Bootstrap's `button:focus` paints a blue ring, which the
   design language rules out as the accent (§1); and the success rule below clears that ring with
   `box-shadow:none; outline:none` without putting anything back, so the primary action on a page —
   Save, Add, Apply — had no visible focus at all (WCAG 2.4.7).
   `:focus-visible` so it shows for keyboard use and not on a mouse click, and specific enough to
   beat both the Bootstrap rule and the success rule below. */
.btn.dbd-btn:focus-visible:not(:disabled) {
    outline: 2px solid var(--accent-success-text);
    outline-offset: 2px;
    box-shadow: 0 0 0 3px rgba(21, 128, 61, .35);
}

[data-theme="dark"] .btn.dbd-btn:focus-visible:not(:disabled) {
    outline: 2px solid var(--accent-success-text);
    box-shadow: 0 0 0 3px rgba(34, 197, 94, .45);
}

/* A mouse click should not leave Bootstrap's blue ring sitting on the button. */
.btn.dbd-btn:focus:not(:focus-visible) {
    box-shadow: none;
}

.dbd-btn.dbd-success:hover:not(:disabled),
.dbd-btn.dbd-success:focus:not(:disabled),
.dbd-btn.dbd-success:focus-visible:not(:disabled),
.dbd-btn.dbd-success:active:not(:disabled),
.dbd-btn.dbd-success:focus-within:not(:disabled) {
    color: #ffffff;
    background: #15803d;
    border-color: transparent;
    filter: brightness(1.05);
    /* Kill Bootstrap's blue focus/active ring that made success buttons flash blue. */
    box-shadow: none;
}

/* Flat-ghost success outline. */
.dbd-btn.dbd-outline-success,
.dbd-btn.btn-outline-success {
    background-color: #ffffff;
    border: 1px solid #15803d;
    color: #15803d;
}

.dbd-btn.dbd-outline-success:hover:not(:disabled),
.dbd-btn.btn-outline-success:hover:not(:disabled) {
    background-color: #f0fdf4;
    border: 1px solid #166534;
    color: #166534;
    filter: none;
}

/* Danger — solid + flat-ghost outline variants. */
.dbd-btn.dbd-danger {
    color: #ffffff;
    background-color: #dc2626;
    border: 1px solid #dc2626;
}

.dbd-btn.dbd-danger:hover:not(:disabled) {
    background-color: #b91c1c;
    border-color: #b91c1c;
    color: #ffffff;
    filter: none;
}

.dbd-btn.dbd-outline-danger,
.dbd-btn.btn-outline-danger {
    background-color: #ffffff;
    border: 1px solid #dc2626;
    color: #dc2626;
}

.dbd-btn.dbd-outline-danger:hover:not(:disabled),
.dbd-btn.btn-outline-danger:hover:not(:disabled) {
    background-color: #fef2f2;
    border: 1px solid #b91c1c;
    color: #b91c1c;
    filter: none;
}

/* Warning — solid + flat-ghost outline. Completes the palette; markup used to reach
   for Bootstrap's .btn-warning because there was no dbd equivalent. */
.dbd-btn.dbd-warning {
    /* Yellow rather than the amber this started as, closer to Bootstrap's #ffc107 while
       staying in the app's palette. Yellow needs dark text - white on this fails contrast. */
    color: #18181b;
    background-color: #f5b312;
    border: 1px solid #f5b312;
}

.dbd-btn.dbd-warning:hover:not(:disabled) {
    background-color: #dd9b06;
    border-color: #dd9b06;
    color: #18181b;
    filter: none;
}

.dbd-btn.dbd-outline-warning {
    background-color: #ffffff;
    border: 1px solid #f5b312;
    color: #a16207;
}

.dbd-btn.dbd-outline-warning:hover:not(:disabled) {
    background-color: #fffbeb;
    border: 1px solid #b45309;
    color: #b45309;
    filter: none;
}

/* Info — solid + flat-ghost outline. */
.dbd-btn.dbd-info {
    color: #ffffff;
    background-color: var(--accent-success-fill);
    border: 1px solid var(--accent-success-fill);
}

.dbd-btn.dbd-info:hover:not(:disabled) {
    background-color: var(--accent-success-fill);
    border-color: var(--accent-success-fill);
    color: #ffffff;
    filter: none;
}

.dbd-btn.dbd-outline-info {
    background-color: #ffffff;
    border: 1px solid #0284c7;
    color: #0369a1;
}

.dbd-btn.dbd-outline-info:hover:not(:disabled) {
    background-color: #f0f9ff;
    border: 1px solid #0369a1;
    color: #0369a1;
    filter: none;
}

/* Checked neutral (outline-secondary) toggle → pressed look */
.btn-check:checked + .dbd-outline-secondary {
    background-color: #e4e4e7;
    border-color: #d4d4d8;
    color: #18181b;
}

.defaultToolbar {
    min-height: 50px !important;
}

.btn-default {
    border-style: solid;
    border-width: 1px;
    border-color: #c4c4c4;
    background-color: #eeeeeea8;
    color: #374151;
}

.btn-default:hover {
    background-color: #f9fafb;
    border-color: #9ca3af;
}

.btn-pulse-danger {
    animation: pulse-danger 1.5s ease-in-out infinite;
}

@keyframes pulse-danger {
    0%, 100% {
        box-shadow: 0 0 0 0 rgba(220, 38, 38, 0.6);
    }

    50% {
        box-shadow: 0 0 0 0.5rem rgba(220, 38, 38, 0);
    }
}

@media (prefers-reduced-motion: reduce) {
    .btn-pulse-danger {
        animation: none;
    }
}

/*.btn-modern {
    border-radius: 8px;
    font-weight: 500;
    transition: all 0.2s ease;
    border: 1px solid transparent;
    font-size: 14px;
    position: relative;
    overflow: hidden;
}

.btn-modern.btn-primary {
    background-color: #3b82f6;
    color: #ffffff;
    border-color: #3b82f6;
}

.btn-modern.btn-primary:hover {
    background-color: #2563eb;
    border-color: #2563eb;
    transform: translateY(-1px);
}

.btn-modern.btn-success:hover {
    background-color: #059669;
    border-color: #059669;
    transform: translateY(-1px);
}

.btn-modern.btn-warning {
    background-color: #f59e0b;
    color: #ffffff;
    border-color: #f59e0b;
}

.btn-modern.btn-warning:hover {
    background-color: #d97706;
    border-color: #d97706;
    transform: translateY(-1px);
}

.btn-modern.btn-danger {
    background-color: #ef4444;
    color: #ffffff;
    border-color: #ef4444;
}

.btn-modern.btn-danger:hover {
    background-color: #dc2626;
    border-color: #dc2626;
    transform: translateY(-1px);
}

.btn-modern.btn-secondary {
    background-color: #ffffff;
    color: #374151;
    border-color: #d1d5db;
}

.btn-modern.btn-secondary:hover {
    background-color: #f9fafb;
    border-color: #9ca3af;
    color: #111827;
    transform: translateY(-1px);
}*/

/* Save/Cancel Button Styles */
/*.btn-modern.btn-save {
    background-color: #3b82f6;
    color: #ffffff;
    border-color: #3b82f6;
    font-weight: 600;
}

.btn-modern.btn-save:hover {
    background-color: #2563eb;
    border-color: #2563eb;
    transform: translateY(-1px);
}

.btn-modern.btn-cancel {
    background-color: #ffffff;
    color: #6b7280;
    border-color: #d1d5db;
    font-weight: 500;
}

.btn-modern.btn-cancel:hover {
    background-color: #f9fafb;
    border-color: #9ca3af;
    color: #374151;
    transform: translateY(-1px);
}

.btn-modern.btn-debug {
    background-color: #f9fafb;
    color: #6b7280;
    border-color: #d1d5db;
    font-size: 12px;
    font-weight: 500;
}

.btn-modern.btn-debug:hover {
    background-color: #f3f4f6;
    border-color: #9ca3af;
    color: #374151;
    transform: translateY(-1px);
}*/

.no-image-icon {
    font-size: 3rem;
    opacity: 0.6;
    color: #9ca3af;
}

.mud-picker-inline {
    overflow: auto !important;
    max-height: 40px !important;
}

.mud-input-root-text {
    padding: 4px 0 4px !important;
}

.mud-icon-button {
    padding: 5px !important;
    border-radius: 6px;
    transition: background-color 0.2s ease;
}

    .mud-icon-button:hover {
        background-color: #f3f4f6;
    }


.map-layer-input {
    float: left;
    margin-right: 5px;
}

.sub-layer-div {
    margin-left: 20px;
}

.select-with-map-search-control {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-style: none !important;
}

.select-with-map-search-overlay-host {
    position: relative !important;
}

.select-with-map-search-loading-overlay {
    position: absolute;
    inset: 0;
    z-index: 1200;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    background: rgba(0, 0, 0, 0.45);
    color: #ffffff;
    font-weight: 600;
}

/* ── Message modals ───────────────────────────────────────────────────
   Shared body language for the three message modals (inbox, detail, compose).
   Built on the theme tokens so light and dark both work, and matching the other
   AMX modals: bordered surfaces, muted supporting text, `.dbd-btn` buttons. */
.msg-title {
    display: inline-flex;
    align-items: flex-start;
    gap: 0.6rem;
    min-width: 0;
}

.msg-title > i {
    margin-top: 0.2rem;
    color: var(--accent-primary);
}

.msg-heading {
    display: flex;
    flex-direction: column;
    line-height: 1.3;
    min-width: 0;
    font-weight: 600;
}

.msg-subtitle {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 0.8rem;
    font-weight: 400;
    color: var(--text-muted);
}

.msg-count {
    flex: 0 0 auto;
    padding: 0.15rem 0.6rem;
    border-radius: 999px;
    background-color: var(--bg-secondary);
    border: 1px solid var(--border-color);
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--text-secondary);
}

.msg-count.is-unread {
    background-color: var(--accent-wash);
    border-color: rgba(var(--accent-primary-rgb), 0.35);
    color: var(--accent-primary);
}

.msg-body-stack {
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
    min-height: 0;
}

.msg-toolbar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.6rem;
}

.msg-search {
    flex: 1 1 16rem;
    max-width: 22rem;
}

.msg-search .input-group-text {
    background-color: var(--bg-secondary);
    border-color: var(--border-color);
    color: var(--text-muted);
}

.msg-loading {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 3rem 1rem;
}

.msg-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    padding: 2.5rem 1rem;
    text-align: center;
    color: var(--text-muted);
}

.msg-empty i {
    font-size: 1.6rem;
    opacity: 0.6;
}

.msg-empty span {
    font-weight: 600;
    color: var(--text-secondary);
}

/* Bordered surface used for tables and grouped content. */
.msg-panel {
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background-color: var(--bg-card);
    overflow: auto;
}

.msg-table {
    width: 100%;
    margin: 0;
    font-size: 0.85rem;
    border-collapse: separate;
    border-spacing: 0;
}

.msg-table thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    padding: 0.55rem 0.85rem;
    background-color: var(--bg-secondary);
    border-bottom: 1px solid var(--border-color);
    font-size: 0.72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--text-muted);
    white-space: nowrap;
}

.msg-table tbody td {
    padding: 0.5rem 0.85rem;
    border-bottom: 1px solid var(--border-light);
    vertical-align: middle;
    color: var(--text-primary);
}

.msg-table tbody tr:last-child td {
    border-bottom: 0;
}

.msg-table tbody tr:hover td {
    background-color: var(--bg-hover);
}

/* Unread rows are marked with an accent edge and weight rather than by dimming
   everything that has been read. */
.msg-table tbody tr.is-unread td:first-child {
    box-shadow: inset 3px 0 0 0 var(--accent-primary);
}

.msg-table tbody tr.is-unread td {
    font-weight: 600;
}

.msg-priority {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.05rem 0.45rem;
    border-radius: 999px;
    background-color: rgba(239, 68, 68, 0.12);
    color: var(--accent-danger);
    font-size: 0.72rem;
    font-weight: 600;
    white-space: nowrap;
}

/* Label / value pairs, as used by the message detail view. */
.msg-meta {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 0.75rem;
    padding: 0.85rem;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background-color: var(--bg-secondary);
}

@media (min-width: 576px) {
    .msg-meta {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

.msg-meta-item {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    min-width: 0;
}

.msg-meta-label {
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
}

.msg-meta-value {
    overflow-wrap: anywhere;
    color: var(--text-primary);
}

/* A missing value reads as a quiet placeholder rather than a blank gap. */
.msg-meta-value.is-empty,
.msg-meta-value .is-empty {
    color: var(--text-muted);
    font-style: italic;
}

.msg-note {
    min-height: 50px;
    padding: 0.85rem;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background-color: var(--bg-card);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    color: var(--text-primary);
}

/* Nothing to show: keep the block at its normal size and say so, rather than
   collapsing to an empty bordered strip. */
.msg-note.is-empty {
    display: flex;
    align-items: center;
    color: var(--text-muted);
    font-style: italic;
}

/* Grouped form sections in the compose modal. */
.msg-section {
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background-color: var(--bg-card);
    overflow: hidden;
}

.msg-section-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.55rem 0.85rem;
    background-color: var(--bg-secondary);
    border-bottom: 1px solid var(--border-color);
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--text-primary);
}

.msg-section-body {
    padding: 0.85rem;
}

/* ── Map control modals ───────────────────────────────────────────────
   Shared body styling for the map control modals (search, layers, filters and
   What3Words). They render inside AmxModal, so the shell, buttons and inputs
   come from the app's Bootstrap + `.dbd-btn` language; this only lays out the
   content. Colours are taken from the theme variables so both themes work. */
.map-modal {
    display: flex;
    flex-direction: column;
    gap: 1rem;

    /* Takes the height it is given rather than the height of its contents, so a panel's footer
       sits on the bottom of the panel instead of riding up under a short list. Where the host
       has no height of its own to give - a dialog sized to its content - this computes to auto
       and nothing changes. */
    height: 100%;
    min-height: 0;

    /* The width the layer columns are measured against - see .map-modal-list-packed. */
    container-type: inline-size;
    container-name: map-modal;
}

/* Nothing in the body is squashed to make it fit. A flex column with a height of its own shrinks
   its children by default, and the cards clip what does not fit (they hide their overflow for
   their own rounded corners) - which is how a settings card came to have its title cut off along
   the top and its hint along the bottom. The body scrolls instead. */
.map-modal > * {
    flex-shrink: 0;
}

.map-modal-title {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
}

.map-modal-title i {
    color: var(--accent-primary);
}

.map-modal-lead {
    margin: 0;
    color: var(--text-secondary);
    font-size: 0.875rem;
}

.map-modal-field {
    display: flex;
    flex-direction: column;
}

.map-modal-field .form-text {
    color: var(--text-muted);
}

.map-modal-list {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    overflow-y: auto;
}

@media (min-width: 768px) {
    .map-modal-list-columns {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        align-items: start;
    }
}

/* AmxSelect and the rest of the shared inputs are written against the --dbd-* tokens, which are
   only declared inside the designer pages that introduced them - everywhere else the control
   falls back to its own literals, and those literals are light. That is why a profile picker on
   a dark map came out white on white. Mapped here from the app's theme tokens, which do follow
   the theme, so every input inside a map panel is themed wherever it is shown.

   The proper home for this is a global declaration alongside the theme tokens; until then it is
   at least declared where the inputs actually are. */
.map-modal,
.map-layers-panel {
    --dbd-surface: var(--bg-card);
    --dbd-fg: var(--text-primary);
    --dbd-muted: var(--text-muted);
    --dbd-border-strong: var(--border-color);
    --dbd-subtle: var(--bg-secondary);
    --dbd-accent: var(--accent-primary);
}

/* The map layers panel (MapLayersPanel). Shown two ways - pinned into the map, where
   .map-layers-dock positions it, or as the body of the layers dialog - so it carries its own
   frame and drops it again inside the dialog, which already has one. Not in the map's scoped
   stylesheet because the dialog is not rendered inside the map. */
.map-layers-panel {
    display: flex;
    flex-direction: column;
    min-height: 0;
    width: 100%;

    /* Narrower than this and the two-column rows in the panel body (label + control, the opacity
       slider, the profile picker) start wrapping onto each other. */
    min-width: 290px;
    background-color: var(--bg-card);
    border: 1px solid var(--border-color);
    border-radius: 8px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
    overflow: hidden;
}

.map-layers-panel-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    border-bottom: 1px solid var(--border-color);
    background-color: var(--bg-secondary);
}

.map-layers-panel-head-actions {
    display: flex;
    align-items: center;
    gap: 0.25rem;
}

.map-layers-panel-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    background: none;
    border: none;
    border-radius: 6px;
    color: var(--text-secondary);
    cursor: pointer;
}

.map-layers-panel-btn:hover {
    background-color: var(--bg-hover, rgba(0, 0, 0, 0.05));
    color: var(--text-primary);
}

/* The pin, while the panel is on the map. */
.map-layers-panel-btn.is-on {
    color: var(--accent-primary);
    transform: rotate(45deg);
}

/* The body scrolls, the head and the actions do not - so the profile picker and Show all / Hide
   all stay reachable however many layers the map has. It also takes the space the head and foot
   leave, which is what holds the foot on the bottom of the panel: without it a panel with two
   layers in it had its actions half way up. */
.map-layers-panel > .map-modal {
    flex: 1 1 auto;
    min-height: 0;
    padding: 0.75rem;
    overflow-y: auto;
}

/* The layers dialog's header row - the title, and the pin that moves the whole panel onto the
   map. AmxModal wraps header content in its own title element, so the row is made here. */
.map-layers-modal-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    width: 100%;
}

/* Inside the panel the body is the one thing that scrolls. The list must not also be a
   bounded-height scroller: a multi-column block with a definite height puts what does not fit
   into further columns to the RIGHT, which is why the pinned panel showed a horizontal
   scrollbar and layer rows that could not be reached. Left to grow, it scrolls with the body. */
.map-layers-panel .map-modal-list {
    overflow: visible;
}

.map-layers-panel > .map-modal {
    overflow-x: hidden;
}

.map-layers-panel-foot {
    display: flex;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    border-top: 1px solid var(--border-color);
    background-color: var(--bg-secondary);
}

/* In the dialog the panel is the whole body, so it draws none of its own frame - the dialog
   supplies the surface, and a panel inside a panel reads as a mistake. */
.modal-body .map-layers-panel {
    border: none;
    border-radius: 0;
    box-shadow: none;
    background: none;
    overflow: visible;

    /* And fills the body, so the panel's own actions sit on the bottom rather than under the
       last setting - the same reason the body flexes above. */
    height: 100%;
}

.modal-body .map-layers-panel-head,
.modal-body .map-layers-panel-foot {
    background: none;
}

.modal-body .map-layers-panel > .map-modal {
    padding: 0;
}

/* Bootstrap is never told the app's theme (no data-bs-theme), so the controls it styles from its
   own variables stay light wherever the app is dark. .form-control and .form-select are already
   corrected above; the range and the switch are not, and the settings panel is built out of both.
   Corrected here for the map panels rather than app-wide - restyling every switch in the app is a
   bigger change than this, and belongs with the global --dbd-* declaration. The heat map panel is
   built out of the same range and joins them here rather than correcting it a second time. */
.map-modal .form-range,
.map-layers-panel .form-range,
.heatmap-settings-panel .form-range {
    background: transparent;
}

.map-modal .form-range::-webkit-slider-runnable-track,
.map-layers-panel .form-range::-webkit-slider-runnable-track,
.heatmap-settings-panel .form-range::-webkit-slider-runnable-track {
    background-color: var(--border-color);
}

.map-modal .form-range::-moz-range-track,
.map-layers-panel .form-range::-moz-range-track,
.heatmap-settings-panel .form-range::-moz-range-track {
    background-color: var(--border-color);
}

.map-modal .form-range::-webkit-slider-thumb,
.map-layers-panel .form-range::-webkit-slider-thumb,
.heatmap-settings-panel .form-range::-webkit-slider-thumb {
    background-color: var(--accent-primary);
}

.map-modal .form-range::-moz-range-thumb,
.map-layers-panel .form-range::-moz-range-thumb,
.heatmap-settings-panel .form-range::-moz-range-thumb {
    background-color: var(--accent-primary);
}

/* An unchecked switch is a white pill from Bootstrap, which on a dark panel is the brightest
   thing on it - and reads as "on" at a glance, which is the opposite of what it means. */
[data-theme="dark"] .map-modal .form-check-input,
[data-theme="dark"] .map-layers-panel .form-check-input {
    background-color: var(--bg-input);
    border-color: var(--border-color);
}

[data-theme="dark"] .map-modal .form-check-input:checked,
[data-theme="dark"] .map-layers-panel .form-check-input:checked {
    background-color: var(--accent-primary);
    border-color: var(--accent-primary);
}

/* The knob and the tick are drawn as background images with a colour baked in, so they need
   restating or the knob stays dark-on-dark. */
[data-theme="dark"] .map-modal .form-switch .form-check-input,
[data-theme="dark"] .map-layers-panel .form-switch .form-check-input {
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2398a2b8'/%3e%3c/svg%3e");
}

[data-theme="dark"] .map-modal .form-switch .form-check-input:checked,
[data-theme="dark"] .map-layers-panel .form-switch .form-check-input:checked {
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23ffffff'/%3e%3c/svg%3e");
}

/* The map settings panel (MapSettingsPanel). Settings are grouped into flat section cards -
   surface, hairline border, radius, a header row of accent icon + title + optional hint - which
   is the app's own settings-page language (see DESIGN_LANGUAGE Sectioned settings), rather than
   a run of rows separated by rules. Four short cards say what each group is for without the user
   having to work it out from the settings inside it. */
.map-settings-card {
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background-color: var(--bg-card);
    overflow: hidden;
}

.map-settings-card-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    border-bottom: 1px solid var(--border-light);
    background-color: var(--bg-secondary);
}

/* The small solid-accent square the app's section headers carry. */
.map-settings-card-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: var(--radius-sm);
    background-color: var(--accent-primary);
    color: #ffffff;
    font-size: 0.7rem;
}

.map-settings-card-title {
    font-weight: 600;
    color: var(--text-primary);
}

.map-settings-card-hint {
    flex: 1 1 100%;
    color: var(--text-muted);
    font-size: 0.75rem;
}

.map-settings-card-body {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    padding: 0.75rem;
}

/* One setting: its label, its control, and whatever has to be said about it. Stacked rather than
   label-beside-control, so a long label does not squeeze the control to nothing in the pinned
   panel's narrow column. */
.map-settings-field {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

/* A slider's label and its current value on one line - a slider with no readout is a guess. */
.map-settings-field-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
}

/* The words at the ends of the icon size slider - the setting is how much is added to the
   icon's natural size, so it words rather than numbers its range: Small, Medium, Large. */
.map-settings-range-ends {
    display: flex;
    justify-content: space-between;
    margin-top: -0.15rem;
    color: var(--text-muted);
    font-size: 0.6875rem;
}

.map-settings-label {
    margin: 0;
    color: var(--text-primary);
    font-size: 0.8125rem;
    font-weight: 600;
}

.map-settings-value {
    color: var(--text-secondary);
    font-size: 0.8125rem;
    font-variant-numeric: tabular-nums;
}

/* Switches sit together on a shelf, which is what separates them from the fields around them. */
.map-settings-shelf {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    padding: 0.6rem 0.75rem;
    border-radius: var(--radius-sm);
    background-color: var(--bg-secondary);
}

/* A control that belongs to the option above it. */
.map-settings-sub {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    padding-left: 1.5rem;
}

.map-settings-number {
    width: 7rem;
}

/* Why a setting is there, under the setting. Not a tooltip: these are the ones a user is most
   likely to get wrong, and a hint they have to hover to find is a hint they will not read. */
.map-settings-hint {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.75rem;
    line-height: 1.35;
}

/* ── The map settings panel (MapSettingsPanel), as grouped rows ──

   Each section is a quiet heading over one group of rows on the card surface, hairlines between
   the rows; each row is the setting's words on the left and its control on the right (a switch, a
   select), or, for a slider, the words over the control's full width. A row that only applies
   while the switch above it is on is indented on a tinted ground, so what depends on what can be
   read at a glance. This replaced a card per section, each with its own header band and an accent
   square, which spent most of a narrow panel on chrome.

   The sections flow into columns - as many 260px columns as the panel has room for, up to three -
   so the same panel is one column pinned beside the map and two or three dropped down over a wide
   one, where most of the settings are in view without scrolling. CSS columns rather than a grid:
   the sections are different heights, and columns flow the next one into the space under a short
   one instead of leaving a gap the height of its neighbour. */
.msp-sections {
    columns: 260px 3;
    column-gap: 1.25rem;
}

/* A tab with one section in it (Linear Features, Grouping) gives it the whole width instead of leaving it in the
   first of three columns beside two empty ones. */
.msp-sections-single {
    columns: auto;
}

.msp-section {
    display: flow-root;
    break-inside: avoid;
    margin-bottom: 1.25rem;
}

.msp-section:last-child {
    margin-bottom: 0;
}

.msp-title {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0 0 0.5rem;
    color: var(--text-secondary);
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
}

.msp-title i {
    width: 1em;
    text-align: center;
}

.msp-section-hint {
    margin: -0.25rem 0 0.5rem;
    color: var(--text-muted);
    font-size: 0.75rem;
    line-height: 1.35;
}

.msp-group {
    background-color: var(--bg-card);
    border: 1px solid var(--border-color);
    border-radius: 10px;
    overflow: hidden;
}

.msp-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 0.5rem 0.75rem;
    min-height: 44px;
    margin: 0;
    padding: 0.55rem 0.75rem;
}

.msp-row + .msp-row {
    border-top: 1px solid var(--border-light);
}

/* A slider, or a segmented choice: the words above, the control the full width under them. */
.msp-row-stack {
    flex-direction: column;
    align-items: stretch;
    gap: 0.4rem;
}

.msp-row-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
}

/* Only there while the switch above it is on. */
.msp-row-sub {
    padding-left: 1.75rem;
    background-color: var(--bg-secondary);
}

.msp-row-text {
    display: flex;
    flex: 1 1 11rem;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.msp-label {
    margin: 0;
    color: var(--text-primary);
    font-size: 0.8125rem;
    font-weight: 550;
    line-height: 1.3;
}

.msp-row > .msp-label {
    flex: 1 1 10rem;
    min-width: 0;
}

/* In a stacked row the flex direction is a column, where that basis would be a height. */
.msp-row-stack > .msp-label {
    flex: none;
}

.msp-hint {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.75rem;
    font-weight: 400;
    line-height: 1.35;
}

.msp-value {
    color: var(--text-secondary);
    font-size: 0.8125rem;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* A select or a number at the end of a row. Drops under the words when the row is too narrow
   for both, rather than squeezing either. */
.msp-control {
    flex: 0 1 11rem;
    min-width: 9rem;
}

.msp-control > *,
.msp-number {
    width: 100%;
}

/* Bootstrap's switch, at the end of the row instead of in front of its label. */
.msp-row.form-switch {
    padding-left: 0.75rem;
}

/* A switch that only applies while the one above it is on keeps the sub-row's indent. */
.msp-row-sub.form-switch {
    padding-left: 1.75rem;
}

.msp-row.form-switch .form-check-input {
    flex: none;
    float: none;
    width: 2.25em;
    height: 1.25em;
    margin: 0;
    cursor: pointer;
}

.msp-row.form-switch > .msp-label {
    cursor: pointer;
}

.msp-range-ends {
    display: flex;
    justify-content: space-between;
    margin-top: -0.15rem;
    color: var(--text-muted);
    font-size: 0.6875rem;
}

/* An either/or setting as the app's segmented control, on native radios: the inputs clipped (not
   display:none, which would drop them from the tab order), the chosen one drawn from :checked.

   A grid of equal columns rather than a flex row of flex: 1 1 0 options. In the stacked row (a
   column flexbox) a flex row's height is measured as if each option were squeezed to its
   narrowest, so "While the map is busy" was given two or three lines of height while drawing on
   one. The clipped inputs are absolutely positioned, so they take no column. */
.msp-segmented {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: minmax(0, 1fr);
    gap: 2px;
    padding: 3px;
    background-color: var(--bg-secondary);
    border: 1px solid var(--border-color);
    border-radius: 8px;
}

.msp-segmented-input {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
}

.msp-segmented-option {
    margin: 0;
    padding: 0.35rem 0.5rem;
    border-radius: 6px;
    color: var(--text-secondary);
    font-size: 0.8125rem;
    font-weight: 550;
    text-align: center;
    cursor: pointer;
}

.msp-segmented-option:hover {
    color: var(--text-primary);
}

.msp-segmented-input:checked + .msp-segmented-option {
    background-color: var(--bg-card);
    color: var(--accent-link);
    box-shadow: var(--shadow-sm);
}

/* In dark the card surface is barely off the track, so the chosen segment is lifted a step more. */
[data-theme="dark"] .msp-segmented-input:checked + .msp-segmented-option {
    background-color: var(--bg-tertiary);
}

.msp-segmented-input:focus-visible + .msp-segmented-option {
    outline: 3px solid var(--accent-primary);
    outline-offset: -1px;
}

.msp-foot {
    align-items: center;
    justify-content: space-between;
}

.msp-foot-note {
    color: var(--text-muted);
    font-size: 0.75rem;
}

/* Pinned, the note wraps rather than the button. */
.msp-foot .btn {
    flex: none;
    white-space: nowrap;
}

.msp-foot-note {
    text-align: right;
}

/* Under the title: whose settings these are and where they are kept - every map the user opens,
   and the map profile that is on. A quiet band on the secondary ground, the profile as a chip. */
.msp-scope {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.375rem 0.625rem;
    padding: 0.5rem 0.75rem;
    background-color: var(--bg-secondary);
    border-bottom: 1px solid var(--border-light);
    color: var(--text-secondary);
    font-size: 0.6875rem;
    line-height: 1.35;
}

.msp-scope-text,
.msp-scope-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
}

.msp-scope-chip {
    padding: 2px 0.5rem;
    background-color: var(--bg-card);
    border: 1px solid var(--border-color);
    border-radius: 999px;
    color: var(--text-primary);
    font-weight: 550;
}

.msp-scope-chip i {
    color: var(--accent-link);
}

/* The tabs, by topic: the app's segmented track, the chosen tab lifted onto the card surface.
   Each takes an equal share, so four fit the pinned panel's 290px without scrolling. */
.msp-tabs {
    display: flex;
    gap: 2px;
    margin: 0.75rem 0.75rem 0;
    padding: 3px;
    background-color: var(--bg-secondary);
    border: 1px solid var(--border-color);
    border-radius: 9px;
}

/* Sized to their labels, so "Linear Features" fits beside the short ones in the 380px panel. */
.msp-tab {
    flex: 1 1 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    min-width: 0;
    padding: 0.4rem 0.4rem;
    background: none;
    border: none;
    border-radius: 6px;
    color: var(--text-secondary);
    font-size: 0.8125rem;
    font-weight: 550;
    white-space: nowrap;
    cursor: pointer;
}

.msp-tab:hover {
    color: var(--text-primary);
}

.msp-tab[aria-selected="true"] {
    background-color: var(--bg-card);
    color: var(--accent-link);
    box-shadow: var(--shadow-sm);
}

/* As the segmented control: in dark the card is barely off the track, so the chosen tab is lifted a step more. */
[data-theme="dark"] .msp-tab[aria-selected="true"] {
    background-color: var(--bg-tertiary);
}

.msp-tab:focus-visible {
    outline: 3px solid var(--accent-primary);
    outline-offset: -1px;
}

/* On a group's heading, what on the map it changes: your items, the layers, the base map. Back to
   sentence case and normal tracking inside the uppercase heading, pushed to its far end. */
.msp-applies {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    margin-left: auto;
    padding: 1px 0.45rem;
    background-color: var(--bg-secondary);
    border: 1px solid var(--border-color);
    border-radius: 999px;
    color: var(--text-secondary);
    font-weight: 550;
    letter-spacing: 0;
    text-transform: none;
}

/* Labels: the items' and the layers' side by side where there is room for both, one over the
   other pinned. A grid rather than the sections' columns, so the two start level. */
.msp-split {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: 1.25rem;
    align-items: start;
}

.msp-split > .msp-section {
    margin-bottom: 0;
}

/* A button that takes the user to the panel that now holds a setting - Map layers' way to the
   space between layer labels, on Map settings' Labels tab. */
.msp-link-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    height: 30px;
    padding: 0 0.625rem;
    background-color: var(--bg-card);
    border: 1px solid var(--border-color);
    border-radius: 8px;
    color: var(--text-primary);
    font-size: 0.8125rem;
    font-weight: 550;
    white-space: nowrap;
    cursor: pointer;
}

.msp-link-btn:hover {
    background-color: var(--bg-hover);
}

.msp-link-btn i {
    color: var(--text-secondary);
    font-size: 0.75rem;
}

.map-modal-settings-link {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem 0.75rem;
    padding: 0.5rem 0.75rem;
    background-color: var(--bg-card);
    border: 1px solid var(--border-color);
    border-radius: 10px;
}

.map-modal-settings-link-text {
    flex: 1 1 10rem;
    min-width: 0;
    color: var(--text-secondary);
    font-size: 0.75rem;
    line-height: 1.35;
}

/* A band over the layers list: the base map picker in the layers panel. Sits above the list as a
   band of its own so the switches below stay one uninterrupted field of the same thing. Wraps
   rather than squeezes - on a narrow panel the hint drops under the picker. (Named for the profile
   bar it was first drawn for, which has moved to its own panel - MapProfilePanel.) */
.map-modal-profiles {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem 0.75rem;
    padding: 0.6rem 0.75rem;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    background-color: var(--bg-secondary);
}

/* The map profiles (MapProfilePanel): one pressable row per profile, the one on the map ticked
   and washed with the accent, the note on what happens to changes under them, and the actions in
   the panel's foot. */
.map-profile-list {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin: 0;
    padding: 4px;
    list-style: none;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    background-color: var(--bg-card);
}

.map-profile-option {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    min-height: 36px;
    padding: 0.4rem 0.6rem;
    border: none;
    border-radius: 6px;
    background: transparent;
    color: var(--text-primary);
    font-size: 0.875rem;
    text-align: left;
    cursor: pointer;
}

.map-profile-option:hover:not(:disabled) {
    background-color: var(--bg-hover);
}

.map-profile-option:focus-visible {
    outline: 3px solid var(--accent-primary);
    outline-offset: -1px;
}

.map-profile-option:disabled {
    cursor: progress;
}

.map-profile-option.is-selected {
    background-color: var(--accent-wash);
    font-weight: 600;
}

.map-profile-check {
    display: inline-flex;
    flex: 0 0 1rem;
    justify-content: center;
    color: var(--accent-link);
}

.map-profile-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.map-profile-meta {
    flex: 0 0 auto;
    color: var(--text-secondary);
    font-size: 0.75rem;
    font-weight: 400;
}

/* On the chosen row's accent wash the secondary ink falls short of 4.5:1 at this size. */
.map-profile-option.is-selected .map-profile-meta {
    color: var(--text-primary);
}

.map-profile-note {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    margin: 0.75rem 0 0;
    color: var(--text-secondary);
    font-size: 0.8125rem;
    line-height: 1.45;
}

.map-profile-note > i {
    margin-top: 0.2rem;
    color: var(--text-muted);
}

.map-profile-foot {
    flex-wrap: wrap;
}

/* Changes kept in no profile: the warning edge, the way back under the words. */
.map-profile-unsaved {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    margin-bottom: 0.75rem;
    padding: 0.6rem 0.75rem;
    border: 1px solid var(--border-color);
    border-left: 3px solid var(--accent-warning);
    border-radius: var(--radius-sm);
    background-color: var(--bg-secondary);
    color: var(--text-primary);
    font-size: 0.8125rem;
    line-height: 1.45;
}

.map-profile-unsaved > i {
    margin-top: 0.2rem;
    color: var(--accent-warning-text);
}

.map-profile-unsaved-text {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.5rem;
}

/* The profile panel is narrower than the layers panel, so it does not hold to that one's floor. */
.map-layers-panel.map-profile-panel {
    min-width: 0;
}

/* One line of explanation, on its own row under the picker - a base map is the one control here
   whose effect (the whole map is rebuilt, the layer list changes) is not obvious from its label. */
.map-modal-source-hint {
    flex: 1 1 100%;
    margin: 0;
    font-size: var(--text-xs, 0.6875rem);
    color: var(--text-muted);
}

.map-modal-profile-pick {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-width: 0;
    flex: 1 1 16rem;
}

.map-modal-profile-pick .map-modal-profile-select {
    flex: 1 1 auto;
    min-width: 10rem;
    max-width: 22rem;
}

.map-modal-profile-label {
    margin: 0;
    white-space: nowrap;
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--text-secondary);
}

.map-modal-profile-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}

/* The layers list packs its group cards into as many columns as the dialog can give them -
   up to three - so a map with a lot of layers can be read without scrolling for it.
   Multi-column rather than a grid because the cards are all different heights: a grid row is
   as tall as its tallest card and leaves a gap under every other one, where columns simply
   flow the next card into the space. break-inside keeps a card whole. */
.map-modal-list-packed {
    display: block;
    column-gap: 0.75rem;
    column-count: 1;
}

.map-modal-list-packed > * {
    -webkit-column-break-inside: avoid;
    break-inside: avoid;
    margin-bottom: 0.75rem;
}

/* Fallback for a browser without container queries: the window's width is the only thing it can
   be asked about. Superseded by the container rules below wherever they are understood. */
@media (min-width: 768px) {
    .map-modal-list-packed {
        column-count: 2;
    }
}

@media (min-width: 1200px) {
    .map-modal-list-packed {
        column-count: 3;
    }
}

/* What actually decides the column count is how wide the dialog is, not how wide the window is -
   a narrow dialog on a wide screen has no more room for three columns than a phone does. The
   band is measured against the modal body (the container above), and the max-width rule is there
   to take back what the media queries above would otherwise have granted on width the cards
   never had. */
@container map-modal (max-width: 34rem) {
    .map-modal-list-packed {
        column-count: 1;
    }
}

@container map-modal (min-width: 34rem) {
    .map-modal-list-packed {
        column-count: 2;
    }
}

@container map-modal (min-width: 52rem) {
    .map-modal-list-packed {
        column-count: 3;
    }
}

.map-modal-group {
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    background-color: var(--bg-card);
    overflow: hidden;
}

.map-modal-group-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.6rem 0.75rem;
    background-color: var(--bg-secondary);
    border-bottom: 1px solid var(--border-color);
}

.map-modal-group-body {
    display: flex;
    flex-direction: column;
    padding: 0.25rem 0.75rem 0.5rem;
}

.map-modal-group-single .map-modal-row {
    padding: 0.6rem 0.75rem;
}

.map-modal-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    min-height: 2rem;
    padding: 0.35rem 0;
}

.map-modal-row + .map-modal-row {
    border-top: 1px solid var(--border-light);
}

.map-modal-row .form-check {
    min-width: 0;
}

/* A group inside a group (MapLayersPanel.RenderSubGroup) - an asset layer's asset type: a fold-away
   heading with its own switch and count, its layers indented under a rule. */
.map-modal-subgroup {
    padding: 0.15rem 0;
}

.map-modal-row + .map-modal-subgroup,
.map-modal-subgroup + .map-modal-row,
.map-modal-subgroup + .map-modal-subgroup {
    border-top: 1px solid var(--border-light);
}

.map-modal-subgroup-head {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-height: 2rem;
    padding: 0.2rem 0;
}

.map-modal-subgroup-head .form-check {
    flex: 1 1 auto;
    min-width: 0;
}

.map-modal-subgroup-head .form-check-label {
    font-weight: 600;
}

.map-modal-disclose {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    padding: 0;
    background: none;
    border: none;
    border-radius: 6px;
    color: var(--text-secondary);
    cursor: pointer;
}

.map-modal-disclose:hover {
    background-color: var(--bg-hover);
    color: var(--text-primary);
}

.map-modal-disclose:focus-visible {
    outline: 2px solid var(--accent-primary);
    outline-offset: 1px;
}

.map-modal-disclose i {
    transition: transform 0.15s ease;
}

.map-modal-disclose[aria-expanded="true"] i {
    transform: rotate(90deg);
}

@media (prefers-reduced-motion: reduce) {
    .map-modal-disclose i {
        transition: none;
    }
}

.map-modal-subgroup-body {
    margin: 0 0 0.35rem 0.75rem;
    padding-left: 0.6rem;
    border-left: 2px solid var(--border-color);
}

/* A layer's or group's icon (its asset type's or sub type's), before its name. */
.map-layer-icon {
    display: inline-flex;
    margin-right: 0.4rem;
    vertical-align: -3px;
}

.map-layer-icon img {
    width: 16px;
    height: 16px;
}

/* When a layer with a zoom range shows, under its name (MapLayersPanel.ZoomRangeText). */
.map-layer-zoom-note {
    display: block;
    font-size: 0.75rem;
    line-height: 1.35;
    color: var(--text-secondary);
}

/* The layers the user added to the map (MapAddLayerModal), listed above the map's own. */
.map-added-layers {
    margin-bottom: 0.75rem;
}

.map-added-title {
    font-weight: 600;
    color: var(--text-primary);
}

/* Add a layer leads the panel's footer; Show all and Hide all keep to the right. */
.map-layers-add-btn {
    margin-right: auto;
}

/* The end of a layer's row: the zoom hint, then the cog that opens the layer's options. */
.map-layer-row-end {
    display: flex;
    flex: none;
    align-items: center;
    gap: 0.25rem;
}

.map-layer-options-btn[aria-expanded="true"] {
    background-color: var(--bg-secondary);
    color: var(--text-primary);
}

.map-layer-options-btn:focus-visible {
    outline: 2px solid var(--accent-primary);
    outline-offset: 1px;
}

/* A layer's options, under its row: the map settings panel's rows (.msp-row), on the tint the
   design language gives a block that belongs to the row above it. */
.map-layer-options {
    margin: 0 0 0.5rem;
    border: 1px solid var(--border-light);
    border-radius: var(--radius-sm);
    background-color: var(--bg-secondary);
    overflow: hidden;
}

.map-modal-group-single .map-layer-options {
    margin: 0 0.75rem 0.6rem;
}

.map-layer-options .msp-row {
    min-height: 40px;
    padding: 0.45rem 0.65rem;
}

.map-layer-options .msp-row.form-switch {
    padding-left: 0.65rem;
}

/* --border-light all but vanishes on the tint, so the rows are ruled with the stronger border. */
.map-layer-options .msp-row + .msp-row {
    border-top-color: var(--border-color);
}

.map-layer-options .msp-row-sub {
    padding-left: 1.5rem;
    background-color: var(--bg-card);
}

/* The "Show from" setting's made-up map (zoomPreview.js), drawn at the zoom the slider is on. */
.map-zoom-preview {
    position: relative;
    overflow: hidden;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm, 8px);
}

.map-zoom-preview-map {
    display: block;
    width: 100%;
    height: 7.5rem;
}

.map-zoom-preview-caption {
    position: absolute;
    left: 0.375rem;
    bottom: 0.375rem;
    max-width: calc(100% - 0.75rem);
    padding: 0.125rem 0.5rem;
    border-radius: 999px;
    background: var(--bg-card);
    color: var(--text-primary);
    font-size: 0.6875rem;
    box-shadow: var(--shadow-sm);
}

.map-modal .form-check-label {
    cursor: pointer;
    color: var(--text-primary);
}

.map-modal-filter-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.map-modal-filter-icon {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.25rem;
    height: 1.25rem;
}

.map-modal-filter-icon img {
    width: 1.25rem;
    height: 1.25rem;
    object-fit: contain;
}

.map-modal-swatch {
    flex: 0 0 auto;
    width: 0.85rem;
    height: 0.85rem;
    border-radius: 3px;
    border: 1px solid rgba(0, 0, 0, 0.15);
}

.map-modal-count {
    white-space: nowrap;
    font-size: 0.75rem;
    color: var(--text-muted);
}

/* --accent-link rather than the vivid --accent-primary: this is small text, and the vivid blue
   falls short of 4.5:1 as text on the header's tint. */
.map-modal-count.is-partial {
    color: var(--accent-link);
    font-weight: 600;
}

.map-modal-hint {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    white-space: nowrap;
    font-size: 0.75rem;
    color: var(--text-muted);
}

.map-modal-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
    padding: 2rem 1rem;
    text-align: center;
    color: var(--text-muted);
}

.map-modal-empty i {
    font-size: 1.5rem;
    opacity: 0.6;
}

.map-modal-card {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 1rem;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background-color: var(--bg-secondary);
}

.map-modal-card-label {
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
}

.map-modal-words {
    font-size: 1.25rem;
    font-weight: 600;
    word-break: break-word;
    color: var(--accent-primary);
}

.map-modal-result {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    padding: 0.5rem 0.65rem;
    text-align: left;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    background-color: var(--bg-card);
    color: var(--text-primary);
    font-size: 0.875rem;
}

.map-modal-result + .map-modal-result {
    margin-top: 0.35rem;
}

.map-modal-result:hover,
.map-modal-result:focus-visible {
    border-color: var(--accent-primary);
}

.map-modal-result i {
    flex: 0 0 auto;
    color: var(--accent-primary);
}

.map-modal-divider {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    font-size: 0.75rem;
    text-transform: uppercase;
    color: var(--text-muted);
}

.map-modal-divider::before,
.map-modal-divider::after {
    content: "";
    flex: 1 1 auto;
    height: 1px;
    background-color: var(--border-color);
}

/* Modal-style centered popover for map controls.
   Uses an extremely high z-index so it always sits above any Bootstrap
   modal / modal-backdrop / popover defaults, even when launched from
   inside an AmxModal (e.g. the Change Location dialog). */
.map-control-popover {
    position: fixed !important;
    inset: auto !important;
    top: 50% !important;
    left: 50% !important;
    transform: translate(-50%, -50%) !important;
    margin: 0 !important;
    max-width: 90vw !important;
    min-width: min(320px, 90vw);
    max-height: 80vh !important;
    z-index: 2147483647 !important; /* max 32-bit int */
    isolation: isolate;
    border-radius: var(--radius-lg) !important;
    border: 1px solid var(--border-color) !important;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12) !important;
    background-color: var(--bg-card) !important;
    color: var(--text-primary) !important;
}

/* The list of what is under a cluster (buildClusterListContent). One row per item, each a plain
   full-width target: at this point the user is picking between things that were drawn on top of
   one another, so the rows want to be easy to hit and easy to tell apart. The list scrolls
   inside the popover, which is already capped at the viewport. */
.map-cluster-list {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.map-cluster-list-title {
    font-weight: 600;
    color: var(--text-secondary);
}

.map-cluster-list-items {
    display: flex;
    flex-direction: column;
    max-height: 45vh;
    overflow-y: auto;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
}

.map-cluster-list-item {
    padding: 0.5rem 0.75rem;
    background: none;
    border: none;
    border-radius: 0;
    color: var(--text-primary);
    text-align: left;
    cursor: pointer;
}

.map-cluster-list-item + .map-cluster-list-item {
    border-top: 1px solid var(--border-light);
}

.map-cluster-list-item:hover,
.map-cluster-list-item:focus-visible {
    background-color: var(--bg-secondary);
}

.map-cluster-list-more {
    font-size: 0.8125rem;
    color: var(--text-muted);
}

.map-control-popover .popover-arrow {
    display: none !important;
}

.map-control-popover .popover-header {
    background-color: var(--bg-secondary) !important;
    color: var(--text-primary) !important;
    border-bottom: 1px solid var(--border-color) !important;
    font-weight: 600;
}

.map-control-popover .popover-body {
    max-height: 90vh;
    overflow: auto;
    padding: 1rem;
    color: var(--text-primary) !important;
}

.map-control-popover label {
    color: var(--text-primary);
}

[data-theme="dark"] .map-control-popover .btn-close {
    filter: invert(1) grayscale(100%) brightness(200%);
}

/* Semi-transparent backdrop behind modal popovers.
   Sits above Bootstrap modal (1055) / modal-backdrop (1050) but below
   .map-control-popover so the map layer popup is never hidden by it. */
.map-control-backdrop {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0);
    z-index: 2147483646 !important;
    transition: background 0.2s ease;
}

.map-control-backdrop.show {
    background: rgba(0, 0, 0, 0.5);
}

.content {
    padding-top: 1.1rem;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #10b981;
}

.invalid {
    outline: 1px solid #ef4444;
}

.validation-message {
    color: #ef4444;
}

.validation-messages-wrapper {
    width: 100%;
    display: block;
    margin-top: 10px;
}

    .validation-messages-wrapper ul {
        list-style: none;
    }

#blazor-error-ui {
    background: var(--bg-card);
    color: var(--text-primary);
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
    border-top: 3px solid var(--accent-warning-text);
}

    #blazor-error-ui .dismiss {
        cursor: pointer;
        position: absolute;
        right: 0.75rem;
        top: 0.5rem;
        color: var(--text-primary);
    }

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #dc2626;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
    border-radius: 6px;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.status-bar-safe-area {
    display: none;
}

.maincontent {
    display: flex;
    flex-direction: column;
    height: 100%;
    width: 100%;
    overflow: auto;
    z-index: auto !important;
    background-color: #f8fafc;
}

.node-item .expand-icon-container .chevron:after {
    transition: 0.2s transform;
    content: ' ';
    display: inline-block;
    border-bottom: 1px solid #6b7280;
    border-right: 1px solid #6b7280;
    height: 10px;
    width: 10px;
    transform: rotate(-45deg);
}

.node-item .expand-icon-container .chevron-collapse:after {
    transition: 0.3s transform;
    content: ' ';
    display: inline-block;
    border-bottom: 1px solid #6b7280;
    border-right: 1px solid #6b7280;
    height: 10px;
    width: 10px;
    transform: rotate(45deg);
}

.node-item-child-container {
    padding: 5px 0px 0px 10px;
    transition: all 0.3s;
    animation: expandDiv 0.3s;
}

    .node-item-child-container .node-item.node-level-1 {
        padding-left: 20px;
    }

    .node-item-child-container .node-item.node-level-2 {
        padding-left: 40px;
    }

    .node-item-child-container .node-item.node-level-3 {
        padding-left: 60px;
    }

    .node-item-child-container .node-item.node-level-4 {
        padding-left: 80px;
    }

.item-parent-wrapper {
    padding-bottom: 2px;
}

.node-item {
    display: flex;
    gap: 0;
    align-items: center;
    width: 100%;
    font-size: 0.8rem;
    border-radius: 4px;
    transition: background-color 0.2s ease;
}

    .node-item:hover {
        background-color: #f1f5f9;
    }

    .node-item .node-item-content {
        display: flex;
        gap: 5px;
        width: 100%;
        height: auto;
        cursor: pointer;
    }

    /* Accessibility: focus styles for keyboard navigation */
    .node-item:focus-within {
        outline: 2px solid #2563eb;
        outline-offset: 2px;
    }

    .node-item .node-item-content:focus {
        outline: none;
    }

    /* Dark mode hover fix */
    [data-theme="dark"] .node-item:hover {
        background-color: var(--bg-hover, #2d2d5a);
    }

    [data-theme="dark"] .node-item:hover .node-item-content {
        color: var(--text-primary, var(--text-primary));
    }

    .node-item .checkbox-number-container {
        display: flex;
        align-items: center;
    }

        .node-item .checkbox-number-container > input {
            height: 25px !important;
            accent-color: #2563eb;
        }

    .node-item img {
        height: 15px !important;
        width: auto !important;
    }

    .node-item .expand-icon-container {
        width: 40px;
        padding: 15px;
        display: flex;
        align-items: center;
        justify-content: center;
        border-radius: 4px;
        transition: background-color 0.2s ease;
        cursor: pointer;
    }

        .node-item .expand-icon-container:hover {
            background-color: #e2e8f0;
        }

        /* Dark mode expand icon hover */
        [data-theme="dark"] .node-item .expand-icon-container:hover {
            background-color: var(--bg-tertiary, #0f3460);
        }

.ml-auto {
    margin-left: auto;
}

/* The label track of every DataView field. 200px is what lines labels up down a form, but it is a
   cap rather than a fixed width: past about 440px wide the track is the full 200px and forms look
   as they always have, and below that the label gives ground instead of squeezing the control - at
   a 220px column a fixed track left the control 15px of it. The label wraps, so what it loses in
   width it takes in lines. */
:root {
    --amx-label-track: min(200px, 45%);

    /* The width below which a control stops sharing a line with its label - see .amx-field-row.
       A control narrower than this is not a control any more, it is a sliver. */
    --amx-field-basis: 220px;
}

/* ── Action status colours ───────────────────────────────────────────────────────────────
   What a scheduled, started, overdue, completed or missed action looks like. Shared by the action
   matrix (one asset over time) and the action overview (many assets over the same time), because a
   status has to mean the same thing and look the same on both.

   These are data, not theme - a status means the same thing in either - but each still needs a
   value that clears 3:1 as a meaningful graphic on its own surface, which is why dark re-points
   them rather than reusing the light values. Tuned in the action matrix originally; lifted here so
   there is one definition rather than a copy per screen. */
:root {
    --amx-action-ok: #15803d;
    --amx-action-info: #1d4ed8;
    --amx-action-warn: #a4490a;
    --amx-action-danger: #c4201f;
}

[data-theme="dark"] {
    --amx-action-ok: #4ade80;
    --amx-action-info: #7aa5ff;
    --amx-action-warn: #f0a04b;
    --amx-action-danger: #ff6b6b;
}

/* ── A tab strip's phone form: a picker ──────────────────────────────────────────────────
   Both levels of tab on a record - the form tabs and the section tabs above them - render their
   strip and this select, and only ever show one of them. On a desktop that is the strip; on a
   phone the strips wrap to four or five rows between them and cost most of the screen before the
   first field appears, so below 1000px (175% browser zoom on a laptop) they become two pickers, one row each.

   A native select rather than a custom menu: it opens the platform's own picker, which is one tap
   on a phone, and it arrives keyboard operable, labelled and announced without any of that being
   rebuilt. display:none on the other one keeps it out of the accessibility tree too, so a screen
   reader is never offered the same tabs twice. */
.amx-tab-select {
    display: none;
}

@media (max-width: 1000px) {
    .amx-tab-select {
        display: block;
        width: 100%;
        /* 44px declared, which paints at about 40 once the app scale on body is applied - well
           clear of the 24px WCAG 2.2 asks for, and the same size as every other control here,
           which is scaled by the same amount. */
        min-height: 44px;
        padding: 8px 12px;
        font-size: 1rem;
        color: var(--text-primary);
        background-color: var(--bg-input);
        border: 1px solid var(--border-color);
        border-radius: var(--radius-md, 6px);
    }

    .amx-tab-select:focus-visible {
        outline: 2px solid var(--border-focus, var(--accent-primary));
        outline-offset: 2px;
    }
}

/* ── One field: label beside the control, or above it when there is no room ──────────────
   Every DataView field - text, number, date, lookup, checkbox, file, GUID, photo, diagram,
   location, rich text - is a label and a control, and each one used to lay that out with its own
   copy of the same two-column grid. Eleven copies of one idea. This is the rule they now share:
   put .amx-field-row on the wrapper and the field lays itself out.

   It is a flex row, not a grid and not a container query. The label takes the label track and
   the control the rest. When the field gets too narrow for both - the label under 110px or the
   control under 140px - js/amxFormFit.js marks the row [data-amx-stacked] and it lays out exactly
   like .wrap-label: label above, control below at the full width. Nothing clipped, nothing to
   scroll (ticket 2600209), and the decision is made per field rather than per viewport, which
   matters because these fields sit in form columns that are often narrow on a wide screen. A
   media query would ask about the window and get the wrong answer. (Plain flex-wrap made the same
   decision, but on the flex bases - 200px + 220px - so fields stacked at widths that held them
   comfortably, which is why it was turned off when zooming spilled stacked fields over their rows.)

   A container query would ask about the right box, but container-type: inline-size also applies
   `contain`, and that makes the field a containing block for anything absolutely positioned
   inside it. The lookup browser's picker and the field context menu both hang off these wrappers
   and would be trapped inside a 200px field. Hence a script, which needs no containment. */
.amx-field-row {
    display: flex;
    flex-wrap: nowrap;
    /* Alignment is left at stretch: the tall fields - photo, diagram, rich text - fill the row,
       as they did when this was a grid with stretched tracks. */
    gap: 5px;
    width: 100%;
}

.amx-field-row > .data-view-label {
    flex: 0 1 var(--amx-label-track, 200px);
    /* A flex row of its own so the title can be clamped (below) with the tooltip button still
       beside it - in inline flow the button followed the last line and was clipped with it. */
    display: flex;
    align-items: flex-start;
    gap: 0;
}

/* Each column can use less than the default track when all of its ordinary labels are short.
   The measured width includes tooltip/requirement icons; the existing track remains the cap. */
.amx-field-row:not(.wrap-label):not(.free-label-width):not([data-amx-stacked]) > .data-view-label[data-amx-label-width] {
    flex: 0 0 min(var(--amx-label-content-width), var(--amx-label-track, 200px));
    min-width: 0;
}

/* As many lines as fit beside the control, then an ellipsis - so the label never makes the row
   taller than its control. The count comes from js/amxFormFit.js, which measures the control.
   (Not contain: size on the label to take it out of the row's height - a contained label is zero
   tall wherever the row does not stretch it, and those labels vanished.) text-overflow on the
   label never did this: ellipsis only applies to a single line, and a pre-wrap label simply ran
   on until the row cut it off mid-line.
   The full title stays in the accessible name and in the title tooltip. */
.amx-field-row > .data-view-label > span:first-child {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: var(--amx-label-lines, 1);
    line-clamp: var(--amx-label-lines, 1);
    overflow: hidden;
    overflow-wrap: anywhere;
    min-width: 0;
}

/* Stacked above its control the label has its own line and no row height to fit: two lines. */
.amx-field-row:is(.wrap-label, [data-amx-stacked]) > .data-view-label > span:first-child {
    -webkit-line-clamp: 2;
    line-clamp: 2;
}

/* Whatever is not the label is the control. It fills what the label leaves, or the full width
   once it has wrapped below it. */
.amx-field-row > :not(.data-view-label) {
    flex: 1 1 var(--amx-field-basis, 220px);
    min-width: 0;
}

/* LabelFeeWidth (Stretch) gives just this field a natural-width label. Its neighbours keep
   their shared track. amxFormFit stacks it when the full label and a usable control cannot fit. */
.amx-field-row.free-label-width:not(.wrap-label):not([data-amx-stacked]) > .data-view-label {
    flex: 0 0 auto;
    min-width: 0;
    max-width: none;
    white-space: nowrap;
}

.amx-field-row.free-label-width > .data-view-label > span:first-child {
    display: block;
    white-space: nowrap;
}

/* On narrow screens, and with Label Above, keep the whole label readable above the control. */
.amx-field-row.free-label-width:is(.wrap-label, [data-amx-stacked]) > .data-view-label > span:first-child {
    white-space: normal;
    overflow-wrap: anywhere;
}

/* Validation reads as a line under the field, never a third column beside it. Photo, diagram and
   location (and the phone's file upload) put the field and its messages in .amx-field-stack, as
   the text box does with utility classes: the row fills what the cell has left and the messages
   take their own height. In the row itself the messages were a third column squeezing the
   control, because this row does not wrap. */
.amx-field-stack {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    width: 100%;
    height: 100%;
    min-height: 0;
}

.amx-field-stack > .amx-field-row {
    flex: 1 1 auto;
    min-height: 0;
}

.amx-field-stack > .validation-messages-wrapper {
    flex: 0 0 auto;
}

/* A control that is its own size rather than the width of its field - a checkbox. It still starts
   where every other control on the form starts, because the label keeps its track. */
.amx-field-row.field-inline > :not(.data-view-label) {
    flex: 0 0 auto;
}

.data-view-label {
    gap: 0.5rem;
    padding: 0;
    font-size: var(--amx-label-font-size, 0.875rem);
    line-height: 1.35;
    text-wrap: wrap;
    /* Inside an .amx-field-row the label's width is the label track and this floor is overridden,
       so what is left for it are the labels that sit outside one: grid headers, the inline grid
       editors, the map summary. It gives way rather than pushing the label past whatever space
       there is. A flat 200px was doing the opposite - in a narrow column it forced the label wider
       than its own field, and .component-container's overflow:auto turned that into a scrollbar. */
    min-width: min(200px, 100%);
    white-space: pre-wrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

[data-font-size="large"] .data-view-label {
    font-size: calc(var(--amx-label-font-size, 0.875rem) * 1.125);
}

[data-font-size="large"] .data-view-label .requirement-level-txt,
[data-font-size="large"] .data-view-label .tooltip-icon-container {
    font-size: inherit;
}

.data-grid-header .data-view-label {
    width: 100%;
    padding: 5px !important;
}

/* Stacked because the form says so, rather than because the field ran out of room. The two
   declarations are for the two kinds of wrapper this class lands on: the inline grid editors,
   which are flex columns of their own, and anything still laid out as a two-column grid, where
   label-above-field is one track rather than two. */
.wrap-label,
.amx-field-row[data-amx-stacked] {
    flex-direction: column !important;
    grid-template-columns: 1fr !important;
}

/* An .amx-field-row is already a flex row, so flex-direction above turns it into a column - but
   then the bases set for a row would be read as heights, and wrapping would break the field into
   columns rather than lines. Size on content instead and let the control take the height that is
   left, which is what a stacked field should look like. */
.amx-field-row:is(.wrap-label, [data-amx-stacked]) {
    flex-wrap: nowrap !important;
}

.amx-field-row:is(.wrap-label, [data-amx-stacked]) > .data-view-label {
    flex: 0 0 auto !important;
}

.amx-field-row:is(.wrap-label, [data-amx-stacked]) > :not(.data-view-label) {
    flex: 1 1 auto !important;
}

/* Stacked by the form rather than the designer: the field's column got too narrow to hold the label
   beside a usable control, so amxFormFit.js moved the label above it. A checkbox keeps its size -
   it is the label that needed the room, not the box. */
.amx-field-row.field-inline[data-amx-stacked] {
    align-items: flex-start;
}

.amx-field-row.field-inline[data-amx-stacked] > :not(.data-view-label) {
    flex: 0 0 auto !important;
}

/* A control configured without a label: there is no label to give the first track to, so the field
   takes the whole width rather than starting 200px in. Only grids need telling - in an
   .amx-field-row the label element is simply absent (DataViewTitleTemplate renders nothing for an
   empty title), which leaves the control as the only item, already full width. */
.no-label {
    grid-template-columns: 1fr !important;
}

.wrap-label > .data-view-label,
.amx-field-row[data-amx-stacked] > .data-view-label {
    min-width: 0;
    width: auto;
    max-width: 100%;
    overflow-wrap: anywhere;
}

.data-view-label .tooltip-icon-container {
    /* A stretched flex item painted the glyph at the top of the label box, leaving the icon
       sitting above the text. Pin it to one line of label text and centre it there instead, so
       it reads as part of the label - including when the label wraps to a second line. */
    display: inline-flex;
    align-items: center;
    align-self: flex-start;
    height: 1.35em;
    /* It is a button so it can be reached and announced, but it should read as part of the label,
       so the browser's button chrome is stripped back to the bare glyph. */
    padding: 0;
    background: none;
    border: 0;
    color: inherit;
    font: inherit;
    line-height: inherit;
    cursor: pointer;
    margin-left: 5px;
}

.data-view-label .tooltip-icon-container:focus-visible {
    outline: 2px solid var(--accent-primary, #3b82f6);
    outline-offset: 2px;
    border-radius: 3px;
}

.data-view-label .tooltip-icon-container > i {
    /* Slightly smaller than the label text so it supports the label rather than competing with it. */
    font-size: 0.85em;
}

.sub-tabs-container .tabs-wrapper {
    background-color: #ffffff !important;
    padding: 5px;
    border-radius: 6px;
    border: 1px solid #e5e7eb;
}

#map-summary-html .components-wrapper {
    display: block;
    width: 100%;
    height: 100%;
    overflow: auto;
}

    #map-summary-html .components-wrapper .component-container {
        padding: 0.5em;
        overflow: auto;
    }

#map-summary-html {
    max-height: 800px;
    overflow: auto;
    min-width: 800px;
}

    #map-summary-html .wrap-label > .label {
        max-width: 100px !important;
        min-width: 100px !important;
    }

    #map-summary-html .form-control {
        min-height: 40px !important;
        overflow: auto;
        border-radius: var(--radius-md);
        background-color: var(--bg-input);
        color: var(--text-primary);
        border: 1px solid var(--border-color);
        transition: border-color 0.2s ease, box-shadow 0.2s ease;
    }

        #map-summary-html .form-control:focus {
            border-color: var(--accent-primary);
            box-shadow: 0 0 0 3px rgba(var(--accent-primary-rgb), 0.15);
            outline: none;
        }

    #map-summary-html .controls-container {
        display: grid;
        gap: 5px;
        width: 100%;
        height: 100%;
    }

    /* The pop-up shows the photo's thumbnail, about 200px high, so it is shown no taller than
       that. The form bounds a photo by the rows it spans, but that does not reach the pop-up: a
       photo spanning the summary's columns grew with the pop-up's width (16/9, ~430px), blowing the
       thumbnail up past its own pixels and pushing Open off the bottom of a short screen. */
    #map-summary-html .photo-wrapper .amx-photo > img {
        max-height: 200px;
    }

@supports (-webkit-touch-callout: none) {
    .status-bar-safe-area {
        display: flex;
        position: sticky;
        top: 0;
        height: env(safe-area-inset-top);
        background-color: #f1f5f9;
        width: 100%;
        z-index: 1;
    }

    .flex-column, .navbar-brand {
        padding-left: env(safe-area-inset-left);
    }
}

@keyframes fadeIn {
    0% {
        opacity: 0.1;
    }

    100% {
        opacity: 1;
    }
}

@keyframes toggleSidebar {
    0% {
        opacity: 0;
        visibility: hidden;
    }

    100% {
        opacity: 1;
        visibility: visible;
    }
}


.requirement-level-txt {
    display: inline-flex;
    align-items: center;
    padding-left: 10px;
}

.requirement-level-txt.required {
    color: #dc2626 !important;
}

.requirement-level-txt.recommended {
    color: #ea580c !important;
}

/* ========== SCROLLBAR STYLES ========== */

/* Keep the browser's native dimensions and interactions. These theme tokens already meet
   3:1 non-text contrast on the card surface; never hide the bars or force thin hit targets.
   Modern browsers render the thumb using their platform's scrollbar shape. */
* {
    scrollbar-width: auto;
    scrollbar-color: var(--border-interactive) var(--bg-card);
}

.amx-nodes-scroll:hover,
.table-container:hover {
    scrollbar-color: var(--text-secondary) var(--bg-card);
}

/* Older WebKit browsers: rounded thumbs in a comfortably sized native scroll track.
   Leave touch and system high-contrast rendering to the browser. */
@supports not (scrollbar-color: auto) {
    @media (forced-colors: none) and (pointer: fine) {
        ::-webkit-scrollbar {
            width: 16px;
            height: 16px;
        }

        ::-webkit-scrollbar-track,
        ::-webkit-scrollbar-corner {
            background: var(--bg-card);
        }

        ::-webkit-scrollbar-thumb {
            background: var(--border-interactive);
            border: 3px solid var(--bg-card);
            border-radius: 999px;
        }

        ::-webkit-scrollbar-thumb:hover {
            background: var(--text-secondary);
        }
    }
}

@media (forced-colors: active) {
    *, .amx-nodes-scroll:hover, .table-container:hover {
        scrollbar-color: auto;
        scrollbar-width: auto;
    }
}

/* ========== END SCROLLBAR STYLES ========== */

/* ============================================================
   Blazor reconnect modal — custom, theme-aware UI.
   By providing our own element with id="components-reconnect-modal"
   (see App.razor), Blazor uses it directly and only toggles state
   classes on it, instead of rendering its default Shadow-DOM dialog
   (whose internals external CSS — including dark mode — cannot reach).
   All colours come from the shared theme tokens, so it themes for free.
   State classes toggled by the framework: -show / -retrying / -paused /
   -resume-failed (reconnecting) and -failed / -rejected (give up); -hide.
   ============================================================ */
#components-reconnect-modal {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 20000;
    align-items: center;
    justify-content: center;
    background-color: color-mix(in srgb, var(--bg-primary, #000) 60%, transparent);
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
}

#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-retrying,
#components-reconnect-modal.components-reconnect-paused,
#components-reconnect-modal.components-reconnect-resume-failed,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected {
    display: flex;
}

#components-reconnect-modal.components-reconnect-hide {
    display: none;
}

/* Once the session has expired (sessionExpiry.js) the session-expired dialog is the only way out, so
   the reconnect modal stands aside for it rather than covering it. */
html.amx-session-expired #components-reconnect-modal {
    display: none;
}

/* In the top layer while it shows (reconnectHelper.js), above the map's menus and any modal dialog:
   the browser's own popover box - border, padding, a fitted size, centred margins - stepped out of,
   so it is the same full-screen overlay it is otherwise. */
#components-reconnect-modal:popover-open {
    width: auto;
    height: auto;
    max-width: none;
    max-height: none;
    margin: 0;
    padding: 0;
    border: 0;
    overflow: visible;
    color: inherit;
}

#components-reconnect-modal .reconnect-dialog {
    width: 360px;
    max-width: 90%;
    padding: 2rem 2.25rem;
    text-align: center;
    background-color: var(--bg-card);
    color: var(--text-primary);
    border: 1px solid var(--border-color);
    border-radius: 1rem;
    box-shadow: var(--shadow-lg);
}

/* The app's circle spinner (.amx-spinner, end of this file), sized and placed for the modal. */
#components-reconnect-modal .reconnect-loader {
    --amx-spinner-size: 44px;
    display: block;
    margin: 0 auto 1rem;
}

#components-reconnect-modal .reconnect-rejoin-text {
    margin: 0 0 0.35rem;
    font-size: 1rem;
    font-weight: 600;
}

#components-reconnect-modal .reconnect-attempt-text {
    margin: 0;
    font-size: 0.85rem;
    color: var(--text-secondary);
}

#components-reconnect-modal .reconnect-failed-text {
    display: none;
    margin: 0 0 0.25rem;
    font-size: 1rem;
    font-weight: 600;
    color: var(--text-primary);
}

#components-reconnect-modal .reconnect-actions {
    display: none;
    gap: 0.5rem;
    justify-content: center;
    margin-top: 1rem;
}

#components-reconnect-modal .reconnect-btn {
    padding: 8px 16px;
    font-size: 0.85rem;
    font-weight: 550;
    border-radius: 8px;
    border: 1px solid transparent;
    cursor: pointer;
    background-color: var(--accent-primary);
    color: #ffffff;
    transition: filter 0.12s ease, background-color 0.12s ease;
}

#components-reconnect-modal .reconnect-btn:hover {
    filter: brightness(1.05);
}

#components-reconnect-modal .reconnect-btn-secondary {
    background-color: transparent;
    border-color: var(--border-color);
    color: var(--text-primary);
}

#components-reconnect-modal .reconnect-btn-secondary:hover {
    background-color: var(--bg-hover);
    filter: none;
}

/* Give-up states: swap the spinner + "rejoining" copy for an error message + actions. */
#components-reconnect-modal.components-reconnect-failed .reconnect-loader,
#components-reconnect-modal.components-reconnect-rejected .reconnect-loader,
#components-reconnect-modal.components-reconnect-resume-failed .reconnect-loader,
#components-reconnect-modal.components-reconnect-failed .reconnect-rejoin-text,
#components-reconnect-modal.components-reconnect-rejected .reconnect-rejoin-text,
#components-reconnect-modal.components-reconnect-resume-failed .reconnect-rejoin-text,
#components-reconnect-modal.components-reconnect-failed .reconnect-attempt-text,
#components-reconnect-modal.components-reconnect-rejected .reconnect-attempt-text,
#components-reconnect-modal.components-reconnect-resume-failed .reconnect-attempt-text {
    display: none;
}

#components-reconnect-modal.components-reconnect-failed .reconnect-failed-text,
#components-reconnect-modal.components-reconnect-rejected .reconnect-failed-text,
#components-reconnect-modal.components-reconnect-resume-failed .reconnect-failed-text {
    display: block;
}

#components-reconnect-modal.components-reconnect-failed .reconnect-actions,
#components-reconnect-modal.components-reconnect-rejected .reconnect-actions,
#components-reconnect-modal.components-reconnect-resume-failed .reconnect-actions {
    display: flex;
}

/* ===== Left sidebar: keep Pinned panel + Filter fixed, scroll only the node list ===== */
.amx-left-scroll {
    display: flex;
    flex-direction: column;
    min-height: 0;
    height: 100%;
    overflow: hidden;
}

/* The node list card grows to fill remaining space; its internal list scrolls. */
.amx-left-scroll .amx-nodes-card {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}

/* The toolbar strip and the filter bar (input + pin button) stay pinned at the top of the node card. */
.amx-left-scroll .amx-nodes-card > .amx-actions,
.amx-left-scroll .amx-nodes-card .amx-filter-bar {
    flex: 0 0 auto;
}

/* Only the nodes themselves scroll. */
.amx-nodes-scroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;
}

/* Keep the navigation focus ring inside the title row's clipped bounds. */
.amx-shell .amx-tile-title button:focus-visible {
    outline-offset: -2px;
}

/* Home and Settings reflow: browser zoom reduces the CSS viewport just like resizing the window.
   Release the viewport-height chain when space is limited, so navigation and reports grow
   with their content and the document owns vertical scrolling. Designer shells retain their
   canvas layout. Use the unscaled coordinate space here: a transform-scaled, auto-height body
   would leave a blank scroll tail and make document/fixed-overlay coordinates disagree. */
@media (max-width: 1000px), (max-height: 600px) {
    html:has(.amx-shell, .settings-shell) {
        --amx-ui-scale: 1;
        height: auto;
        overflow-x: clip;
        overflow-y: auto;
    }

    html:has(.amx-shell, .settings-shell) body {
        transform: none;
        width: 100%;
        height: auto;
        min-height: 100vh;
        overflow: visible;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) {
        height: auto;
        min-height: 100vh;
        overflow: visible;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .amx-body,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .amx-content,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .amx-content-scroll,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .amx-left,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .amx-left-scroll,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .amx-nodes-card,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .amx-nodes-scroll {
        flex: 0 0 auto;
        height: auto;
        max-height: none;
        overflow: visible;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .amx-content {
        flex: 1 1 auto;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .amx-left-scroll.peek {
        position: relative;
        inset: auto;
        width: 100%;
        animation: none;
        box-shadow: none;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .amx-left:has(.peek) {
        width: min(var(--amx-left-width, 400px), 35%);
        min-width: 0;
        max-width: 35%;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .navigation-controls {
        flex-wrap: wrap;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .navigation-controls {
        min-width: 0;
        max-width: 100%;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .navigation-controls .form-select {
        min-width: 0;
        height: auto;
        min-height: 38px;
        padding-block: var(--space-2, .5rem);
        white-space: normal;
        overflow-wrap: anywhere;
    }

    /* The selected report view: its name in body ink, its icon in the accent green, as at full width. */
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .report-toolbar .tab-item.active,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .report-toolbar .tab-item.active span:not(.tab-item-count) {
        color: var(--text-primary) !important;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .report-toolbar .tab-item.active i {
        color: var(--accent-success-text) !important;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .amx-tab-btn.active,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .dbd-btn.dbd-success,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .data-view-page .btn-success {
        background: var(--accent-success-fill) !important;
        border-color: var(--accent-success-fill) !important;
        color: var(--text-on-dark) !important;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .report-list-page,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .report-list-view,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .report-list-items,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .dashboard-item,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .dashboard-base-item,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .dashboard-base-item > .card-body,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .recent-assets-items-wrapper,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .recent-assets-list,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .dashboard-report-list,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .dashboard-report-grid .data-grid-container,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .dashboard-report-grid .data-grid-content {
        flex: 0 0 auto;
        height: auto;
        min-width: 0;
        overflow: visible;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .dashboard-report-grid .table-container {
        height: auto;
        overflow-x: auto;
        scrollbar-gutter: auto;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .dashboard-report-header,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .dashboard-report-header-actions {
        flex-wrap: wrap;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .dashboard-items-container {
        display: flex;
        flex-direction: column;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .recent-assets-list {
        grid-template-columns: minmax(0, 1fr);
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .recent-item {
        min-width: 0;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .recent-item-name,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .recent-item-parent-name {
        white-space: normal;
        overflow-wrap: anywhere;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .dashboard-base-item .empty-state {
        background-color: var(--bg-card);
        color: var(--text-primary);
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .dashboard-base-item .empty-subtext {
        color: var(--text-muted) !important;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .dashboard-report-action-btn {
        background-color: var(--bg-secondary) !important;
        border-color: var(--border-interactive) !important;
        color: var(--text-primary) !important;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .report-base-container,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .tab-page-wrapper:not(.fullscreen-mode):not(:fullscreen),
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .tab-page-wrapper:not(.fullscreen-mode):not(:fullscreen) .report-tab-content-wrapper,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .tab-page-wrapper:not(.fullscreen-mode):not(:fullscreen) .report-data-page-container,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .tab-page-wrapper:not(.fullscreen-mode):not(:fullscreen) .report-data-page-container .data-grid-container,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .tab-page-wrapper:not(.fullscreen-mode):not(:fullscreen) .report-data-page-container .data-grid-content {
        flex: 0 0 auto;
        height: auto;
        overflow: visible;
        min-width: 0;
    }

    /* A horizontal flex workspace otherwise gives its non-shrinking child the grid's intrinsic
       width. Bound the content panel so wide columns scroll inside the table at high zoom. */
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .tab-page-wrapper:not(.fullscreen-mode):not(:fullscreen) .report-tab-content-wrapper {
        width: 100%;
        max-width: 100%;
    }

    /* Only tables need sideways scrolling. Auto height leaves their full row/spacer height
       in the page flow, including the virtualised rows, rather than a second vertical viewport. */
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .tab-page-wrapper:not(.fullscreen-mode):not(:fullscreen) .report-data-page-container .table-container {
        height: auto;
        overflow-x: auto;
        scrollbar-gutter: auto;
    }

    /* A map/chart has an intrinsic working height; its accompanying data must not be squeezed
       into the saved split-pane size. Stack the panes and let the data grow below the canvas. */
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .tab-page-wrapper:not(.fullscreen-mode):not(:fullscreen) .amx-split-layout {
        flex: 0 0 auto;
        flex-direction: column;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .tab-page-wrapper:not(.fullscreen-mode):not(:fullscreen) .amx-split-primary {
        flex: 0 0 auto;
        width: 100%;
        min-width: 0;
        height: max(20rem, var(--amx-split-height, 340px));
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .tab-page-wrapper:not(.fullscreen-mode):not(:fullscreen) .amx-split-secondary,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .tab-page-wrapper:not(.fullscreen-mode):not(:fullscreen) .amx-split-secondary .data-grid-container,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .tab-page-wrapper:not(.fullscreen-mode):not(:fullscreen) .amx-split-secondary .data-grid-content {
        flex: 0 0 auto;
        width: 100%;
        min-width: 0;
        height: auto;
        overflow: visible;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .tab-page-wrapper:not(.fullscreen-mode):not(:fullscreen) .amx-split-secondary .table-container {
        height: auto;
        overflow-x: auto;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .tab-page-wrapper:not(.fullscreen-mode):not(:fullscreen) .amx-split-resizer {
        display: none;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .report-toolbar .tab-list,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .report-toolbar .toolbar-btn-group,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .report-data-page-container .header-controls,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .report-data-page-container .selectors-wrapper,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .report-data-page-container .view-toggle-buttons,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .report-data-page-container .toolbar-btn-group {
        flex-wrap: wrap;
        height: auto;
        max-width: 100%;
        overflow: visible;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .report-toolbar .toolbar-btn-group > .toolbar-btn,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .report-data-page-container .view-btn,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .report-data-page-container .grid-actions-btn,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .report-data-page-container .toolbar-btn-group .btn {
        min-height: 36px;
        height: auto;
        flex-shrink: 0;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .report-data-page-container .selectors-wrapper {
        margin-left: 0;
        justify-content: flex-start;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .grid-header-cell {
        background-color: var(--bg-secondary) !important;
        color: var(--text-primary) !important;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .grid-header-cell .header-text {
        color: inherit !important;
    }
}

/* The collapsed strip's contents are a row (chevron beside the title) at normal size and a column
   (chevron over the title) when zoomed in. This is the height half; the width half is in the
   max-width block below. */
@media (max-height: 600px) {
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .amx-left-collapsed {
        flex-direction: row;
        justify-content: flex-start;
        align-items: flex-start;
    }
}

@media (max-width: 1000px) {
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .amx-body {
        flex-direction: column;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .amx-left,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .amx-left.collapsed,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .amx-left:has(.peek) {
        width: 100%;
        min-width: 0;
        max-width: 100%;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .amx-left-resizer {
        display: none;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .amx-left-collapsed {
        flex-direction: row;
        justify-content: flex-start;
        align-items: flex-start;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .amx-left-collapsed-breadcrumb-text {
        writing-mode: horizontal-tb;
        transform: none;
        white-space: normal;
        overflow-wrap: anywhere;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .amx-left-rail-title {
        justify-content: flex-start;
        align-self: center;
        padding: 0 8px;
        text-align: left;
    }


    /* Utility actions must wrap as well when additional tenant-specific buttons are enabled. */
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .amx-topbar,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .amx-topbar-right .menu-icons {
        flex-wrap: wrap;
    }
}


/* Settings uses the same document scroll, including its navigation above the page. */
@media (max-width: 1000px), (max-height: 600px) {
    .settings-shell :is(.settings-body, .settings-column, .settings-sidebar, .settings-main, .settings-content) {
        flex: 0 0 auto;
        height: auto;
        min-height: 0;
        max-height: none;
        min-width: 0;
        overflow: visible;
    }

    .settings-shell .settings-column {
        flex: 1 1 auto;
    }

    .settings-shell :is(.settings-topbar, .settings-topbar-right) {
        flex-wrap: wrap;
        min-width: 0;
    }

    .settings-shell .settings-topbar-center {
        flex: 1 1 16rem;
    }

    .settings-shell .settings-search {
        min-width: 0;
        max-width: 100%;
    }

    .settings-shell :is(.nav-item, .nav-page, .nav-item-label, .nav-page-label) {
        white-space: normal;
        overflow-wrap: anywhere;
    }

    .settings-shell .nav-page.active {
        color: var(--accent-success-text);
    }
}

@media (max-width: 1000px) {
    .settings-shell .settings-body {
        flex-direction: column;
    }

    .settings-shell .settings-sidebar {
        width: 100%;
        border-right: none;
        border-bottom: 1px solid var(--border-color);
    }
}

/* Root record editors share document scrolling with Home and Settings at high zoom.
   Hosted record sessions and dialogs retain their own viewport and scrolling. */
@media (max-width: 1000px), (max-height: 600px) {
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .data-view-page,
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .data-view-page :is(
        .data-view-content, .data-view-main, .data-view-main-content,
        .left-panel, .left-panel-body,
        .section-tab-page-wrapper, .section-tab-content-wrapper,
        .inner-section-tab-content-wrapper, .data-tab-page-wrapper,
        .data-tab-content-wrapper, .inner-data-tab-content-wrapper,
        .dataview-controls-wrapper, .components-wrapper, .sub-tabs-container,
        .validation-error-list, .amx-grid-wrapper, .amx-grid-content,
        .amx-grid-wrapper.folder-mode .amx-grid-main,
        .amx-grid-wrapper.folder-mode .amx-grid-folder-pane, .grid-folder-tree,
        .amx-grid-content .data-grid-container, .amx-grid-content .data-grid-content,
        .amx-grid-content .data-grid-wrapper) {
        flex: 0 0 auto;
        height: auto !important;
        max-height: none;
        min-width: 0;
        overflow: visible !important;
        position: relative;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .data-view-page .data-view-main {
        flex: 1 1 auto;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .data-view-page .components-wrapper > .d-grid {
        height: auto !important;
        grid-template-rows: none !important;
        grid-auto-rows: minmax(48px, max-content) !important;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .data-view-page .component-container {
        min-width: 0;
        height: auto !important;
        max-height: none !important;
        contain: none !important;
        overflow: visible !important;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .data-view-page :is(
        .main-toolbar-item-container, .main-toolbar-left-items-container,
        .main-toolbar-right-items-container, .header-controls, .selectors-wrapper,
        .view-toggle-buttons, .toolbar-btn-group, .grid-toolbar-container,
        .data-tab-list, .section-tab-list, .grid-tab-list) {
        flex-wrap: wrap;
        height: auto !important;
        max-width: 100%;
        min-width: 0;
        overflow: visible !important;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .data-view-page .main-toolbar-left-items-container {
        flex: 1 1 20rem;
        width: auto !important;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .data-view-page .main-toolbar-right-items-container {
        flex: 0 1 auto;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .data-view-page .data-view-title-block {
        flex: 1 1 12rem;
        max-width: 100%;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .data-view-page :is(
        .breadcrumb-inner-div, .breadcrumb-link, .breadcrumb-current,
        .related-assets__link, .related-assets__name) {
        min-width: 0;
        max-width: 100%;
        white-space: normal;
        overflow-wrap: anywhere;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .data-view-page .related-assets__name {
        color: var(--accent-success-text);
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .data-view-page .selectors-wrapper {
        margin-left: 0;
        justify-content: flex-start;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .data-view-page :is(.view-btn, .grid-actions-btn, .grid-toolbar-container button) {
        min-height: 32px;
        flex-shrink: 0;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .data-view-page .amx-grid-content .table-container {
        height: auto;
        overflow-x: auto;
        scrollbar-gutter: auto;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .data-view-page .amx-grid-wrapper {
        background-color: var(--bg-card);
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .data-view-page .amx-grid-wrapper .empty-subtext {
        color: var(--text-muted) !important;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .data-view-page .main-toolbar-item-container .dbd-info {
        background-color: var(--bg-secondary) !important;
        border-color: var(--border-interactive) !important;
        color: var(--text-primary) !important;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .data-view-page .amx-grid-folder-pane {
        resize: none;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .data-view-page .grid-folder-tree {
        width: 100%;
        overflow-wrap: anywhere;
        background-color: var(--bg-card);
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .data-view-page :is(.grid-folder-label, .grid-folder-count) {
        color: var(--text-primary);
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .data-view-page .grid-folder-name {
        white-space: normal;
    }
}

@media (max-width: 1000px) {
    /* Release saved row/column spans as well as the tracks: a single column must keep
       the designer's DOM order without placing neighbouring controls on top of each other. */
    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .data-view-page .components-wrapper > .d-grid {
        grid-template-columns: minmax(0, 1fr) !important;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .data-view-page .components-wrapper > .d-grid > .component-container {
        grid-column: 1 / -1 !important;
        grid-row: auto !important;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .data-view-page .amx-field-row:not(.field-inline) {
        flex-wrap: wrap;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .data-view-page .amx-field-row:not(.field-inline) > :is(.data-view-label, .amx-field) {
        flex-basis: 100%;
        width: 100%;
        max-width: 100%;
        min-width: 0;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .data-view-page .amx-grid-wrapper.folder-mode {
        flex-direction: column;
    }

    :is(.amx-shell.amx-home-shell, .amx-shell:not(.amx-home-shell), .settings-shell) .data-view-page .amx-grid-wrapper.folder-mode .amx-grid-folder-pane {
        width: 100%;
        max-width: 100%;
    }
}

/* ============================================================
   Shared drag handle — .amx-drag-handle
   A 2×3 dot grid (6 dots) rendered with radial-gradients directly on the
   parent background (NO wrapping circle/pill). Colour is token-driven and
   darkens on hover → active to confirm the drag is engaged. Carries a
   "Drag to reorder" tooltip (::after) that fades in after a short delay and
   is suppressed while a drag is in progress (body.amx-dragging, set by
   designerSortable.js). Standard: DESIGN_LANGUAGE.md.
   ============================================================ */
.amx-drag-handle {
    /* 6 dots: 2 columns × 3 rows. Dot ø ~4px, ~4px gap → 8px pitch → 12×20 box. */
    --dh-dot: var(--text-muted);
    display: inline-block;
    width: 12px;
    height: 20px;
    flex-shrink: 0;
    cursor: grab;
    /* dots via radial-gradients; positions are the top-left of each 8px cell */
    background-image:
        radial-gradient(circle 2px at 2px 2px, var(--dh-dot) 99%, transparent 100%),
        radial-gradient(circle 2px at 10px 2px, var(--dh-dot) 99%, transparent 100%),
        radial-gradient(circle 2px at 2px 10px, var(--dh-dot) 99%, transparent 100%),
        radial-gradient(circle 2px at 10px 10px, var(--dh-dot) 99%, transparent 100%),
        radial-gradient(circle 2px at 2px 18px, var(--dh-dot) 99%, transparent 100%),
        radial-gradient(circle 2px at 10px 18px, var(--dh-dot) 99%, transparent 100%);
    background-repeat: no-repeat;
    background-color: transparent; /* sits on the parent background — no container shape */
    transition: --dh-dot 150ms ease; /* graceful in browsers supporting @property; colour swap is instant otherwise */
    position: relative;
}
.amx-drag-handle:hover { --dh-dot: var(--text-secondary); }
.amx-drag-handle:active,
.amx-drag-handle.dragging,
body.amx-dragging .amx-drag-handle { --dh-dot: var(--text-primary); cursor: grabbing; }

/* Compact variant for dense rows (tree rows, list items) — smaller dots, tighter box.
   ø ~3px, ~3px gap → 6px pitch → 9×15 box. */
.amx-drag-handle.sm {
    width: 9px;
    height: 15px;
    background-image:
        radial-gradient(circle 1.5px at 1.5px 1.5px, var(--dh-dot) 99%, transparent 100%),
        radial-gradient(circle 1.5px at 7.5px 1.5px, var(--dh-dot) 99%, transparent 100%),
        radial-gradient(circle 1.5px at 1.5px 7.5px, var(--dh-dot) 99%, transparent 100%),
        radial-gradient(circle 1.5px at 7.5px 7.5px, var(--dh-dot) 99%, transparent 100%),
        radial-gradient(circle 1.5px at 1.5px 13.5px, var(--dh-dot) 99%, transparent 100%),
        radial-gradient(circle 1.5px at 7.5px 13.5px, var(--dh-dot) 99%, transparent 100%);
}

/* Tooltip — reuses no external system; a self-contained dark chip above the handle. */
.amx-drag-handle::after {
    content: "Drag to reorder";
    position: absolute;
    bottom: calc(100% + 7px);
    left: 50%;
    transform: translateX(-50%) scale(0.96);
    padding: 4px 8px;
    border-radius: 6px;
    background: #18181b;               /* dark chip (theme-independent, like a native tooltip) */
    color: #ffffff;
    font-size: 12px;
    line-height: 1.2;
    font-weight: 500;
    white-space: nowrap;
    pointer-events: none;
    opacity: 0;
    z-index: 1000;
    box-shadow: 0 2px 8px rgba(0, 0, 0, .18);
    transition: opacity 100ms ease, transform 100ms ease;
    transition-delay: 0ms;
}
/* Appear on hover after a ~400ms delay (delay only on the way IN, so it hides instantly). */
.amx-drag-handle:hover::after {
    opacity: 1;
    transform: translateX(-50%) scale(1);
    transition-delay: 400ms;
}
/* Never linger while dragging. */
body.amx-dragging .amx-drag-handle::after,
.amx-drag-handle:active::after,
.amx-drag-handle.dragging::after { opacity: 0; transition-delay: 0ms; }

/* Keyboard focus shows a ring (the handle is often focusable for keyboard reorder). */
.amx-drag-handle:focus-visible { outline: 2px solid var(--border-focus, var(--accent-primary)); outline-offset: 2px; border-radius: 3px; }

/* Smooth colour transition where supported. */
@property --dh-dot { syntax: "<color>"; inherits: true; initial-value: #71717a; }

/* Variant: dot-grid visual without the tooltip — for handles whose parent already
   carries the label/tooltip (e.g. a whole cell that is itself the drag handle). */
.amx-drag-handle.no-tip::after { content: none; }

/* ── External links in user text ──────────────────────────────────────
   Addresses found in a text box or in rich text (TextLinkifier). The link is styled at rest so it
   is obviously a link; the icon appears on hover and on keyboard focus to say it leaves the app.
   The icon is not the only signal - every generated link also carries visually-hidden
   "(opens in a new tab)" text, because a hover icon is no use to a screen reader. */
.amx-external-link {
    color: var(--accent-link);
    text-decoration: underline;
    text-underline-offset: 2px;
    text-decoration-thickness: 1px;
    /* Long addresses break rather than stretching the field. */
    word-break: break-word;
}

.amx-external-link:hover,
.amx-external-link:focus-visible {
    color: var(--accent-link);
    text-decoration-thickness: 2px;
}

.amx-external-link:focus-visible {
    outline: 2px solid var(--accent-primary);
    outline-offset: 2px;
    border-radius: 2px;
}

/* fa-arrow-up-right-from-square. Sized in em so it tracks the text, and inline-block so the
   link's own underline does not run through it. */
.amx-external-link::after {
    content: "\f08e";
    font-family: "Font Awesome 6 Free", "Font Awesome 5 Free", sans-serif;
    font-weight: 900;
    display: inline-block;
    width: 0;
    margin-left: 0;
    font-size: 0.8em;
    text-decoration: none;
    opacity: 0;
    overflow: hidden;
    vertical-align: baseline;
    transition: opacity 120ms ease, width 120ms ease, margin-left 120ms ease;
}

.amx-external-link:hover::after,
.amx-external-link:focus-visible::after {
    width: 1em;
    margin-left: 0.25em;
    opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
    .amx-external-link::after {
        transition: none;
    }
}

/* ── Theme switching ──────────────────────────────────────────────────
   Set on <html> by amxTheme (js/themeInit.js) for one frame while the theme or font size
   changes, then removed.

   Controls all over the app carry transitions written for hover and focus ("transition: all
   .12s"). Flipping data-theme changes hundreds of colours at once, so all of those fire together
   and the colours ANIMATE between their light and dark values. The colour half way between
   near-black text and near-white text is, near enough, the dark background - measured at 1.01:1
   contrast on the first frame, and it does not clear 4.5:1 until ~35ms in. That is the flash:
   text vanishing into its own background and fading back.

   Nobody wants a theme change animated, so this suppresses every transition and animation for the
   frame in which it happens. Hover and focus transitions are unaffected - the class is gone by the
   time the user can interact. */
html.amx-theme-switching,
html.amx-theme-switching *,
html.amx-theme-switching *::before,
html.amx-theme-switching *::after {
    transition: none !important;
    animation: none !important;
}

/* ── DataView form item search highlight ──────────────────────────────
   Applied by HighlightFormItem (blazorHelpers.js) to the `data-displayitemid`
   container of the control a user picked in the "Find a form item" search, after
   the section/data/sub tab that renders it has been switched to. The ring fades
   out on its own after a few seconds so nothing is left permanently marked.
   Colour is never the only signal here - the search also scrolls the control into
   view and moves keyboard focus onto it. */
.amx-form-item-found {
    position: relative;
    border-radius: var(--radius-sm);
    outline: 2px solid var(--accent-primary);
    outline-offset: 2px;
    background-color: var(--accent-wash);
    animation: amx-form-item-found-fade 4s ease-out 1 forwards;
    scroll-margin: 24px;
}

.amx-form-item-found:focus,
.amx-form-item-found:focus-visible {
    outline: 2px solid var(--accent-primary);
    outline-offset: 2px;
}

@keyframes amx-form-item-found-fade {
    0% { outline-color: var(--accent-primary); background-color: var(--accent-wash); }
    70% { outline-color: var(--accent-primary); background-color: var(--accent-wash); }
    100% { outline-color: transparent; background-color: transparent; }
}

/* Users who ask for reduced motion still get the ring, just without the fade. */
@media (prefers-reduced-motion: reduce) {
    .amx-form-item-found {
        animation: none;
    }
}

/* ── DataView form item search: tab flash ─────────────────────────────
   Applied by HighlightFormTabs (blazorHelpers.js) to the section tab, data tab and
   sub tab (or accordion header) the search took the user through, so it is obvious
   which tabs changed. Fades out on its own after a few seconds. Colour is never the
   only signal - each of these tabs is also left as the active one. */
.amx-form-tab-found {
    position: relative;
    border-radius: var(--radius-sm);
    outline: 2px solid var(--accent-primary);
    outline-offset: -2px;
    animation: amx-form-tab-found-fade 4s ease-out 1 forwards;
    scroll-margin: 24px;
}

@keyframes amx-form-tab-found-fade {
    0% { outline-color: var(--accent-primary); }
    70% { outline-color: var(--accent-primary); }
    100% { outline-color: transparent; }
}

@media (prefers-reduced-motion: reduce) {
    .amx-form-tab-found {
        animation: none;
    }
}

/* ============================================================
   Session warning — shown by js/sessionExpiry.js while the session is close to
   ending and there is still time to do something about it. Sits below the
   session-expired dialog (20010) because once the session has actually gone the
   warning is stale, and above the reconnect modal (20000).
   Amber is used as a border/icon fill only — the vivid --accent-warning fails as
   body text — so the wording itself is --text-primary.
   ============================================================ */
#amx-session-warning {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 20005;
    align-items: center;
    justify-content: center;
    gap: 0.75rem;
    flex-wrap: wrap;
    padding: 0.75rem 1.25rem;
    background-color: var(--bg-card);
    border-bottom: 2px solid var(--accent-warning);
    color: var(--text-primary);
    font-size: 0.9rem;
    line-height: 1.4;
    text-align: center;
    box-shadow: var(--shadow-md, 0 2px 4px rgb(0 0 0 / 10%));
}

#amx-session-warning.amx-session-warning-show {
    display: flex;
}

#amx-session-warning.amx-session-warning-hide {
    display: none;
}

#amx-session-warning .session-warning-icon {
    color: var(--accent-warning);
}

#amx-session-warning .session-warning-text {
    color: var(--text-primary);
}

/* The announcement region: reachable by assistive tech, no visual footprint. */
#amx-session-warning .session-warning-sr {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

#amx-session-warning .session-warning-btn {
    /* ≥ 24×24px target (WCAG 2.2 2.5.8). */
    min-height: 36px;
    padding: 6px 14px;
    font-size: 0.85rem;
    font-weight: 550;
    color: var(--text-primary);
    background-color: var(--bg-input);
    /* An interactive boundary, so ≥ 3:1 (WCAG 1.4.11) — not the decorative hairline. */
    border: 1px solid var(--border-interactive);
    border-radius: var(--radius-md, 0.5rem);
    cursor: pointer;
}

#amx-session-warning .session-warning-btn:hover {
    background-color: var(--bg-tertiary);
}

#amx-session-warning .session-warning-btn:focus-visible {
    outline: 2px solid var(--border-focus, var(--accent-primary));
    outline-offset: 2px;
}

/* ============================================================
   Session-expired dialog — shown by js/sessionExpiry.js when the server
   confirms the auth cookie behind this page has gone (markup in App.razor).
   Replaces the reconnect modal, which is hidden while this shows: when both
   could apply, the sign-out is the actionable problem and reconnecting is not
   going to help. Like that modal it goes into the browser's top layer while it
   shows (reconnectHelper.js), above the map's menus and any modal dialog.
   All colours come from the theme tokens, so it themes for free.
   ============================================================ */
#amx-session-expired-modal {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 20010;
    align-items: center;
    justify-content: center;
    padding: 1rem;
    background-color: color-mix(in srgb, var(--bg-primary, #000) 60%, transparent);
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
}

#amx-session-expired-modal.amx-session-expired-show {
    display: flex;
}

#amx-session-expired-modal.amx-session-expired-hide {
    display: none;
}

/* The browser's popover box stepped out of, as for the reconnect modal - keeping its own padding. */
#amx-session-expired-modal:popover-open {
    width: auto;
    height: auto;
    max-width: none;
    max-height: none;
    margin: 0;
    border: 0;
    overflow: visible;
    color: inherit;
}

#amx-session-expired-modal .session-expired-dialog {
    width: 420px;
    max-width: 100%;
    padding: 2rem 2.25rem;
    text-align: center;
    background-color: var(--bg-card);
    color: var(--text-primary);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg, 1rem);
    box-shadow: var(--shadow-lg);
}

/* Amber as a fill/icon colour only — the vivid --accent-warning fails as body text. */
#amx-session-expired-modal .session-expired-icon {
    display: block;
    margin: 0 auto 1rem;
    font-size: 2rem;
    color: var(--accent-warning);
}

#amx-session-expired-modal .session-expired-title {
    margin: 0 0 0.5rem;
    font-size: 1.15rem;
    font-weight: 600;
    color: var(--text-primary);
}

#amx-session-expired-modal .session-expired-text {
    margin: 0;
    font-size: 0.9rem;
    line-height: 1.45;
    color: var(--text-secondary);
}

#amx-session-expired-modal .session-expired-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    justify-content: center;
    margin-top: 1.5rem;
}

#amx-session-expired-modal .session-expired-btn {
    /* ≥ 24×24px target (WCAG 2.2 2.5.8). */
    min-height: 36px;
    padding: 8px 16px;
    font-size: 0.85rem;
    font-weight: 550;
    border-radius: var(--radius-sm, 8px);
    border: 1px solid transparent;
    cursor: pointer;
    background-color: var(--accent-primary);
    color: #ffffff;
    transition: filter 0.12s ease, background-color 0.12s ease;
}

/* The dark-theme --accent-primary is a brighter blue that white text does not clear 4.5:1 against;
   dark ink on it does. This is the labelling rule the token-contrast guard asserts for the raw
   accent (white in light, --bg-primary ink in dark) — see AMXWebTestsUI token-contrast.spec.ts. */
[data-theme="dark"] #amx-session-expired-modal .session-expired-btn {
    color: var(--bg-primary);
}

#amx-session-expired-modal .session-expired-btn:hover {
    filter: brightness(1.05);
}

#amx-session-expired-modal .session-expired-btn:focus-visible {
    outline: 2px solid var(--border-focus, var(--accent-primary));
    outline-offset: 2px;
}

#amx-session-expired-modal .session-expired-btn-secondary {
    background-color: transparent;
    /* The border is this button's only boundary, so it needs the 3:1 interactive token, not the
       decorative hairline (WCAG 1.4.11). */
    border-color: var(--border-interactive);
    color: var(--text-primary);
}

[data-theme="dark"] #amx-session-expired-modal .session-expired-btn-secondary {
    color: var(--text-primary);
}

#amx-session-expired-modal .session-expired-btn-secondary:hover {
    background-color: var(--bg-hover);
    filter: none;
}

/* The map's distance measuring tool - see measureControl.js.

   The button is one of the map's own icon controls and takes its look from the rules above; only
   the on-state differs, and it is the red of the polygon select rather than the green of a
   toggle, because pressing it again stops the tool rather than turning something off.

   The bar is the app's own surface rather than the map's blue chrome: it carries a number to be
   read, a picker and four actions, which is a small panel over the map and not another control.
   Both live here, in app.css, rather than in the map component's scoped stylesheet - the map is
   moved inside a modal to go full screen, where a ::deep rule anchored on the component's own
   wrapper no longer reaches it.

   The polygon select (polygonSelectControl.js) shares the on-state. It has its own place on the
   toolbar, so while it runs it is a "Stop drawing areas" pill on the bar, and the dock's rules for
   its tools would otherwise paint it as any other tool - with the !important, the stop colour wins
   on the bar, in a menu and full screen alike. */
.custom-map-control.map-measure-control.active,
.custom-map-control.map-polygon-select.active {
    background-color: var(--map-control-stop) !important;
    border-color: var(--map-control-stop) !important;
    color: var(--map-control-state-fg) !important;
}

.custom-map-control.map-measure-control.active i,
.custom-map-control.map-polygon-select.active i {
    color: inherit !important;
}

.map-measure-bar {
    position: absolute;
    left: 50%;
    /* Clear of a bar along the bottom of the map (--map-bottom-offset, from mapToolbar.js). */
    bottom: calc(var(--map-bottom-offset, 8px) + 8px);
    transform: translateX(-50%);
    z-index: 1000;
    /* The stop-event container the bar hangs in takes no pointer events itself and hands them to
       its children - which is how OpenLayers keeps a control's clicks off the map. A control
       added through map.addControl has this set for it; the bar is appended by hand, so it has
       to say so. */
    pointer-events: auto;
    display: none;
    align-items: center;
    gap: 12px;
    max-width: calc(100% - 24px);
    padding: 8px 10px 8px 14px;
    background-color: var(--bg-card);
    border: 1px solid var(--border-color);
    border-radius: 12px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
    color: var(--text-primary);
}

.map-measure-bar.show {
    display: flex;
}

/* Distance or area, as the app's segmented control: a subtle track with the chosen segment
   raised out of it on the card surface. Built on native radios, so the arrow keys and the group
   semantics come for free - the inputs are clipped rather than display:none, which would take
   them out of the tab order. */
.map-measure-modes {
    display: flex;
    gap: 2px;
    padding: 3px;
    background-color: var(--bg-secondary);
    border-radius: 8px;
}

.map-measure-mode-input {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.map-measure-mode {
    display: flex;
    align-items: center;
    gap: 5px;
    height: 26px;
    padding: 0 10px;
    border-radius: 6px;
    color: var(--text-secondary);
    font-size: 12px;
    font-weight: 500;
    white-space: nowrap;
    cursor: pointer;
    transition: background-color 0.1s ease, color 0.1s ease;
}

.map-measure-mode:hover {
    color: var(--text-primary);
}

.map-measure-mode-input:checked + .map-measure-mode {
    background-color: var(--bg-card);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
    color: var(--accent-primary);
}

.map-measure-mode-input:focus-visible + .map-measure-mode {
    outline: 2px solid var(--accent-primary);
    outline-offset: 1px;
}

.map-measure-mode i {
    font-size: 12px;
    pointer-events: none;
}

.map-measure-readout {
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 132px;
}

.map-measure-total {
    display: flex;
    align-items: baseline;
    gap: 5px;
    line-height: 1.1;
}

.map-measure-value {
    font-size: 19px;
    font-weight: 600;
}

.map-measure-suffix {
    font-size: 13px;
    color: var(--text-secondary);
}

.map-measure-hint {
    font-size: 11px;
    color: var(--text-muted);
    white-space: nowrap;
}

/* Bootstrap is never told the app's theme, so .form-select would come up light on a dark map -
   this is styled from the app's own tokens instead. See the map settings notes in
   DESIGN_LANGUAGE.md. */
.map-measure-unit {
    height: 32px;
    min-width: 122px;
    padding: 0 6px;
    background-color: var(--bg-card);
    color: var(--text-primary);
    border: 1px solid var(--border-color);
    border-radius: 8px;
    color-scheme: light dark;
    font-size: 12px;
    font-weight: 500;
    cursor: pointer;
}

.map-measure-unit:hover {
    border-color: var(--accent-primary);
}

/* The bar sits over a map, where a browser's own faint focus ring is easily lost - every control
   on it takes the app's 2px accent ring (ACCESSIBILITY_LANGUAGE.md, keyboard and focus). */
.map-measure-unit:focus-visible,
.map-measure-action:focus-visible,
.map-select-action:focus-visible {
    outline: 2px solid var(--accent-primary);
    outline-offset: 2px;
    border-color: var(--accent-primary);
}

/* Only the picker for the mode being measured in is shown, and it is hidden rather than
   detached so each keeps the unit the user chose in it. Stated because a bare [hidden] is
   easily outranked by a rule that sets display on the element. */
.map-measure-unit[hidden] {
    display: none;
}

.map-measure-actions {
    display: flex;
    align-items: center;
    gap: 4px;
    padding-left: 10px;
    border-left: 1px solid var(--border-color);
}

.map-measure-action,
.map-select-action {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    background-color: transparent;
    color: var(--text-secondary);
    border: 1px solid var(--border-color);
    border-radius: 8px;
    font-size: 13px;
    cursor: pointer;
    transition: background-color 0.1s ease, color 0.1s ease;
}

.map-measure-action:hover:not(:disabled),
.map-select-action:hover:not(:disabled) {
    background-color: var(--bg-hover);
    color: var(--text-primary);
}

/* Nothing measured yet is not a fault, so the actions that need a measurement go quiet rather
   than disappearing - the bar keeps its shape as the user draws. */
.map-measure-action:disabled,
.map-select-action:disabled {
    opacity: 0.45;
    cursor: default;
}

.map-measure-action i,
.map-select-action i {
    pointer-events: none;
}

/* The map says what it is for while the tool is running: a crosshair over the whole viewport,
   including over the assets, which are not what a click picks up while measuring. Over a
   measurement it says the opposite - this one is a thing to take hold of, not a place to put a
   point, and the drawing has stood down for as long as the pointer is there. (The selected one
   gets its own grab cursor from OpenLayers' Translate, inline and so ahead of these.) */
.map-measuring,
.map-measuring canvas {
    cursor: crosshair;
}

.map-measuring.map-measure-over,
.map-measuring.map-measure-over canvas {
    cursor: pointer;
}

/* Under about 560px the bar's own row is wider than the map it sits on, so the readout takes the
   top line and the picker and actions the one below it. */
@media (max-width: 560px) {
    .map-measure-bar {
        flex-wrap: wrap;
        justify-content: center;
        row-gap: 8px;
        padding: 10px 12px;
    }

    .map-measure-readout {
        width: 100%;
        min-width: 0;
        align-items: center;
    }

    .map-measure-actions {
        padding-left: 0;
        border-left: 0;
    }
}

/* The map's area select - see polygonSelectControl.js.

   While it runs the map carries one bar along the bottom: how many records the areas drawn take in,
   what to do next, undo and clear, Cancel, and Continue, which opens the records. It is the
   measuring bar's surface, and its icon buttons are the measuring bar's (above), so the map's two
   drawing tools read as one family; it is here in app.css for the same reason that one is.

   The bar sits in a dock that spans the map clear of the side dock (--map-right-offset). The dock
   centres it, takes no pointer events of its own - only the bar should catch them - and is the size
   container the narrow layout is decided by: a map in split view is narrower than the window, so a
   media query would wrap the bar on the wrong maps. */
.map-select-dock {
    position: absolute;
    left: 8px;
    right: var(--map-right-offset, 8px);
    /* Clear of a bar along the bottom of the map (--map-bottom-offset, from mapToolbar.js). */
    bottom: calc(var(--map-bottom-offset, 8px) + 8px);
    z-index: 1000;
    display: none;
    justify-content: center;
    pointer-events: none;
    container-type: inline-size;
}

.map-select-dock.show {
    display: flex;
}

.map-select-bar {
    display: flex;
    align-items: center;
    gap: 8px 12px;
    box-sizing: border-box;
    max-width: 100%;
    padding: 8px;
    background-color: var(--bg-card);
    border: 1px solid var(--border-color);
    border-radius: 12px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
    color: var(--text-primary);
    pointer-events: auto;
}

.map-select-lead {
    display: flex;
    align-items: center;
    gap: 10px;
    padding-left: 2px;
}

/* The tool's own icon until the areas take something in, then the green tick the map draws on
   what is selected - the link between the number and the records it counts. */
.map-select-badge {
    display: flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: 8px;
    background-color: var(--bg-secondary);
    color: var(--text-secondary);
    font-size: 14px;
    transition: background-color 0.15s ease, color 0.15s ease;
}

.map-select-bar.has-items .map-select-badge {
    background-color: var(--accent-success-fill);
    color: var(--text-on-dark);
}

/* Wide enough for the longest hint, so the bar keeps its width as the hint changes under it. */
.map-select-readout {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 196px;
}

.map-select-total {
    display: flex;
    align-items: baseline;
    gap: 5px;
    line-height: 1.1;
}

.map-select-count {
    font-size: 19px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.map-select-noun {
    font-size: 13px;
    color: var(--text-secondary);
}

.map-select-hint {
    font-size: 11px;
    color: var(--text-muted);
    white-space: nowrap;
}

.map-select-controls {
    display: flex;
    align-items: center;
    gap: 8px;
}

.map-select-actions {
    display: flex;
    align-items: center;
    gap: 4px;
    padding-left: 10px;
    border-left: 1px solid var(--border-color);
}

.map-select-cancel,
.map-select-continue {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    height: 32px;
    padding: 0 12px;
    border-radius: 8px;
    font-size: 13px;
    font-weight: 500;
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
    transition: background-color 0.1s ease, filter 0.1s ease;
}

.map-select-cancel {
    background-color: transparent;
    color: var(--text-primary);
    border: 1px solid transparent;
}

.map-select-cancel:hover {
    background-color: var(--bg-hover);
}

/* The page's one way on, so it is the app's green action fill (DESIGN_LANGUAGE.md). Its ink is
   stated with !important: app.css's dark-theme button rule otherwise repaints it in the pale
   --text-primary, which is short of 4.5:1 on the green. */
.map-select-continue {
    padding: 0 14px;
    background-color: var(--accent-success-fill);
    color: var(--text-on-dark) !important;
    border: 1px solid var(--accent-success-fill);
    font-weight: 600;
}

.map-select-continue i {
    font-size: 12px;
    pointer-events: none;
}

.map-select-continue:hover:not(:disabled) {
    filter: brightness(0.92);
}

/* Nothing selected yet is not a fault, so the actions that need a selection go quiet rather than
   disappearing - the bar keeps its shape as the user draws. */
.map-select-continue:disabled {
    opacity: 0.45;
    cursor: default;
}

.map-select-cancel:focus-visible,
.map-select-continue:focus-visible {
    outline: 2px solid var(--accent-primary);
    outline-offset: 2px;
}

/* Stated because a bare [hidden] is easily outranked by a rule that sets display on the element. */
.map-select-continue[hidden] {
    display: none;
}

/* On a map too narrow for one row, the readout takes the top line and the buttons the one below,
   with the undo and clear at its start and Cancel and Continue at its end. */
@container (max-width: 600px) {
    .map-select-bar {
        flex-wrap: wrap;
    }

    .map-select-lead,
    .map-select-controls {
        flex: 1 1 100%;
    }

    .map-select-readout {
        min-width: 0;
    }

    .map-select-hint {
        white-space: normal;
    }

    .map-select-actions {
        margin-right: auto;
        padding-left: 0;
        border-left: 0;
    }
}

/* The report editor normally fills the space below the top bar. At compact
   sizes its form panels use document scrolling, so its ancestors must grow
   too. Limit this to the report route; other designers keep their own layout. */
@media (max-width: 1000px), (max-height: 600px) {
    html:has(.report-designer-page) {
        --amx-ui-scale: 1;
        height: auto;
        overflow-x: clip;
        overflow-y: auto;
    }

    html:has(.report-designer-page) body {
        transform: none;
        width: 100%;
        height: auto;
        min-height: 100vh;
        overflow: visible;
    }

    .amx-shell:has(.report-designer-page) {
        height: auto;
        min-height: 100vh;
        overflow: visible;
    }

    .amx-shell:has(.report-designer-page) :is(.amx-body, .amx-content, .amx-content-scroll) {
        flex: 1 1 auto;
        height: auto;
        max-height: none;
        overflow: visible;
    }

    .amx-shell:has(.report-designer-page) :is(.amx-topbar, .amx-topbar-right .menu-icons) {
        flex-wrap: wrap;
        height: auto;
    }
}

/* Entity editor headers: utility actions first, then the paired save/cancel actions. */
@media (max-width: 1000px), (max-height: 600px) {
    /* The form canvas owns scrolling and is positioned inside the main region. */
    .amx-shell:has(.designer-page:not(.designer-page-hosted)) {
        height: var(--amx-viewport-h);
        min-height: 0;
        overflow: hidden;
    }

    .amx-shell:has(.designer-page:not(.designer-page-hosted)) :is(.amx-body, .amx-content, .amx-content-scroll) {
        flex: 1 1 auto;
        height: 100%;
        min-height: 0;
        max-height: 100%;
        overflow: hidden;
    }
}

.amx-editor-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-inline-start: auto;
    max-width: 100%;
}

.amx-editor-actions .amx-editor-utilities,
.amx-editor-actions .amx-editor-primary {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.amx-editor-actions .amx-editor-utilities {
    flex-wrap: wrap;
    justify-content: flex-end;
    min-width: 0;
}

.amx-editor-actions .amx-editor-primary {
    flex-shrink: 0;
    flex-wrap: nowrap;
}

.amx-editor-divider {
    width: 1px;
    height: 24px;
    flex-shrink: 0;
    background: var(--border-color);
    margin-inline: 0.25rem;
}

/* Use the contrast-tested fills in both themes, including keyboard and pointer states. */
.rs-header-actions .dbd-btn.dbd-success,
.rs-header-actions .dbd-btn.dbd-success:is(:hover, :focus, :active):not(:disabled),
.amx-editor-actions .dbd-btn.dbd-success,
.amx-editor-actions .dbd-btn.dbd-primary,
.amx-editor-actions .dbd-btn:is(.dbd-success, .dbd-primary):is(:hover, :focus, :active):not(:disabled),
.modal-footer .btn.dbd-btn.dbd-success,
.modal-footer .btn.dbd-btn.dbd-primary,
.modal-footer .btn.dbd-btn:is(.dbd-success, .dbd-primary):is(:hover, :focus, :active):not(:disabled) {
    background: var(--accent-success-fill);
    border-color: var(--accent-success-fill);
    color: var(--text-on-dark);
    filter: none;
}

.amx-editor-actions .dbd-btn.dbd-danger,
.amx-editor-actions .dbd-btn.dbd-danger:is(:hover, :focus, :active):not(:disabled),
.modal-footer .btn.dbd-btn.dbd-danger,
.modal-footer .btn.dbd-btn.dbd-danger:is(:hover, :focus, :active):not(:disabled) {
    background: var(--accent-danger-fill);
    border-color: var(--accent-danger-fill);
    color: var(--text-on-dark) !important;
    filter: none;
}

.amx-editor-actions .btn.dbd-btn:focus-visible:not(:disabled),
.modal-footer .btn.dbd-btn:focus-visible:not(:disabled) {
    outline: 2px solid var(--border-focus, var(--accent-success-fill));
    outline-offset: 2px;
}

/* Popup actions read and tab left to right: utilities, divider, confirm, cancel. */
.amx-modal-action-divider {
    display: inline-block;
    flex: 0 0 1px;
    width: 1px;
    height: 24px;
    align-self: center;
    background: var(--border-color);
    margin-inline: 0.25rem;
}


/* ==========================================================================
   Loading spinner
   ========================================================================== */

/* The one circle spinner, for every wait: pages, tabs, grids, modals, buttons. Rendered by
   SpinnerComponent (Components/Spinner), or written by hand as
   <span class="amx-spinner amx-spinner--inline" aria-hidden="true"></span> inside a button or line
   whose text already says what is happening. Don't hand-roll another (no spinner-border, fa-spin
   icons, or page-local @keyframes rings). AMX.Mobile/wwwroot/css/app.css carries the same rules -
   keep the two in step. The wrapper .amx-loading carries role="status" and the text. */
.amx-loading {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 14px;
}

.amx-loading-text {
    display: block;
    width: 100%;
    text-align: center;
    font-size: 14px;
    font-weight: 500;
    color: var(--text-secondary);
}

/* The arc takes --amx-spinner-colour (SpinnerComponent sets it to the customer's colour) and the
   track is the same colour washed out, so it works on any surface in either theme. */
.amx-spinner {
    --amx-spinner-size: 40px;
    --amx-spinner-stroke: 3.5px;
    --amx-spinner-arc: var(--amx-spinner-colour, var(--accent-success));
    display: inline-block;
    flex-shrink: 0;
    box-sizing: border-box;
    width: var(--amx-spinner-size);
    height: var(--amx-spinner-size);
    border-radius: 50%;
    border: var(--amx-spinner-stroke) solid color-mix(in srgb, var(--amx-spinner-arc) 20%, transparent);
    border-top-color: var(--amx-spinner-arc);
    animation: amx-spinner-turn .8s linear infinite;
}

/* Customer colours are usually picked against white; a navy or dark green all but disappears on the
   dark surface, so lift the arc towards white there. Inline spinners follow their text and are left. */
[data-theme="dark"] .amx-spinner:not(.amx-spinner--inline) {
    --amx-spinner-arc: color-mix(in srgb, var(--amx-spinner-colour, var(--accent-success)) 60%, #ffffff);
}

.amx-spinner--sm {
    --amx-spinner-size: 22px;
    --amx-spinner-stroke: 2.5px;
}

.amx-spinner--lg {
    --amx-spinner-size: 52px;
    --amx-spinner-stroke: 4px;
}

/* Sits in a line of text or a button and follows its colour. */
.amx-spinner--inline {
    --amx-spinner-size: 1em;
    --amx-spinner-stroke: 2px;
    --amx-spinner-colour: currentColor;
    vertical-align: -0.125em;
}

@keyframes amx-spinner-turn {
    to { transform: rotate(360deg); }
}

/* A rare smile (one load in a thousand), or the Konami code's .amx-smiley-mode on <html>: the ring
   gives way to a turning face. Inline spinners stay as they are. */
.amx-smiley-spinner .amx-spinner,
.amx-smiley-mode .amx-spinner:not(.amx-spinner--inline) {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-color: transparent;
    animation-duration: 1.8s;
}

    .amx-smiley-spinner .amx-spinner::after,
    .amx-smiley-mode .amx-spinner:not(.amx-spinner--inline)::after {
        content: "\f118";
        font: var(--amx-spinner-size) "Font Awesome 6 Pro";
        color: var(--text-secondary);
    }

/* Slowed rather than stopped: a frozen indicator reads as a hung app. */
@media (prefers-reduced-motion: reduce) {
    .amx-spinner {
        animation-duration: 2.4s;
    }
}

/* ========== STARTUP SCREEN ==========
   AuthMainLayout's "Please wait" card. Kept here, in the head, rather than in that layout's <style>
   block, which the browser reaches only after the card is painted (see the note on the markup).
   Shown while the layout builds. It is the first thing anyone sees each session, so it greets the
   user rather than reporting on the framework's progress ("Loading layout…"). */
.amx-loading-screen {
    display: flex;
    align-items: center;
    justify-content: center;
    /* Not 100vh: body is transform-scaled, so a viewport-height box covers only --amx-ui-scale
       of the screen and leaves a strip of bare canvas below. .amx-shell compensates the same way. */
    height: var(--amx-viewport-h);
    width: 100%;
    padding: 24px;
    box-sizing: border-box;
    overflow: hidden;
    background: var(--bg-secondary, #fafafa);
}

.amx-loading-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 6px;
    width: 100%;
    max-width: 420px;
    padding: 40px 32px 32px;
    background: var(--bg-card, #ffffff);
    border: 1px solid var(--border-color, #e4e4e7);
    border-radius: 18px;
    box-shadow: 0 12px 32px rgba(0, 0, 0, .06);
    animation: amx-loading-rise .5s cubic-bezier(.2, .7, .3, 1) both;
}

/* The app's own mark (the favicon), breathing rather than spinning — calmer, and it identifies the
   product straight away. It keeps a light tile in both themes: the logo is multi-coloured and its
   cross is formed by negative space, so it needs the background it was drawn for. */
.amx-loading-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 84px;
    height: 84px;
    margin-bottom: 16px;
    padding: 12px;
    box-sizing: border-box;
    border-radius: 22px;
    background: #ffffff;
}

.amx-loading-mark img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: contain;
}

.amx-loading-greeting {
    margin: 0;
    font-size: 1.45rem;
    font-weight: 650;
    letter-spacing: -0.02em;
    color: var(--text-primary, #18181b);
    animation: amx-loading-rise .5s cubic-bezier(.2, .7, .3, 1) .08s both;
}

.amx-loading-sub {
    margin: 0 0 22px;
    font-size: .9rem;
    color: var(--text-secondary, #6b6b73);
    animation: amx-loading-rise .5s cubic-bezier(.2, .7, .3, 1) .16s both;
}

/* Indeterminate sweep: there is no real progress to report, so it says "working" without
   pretending to know how far along it is. */
.amx-loading-bar {
    position: relative;
    width: 100%;
    height: 4px;
    border-radius: 999px;
    background: var(--bg-hover, #f4f4f5);
    overflow: hidden;
}

.amx-loading-bar span {
    position: absolute;
    inset: 0 auto 0 0;
    width: 40%;
    border-radius: inherit;
    background: linear-gradient(90deg, transparent, #16a34a, transparent);
    animation: amx-loading-sweep 1.3s ease-in-out infinite;
}

/* The interactive render taking over from the prerendered one: already on screen, so no entrance. */
.amx-loading-screen--resumed .amx-loading-card,
.amx-loading-screen--resumed .amx-loading-sub {
    animation: none;
}

[data-theme="dark"] .amx-loading-card {
    box-shadow: 0 12px 32px rgba(0, 0, 0, .45);
}

[data-theme="dark"] .amx-loading-mark {
    border-color: rgba(255, 255, 255, .12);
    box-shadow: 0 8px 22px rgba(0, 0, 0, .45);
}

[data-theme="dark"] .amx-loading-bar span {
    background: linear-gradient(90deg, transparent, #22c55e, transparent);
}

@keyframes amx-loading-rise {
    from {
        opacity: 0;
        transform: translateY(10px);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

@keyframes amx-loading-pulse {
    0%, 100% {
        transform: scale(1);
    }

    50% {
        transform: scale(1.06);
    }
}

@keyframes amx-loading-sweep {
    from {
        left: -40%;
    }

    to {
        left: 100%;
    }
}

/* Motion is decorative here; the text already says what is happening. */
@media (prefers-reduced-motion: reduce) {
    .amx-loading-card,
    .amx-loading-greeting,
    .amx-loading-sub {
        animation: none;
    }

    .amx-loading-mark {
        animation: none;
    }

    .amx-loading-bar span {
        animation-duration: 3s;
    }
}
