/* ═══════════════════════════════════════════════════════════════════════════
   Design tokens (P8-T1 — FR-8.1 / FR-8.3)
   Single source of truth for the shipped visual identity. Every CSS rule below
   consumes these tokens rather than hard-coded values, so changing a token here
   propagates consistently across every component that uses it.

   Colors are stored as space-separated RGB triplets (e.g. "13 110 253") so a
   solid color and all of its glow/alpha variants derive from ONE value — change
   the triplet and both the fill and its glows move together, using the modern
   rgb(<triplet> / <alpha>) syntax.

   These colors are also mirrored, by necessity, in C# at
   AbISEMS.FrontEnd.Shared/Theming/SemanticColors.cs for the Syncfusion-chart /
   inline-style consumers that cannot resolve var() (SVG presentation attributes /
   string props). Keep the two layers in sync.

   IR1-1: this is the SHARED base layer, served to both frontends from
   _content/AbISEMS.FrontEnd.Shared/css/ems-base.css. Everything here is semantic
   (source/severity), layout, state, typography or grid styling. Per-product BRAND
   tokens are NOT here — each app defines them in its own css/app.css, loaded after
   this file so it can also override any base rule.

   Identity per Task-Owner ruling (2026-07-17): Bootstrap 5 dark/light,
   Grid = blue, Genset = red, Solar = amber, Bootstrap-aligned severity scale,
   theme-aware glow / status-dot language as the signature element. No redesign.
   ═══════════════════════════════════════════════════════════════════════════ */
:root {
    /* ── Palette primitives (RGB triplets, Bootstrap-aligned) ── */
    --palette-blue-rgb: 13 110 253;
    --palette-red-rgb: 220 53 69;
    --palette-amber-rgb: 255 193 7;
    --palette-green-rgb: 25 135 84;
    --palette-gray-rgb: 108 117 125;

    /* ── Brand identity ──
       Deliberately NOT defined here. --brand-* tokens are per-PRODUCT (ruling R4) and live in each
       app's own css/app.css, loaded after this file, because their values must stay grep-equal to
       that app's index.html <meta name="theme-color"> and manifest.webmanifest literals. Nothing in
       this base layer consumes them. */

    /* ── Source colors (Grid = blue, Genset = red, Solar = amber) ── */
    --source-grid-rgb: var(--palette-blue-rgb);
    --source-genset-rgb: var(--palette-red-rgb);
    --source-solar-rgb: var(--palette-amber-rgb);
    --source-muted-rgb: var(--palette-gray-rgb);
    --source-grid: rgb(var(--source-grid-rgb));
    --source-genset: rgb(var(--source-genset-rgb));
    --source-solar: rgb(var(--source-solar-rgb));
    --source-muted: rgb(var(--source-muted-rgb));

    /* ── Severity / status scale ── */
    --severity-normal-rgb: var(--palette-green-rgb);
    --severity-warning-rgb: var(--palette-amber-rgb);
    --severity-critical-rgb: var(--palette-red-rgb);
    --severity-info-rgb: var(--palette-blue-rgb);
    --severity-normal: rgb(var(--severity-normal-rgb));
    --severity-warning: rgb(var(--severity-warning-rgb));
    --severity-critical: rgb(var(--severity-critical-rgb));
    --severity-info: rgb(var(--severity-info-rgb));

    /* Warning as a FOREGROUND (text/icon) role (P8-T4 / FR-8.9 contrast).
       The bright amber warning FILL (#ffc107) is correct behind dark text, but as text/icon color on a
       light card it fails the WCAG 2.1 AA 4.5:1 floor (~1.3:1). This darkened amber passes on light
       surfaces (~5:1 on white) and is overridden back to the bright amber on dark themes below, where the
       bright value passes. Fills/dots/badges keep --severity-warning; only text/icon roles use this. */
    --severity-warning-fg: #8a6a00;

    /* Neutral reference/threshold accent (mirror of BrandColors.NeutralReference) — a chart marker that
       encodes neither source nor severity (e.g. the benchmarking "Site avg" stripline). P8-T1 N-1. */
    --reference-neutral: rgb(var(--palette-blue-rgb));

    /* status dots reuse the severity scale */
    --status-online-rgb: var(--severity-normal-rgb);
    --status-warning-rgb: var(--severity-warning-rgb);
    --status-offline-rgb: var(--severity-critical-rgb);
    --status-disabled-rgb: var(--palette-gray-rgb);

    /* ── Text on colored fills ── */
    --text-on-dark-fill: #fff;
    --text-on-light-fill: #212529; /* on the light amber (Solar) fill */

    /* ── Typography — type scale (P8-T2 / FR-8.4) ──
       Formalizes the shipped Bootstrap 5.3 type identity as named tokens (the
       single source of truth per FR-8.1). The size values below are the exact
       Bootstrap 5.3 scale the app already renders — a formalization, not a
       redesign (Task-Owner ruling 2026-07-17: formalize the existing identity).
       Bootstrap continues to render the h1–h6/body/.small classes at these
       values (h1–h4 additionally scale down on narrow viewports via RFS), so
       the tokens are NOT re-applied over those classes; custom components and
       the rules in this file consume the tokens directly. */
    --font-family-base: 'Helvetica Neue', Helvetica, Arial, sans-serif;

    /* Tabular / utility face for numeric data — the same family with tabular +
       lining figures so columns of numbers align and a changing value never
       reflows. font-variant-numeric substitutes ONLY numeral glyphs, never
       letters, so this face is safe to apply broadly to display/emphasis type. */
    --font-family-numeric: var(--font-family-base);
    --font-variant-numeric-tabular: tabular-nums lining-nums;

    /* Font sizes (rem) — display / heading roles + body + small */
    --font-size-display: 2.5rem; /* largest display / hero figure */
    --font-size-h1: 2.5rem;
    --font-size-h2: 2rem;
    --font-size-h3: 1.75rem;
    --font-size-h4: 1.5rem;
    --font-size-h5: 1.25rem;
    --font-size-h6: 1rem;
    --font-size-body: 1rem;      /* base body copy */
    --font-size-sm: 0.875rem;    /* .small / secondary text */

    /* Fine-print sizes below Bootstrap's .small — the three the product actually uses for dense
       meta/labels (badges, device sub-labels, group headers, last-reading stamps). Formalized as
       tokens so these no longer live as scattered inline `style="font-size: …"` literals; the exact
       shipped values are preserved (zero visual churn). Consume via the .fs-xs/.fs-2xs/.fs-3xs
       utilities below. */
    --font-size-xs: 0.75rem;
    --font-size-2xs: 0.7rem;
    --font-size-3xs: 0.65rem;

    /* Font weights — the three the product actually uses (400 / 600 / 700) */
    --font-weight-normal: 400;
    --font-weight-semibold: 600;
    --font-weight-bold: 700;

    /* Line heights */
    --line-height-base: 1.5;     /* body copy */
    --line-height-heading: 1.2;  /* headings / display */

    /* ── Radius ── */
    --radius-sm: 0.375rem;
    --radius-round: 50%;

    /* ── Spacing (values used in this file) ── */
    --space-2: 0.5rem;
    --space-3: 0.75rem;

    /* ── Motion ── */
    --motion-fast: 0.15s;
    --motion-base: 0.3s;
    --motion-blink: 1.5s;
    --easing-standard: ease;
    --easing-emphasis: ease-in-out;

    /* ── Elevation / glow blur radii (the signature theme-aware glow language) ── */
    --glow-xs: 4px;
    --glow-sm: 6px;
    --glow-md: 8px;
    --glow-lg: 12px;
    --glow-xl: 25px;
}

html, body {
    font-family: var(--font-family-base);
}

/* Base body type — token-driven, matches the shipped Bootstrap body defaults */
body {
    font-size: var(--font-size-body);
    font-weight: var(--font-weight-normal);
    line-height: var(--line-height-base);
}

/* ═══════════════════════════════════════════════════════════════════════════
   Accessibility baseline (P8-T4 — FR-8.9, WCAG 2.1 AA floor)
   Visible keyboard focus, sufficient contrast in both themes, and reduced-motion
   are handled here from the design tokens (not scattered literals). Non-color-only
   meaning is handled in the markup (every status dot / badge / severity indicator
   is paired with an icon or a text label).
   ═══════════════════════════════════════════════════════════════════════════ */

/* On dark themes the bright amber is legible, so the warning FOREGROUND token reverts to it.
   (data-bs-theme is set on <body>; the custom property inherits to all descendants.) */
[data-bs-theme="dark"] {
    --severity-warning-fg: var(--severity-warning);
}

/* Amber as TEXT fails 4.5:1 on light surfaces — route Bootstrap's .text-warning through the
   contrast-safe foreground token (bright amber on dark, darkened on light). app.css loads after
   Bootstrap, so this equal-specificity !important rule wins. Fills (.bg-warning) are untouched. */
.text-warning {
    color: var(--severity-warning-fg) !important;
}

/* Visible keyboard focus (WCAG 2.4.7) — a token-colored ring on any element focused via the
   keyboard, in BOTH themes. :focus-visible keeps the ring off mouse clicks (no visual noise) while
   guaranteeing it for keyboard/AT users. Layers on top of Bootstrap/Syncfusion focus styling. */
:focus-visible {
    outline: 2px solid var(--severity-info);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

/* ═══════════════════════════════════════════════════════════════════════════
   Tabular numerals (P8-T2 — FR-8.4)
   Numeric/tabular data renders with aligned, legible figures. The tabular +
   lining figure set gives every digit an equal advance width, so columns of
   numbers line up and a value that changes (e.g. a polled KPI) never reflows.
   font-variant-numeric only substitutes numeral glyphs — letters, labels and
   punctuation are untouched — so applying it to display/emphasis type and to
   data tables/grids aligns figures with zero effect on textual content.
   Coverage: headings (Dashboard/Analysis/Billing KPI values + card titles);
   .fw-bold/.fw-semibold (Monitoring three-pillar KPI values, emphasized
   figures); .table (phase-readings + every plain data table); Syncfusion grid
   body + summary cells (numeric columns). .tabular-nums is the opt-in utility
   for any other numeric surface. */
h1, h2, h3, h4, h5, h6,
.fw-bold, .fw-semibold,
.table,
.e-grid .e-rowcell,
.e-grid .e-summarycell,
.tabular-nums {
    font-variant-numeric: var(--font-variant-numeric-tabular);
    font-feature-settings: "tnum" 1, "lnum" 1;
}

/* Fine-print size utilities (P8-T2 follow-up) — token-driven replacements for inline
   `style="font-size: …rem"` literals on dense meta/labels. Exact shipped values, no visual change. */
.fs-xs { font-size: var(--font-size-xs); }
.fs-2xs { font-size: var(--font-size-2xs); }
.fs-3xs { font-size: var(--font-size-3xs); }

/* Remove hardcoded light-mode colors — let Syncfusion theme + data-bs-theme handle it */
h1:focus {
    outline: none;
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
    box-shadow: 0 0 0 0.1rem rgba(255,255,255,0.25), 0 0 0 0.25rem #258cfb;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid red;
}

.validation-message {
    color: red;
}

#blazor-error-ui {
    background: lightyellow;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    box-sizing: border-box;
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
    color: #333;
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    position: absolute;
    right: 0.75rem;
    top: 0.5rem;
}

.blazor-error-boundary {
    background: #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

.blazor-error-boundary::after {
    content: "An error has occurred.";
}

.loading-progress {
    position: absolute;
    display: block;
    width: 8rem;
    height: 8rem;
    inset: 20vh 0 auto 0;
    margin: 0 auto;
}

.loading-progress circle {
    fill: none;
    stroke: #e0e0e0;
    stroke-width: 0.6rem;
    transform-origin: 50% 50%;
    transform: rotate(-90deg);
}

.loading-progress circle:last-child {
    stroke: #1b6ec2;
    stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
    transition: stroke-dasharray 0.05s ease-in-out;
}

.loading-progress-text {
    position: absolute;
    text-align: center;
    font-weight: var(--font-weight-bold);
    inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
}

.loading-progress-text:after {
    content: var(--blazor-load-percentage-text, "Loading");
}

/* ═══════════ Glow Effects (dark themes only) ═══════════ */
/* !important needed to override Syncfusion theme CSS and Blazor scoped CSS specificity */

/* Status dot glow — stronger on dark themes */
[data-bs-theme="dark"] .status-dot-online {
    box-shadow: 0 0 var(--glow-md) 3px rgb(var(--status-online-rgb) / 0.7) !important;
}
[data-bs-theme="dark"] .status-dot-offline {
    box-shadow: 0 0 var(--glow-md) 3px rgb(var(--status-offline-rgb) / 0.7) !important;
}
[data-bs-theme="dark"] .status-dot-warning {
    box-shadow: 0 0 var(--glow-md) 3px rgb(var(--status-warning-rgb) / 0.7) !important;
}

/* Card border glow + hover intensify */
[data-bs-theme="dark"] .card {
    border: 1px solid rgb(var(--source-grid-rgb) / 0.15) !important;
    box-shadow: 0 0 var(--glow-lg) rgb(var(--source-grid-rgb) / 0.08) !important;
    transition: box-shadow var(--motion-base) var(--easing-standard), border-color var(--motion-base) var(--easing-standard) !important;
}
[data-bs-theme="dark"] .card:hover {
    border-color: rgb(var(--source-grid-rgb) / 0.3) !important;
    box-shadow: 0 0 var(--glow-xl) rgb(var(--source-grid-rgb) / 0.18), 0 4px var(--glow-lg) rgb(0 0 0 / 0.4) !important;
}

/* Source pill glow */
[data-bs-theme="dark"] .source-pill-grid-active {
    box-shadow: 0 0 var(--glow-lg) rgb(var(--source-grid-rgb) / 0.5) !important;
}
[data-bs-theme="dark"] .source-pill-genset-active {
    box-shadow: 0 0 var(--glow-lg) rgb(var(--source-genset-rgb) / 0.5) !important;
}
[data-bs-theme="dark"] .source-pill-solar-active {
    box-shadow: 0 0 var(--glow-lg) rgb(var(--source-solar-rgb) / 0.5) !important;
}

/* KPI large value glow */
[data-bs-theme="dark"] h2.fw-bold,
[data-bs-theme="dark"] h3.fw-bold,
[data-bs-theme="dark"] .display-6 {
    text-shadow: 0 0 var(--glow-lg) rgb(var(--source-grid-rgb) / 0.4) !important;
}

/* Chart SVG glow — makes lines appear to emit light */
[data-bs-theme="dark"] .e-chart svg {
    filter: drop-shadow(0 0 var(--glow-sm) rgb(var(--source-grid-rgb) / 0.25)) !important;
}

/* Badge glow */
[data-bs-theme="dark"] .badge.bg-success {
    box-shadow: 0 0 var(--glow-md) rgb(var(--severity-normal-rgb) / 0.5) !important;
}
[data-bs-theme="dark"] .badge.bg-danger {
    box-shadow: 0 0 var(--glow-md) rgb(var(--severity-critical-rgb) / 0.5) !important;
}
[data-bs-theme="dark"] .badge.bg-warning {
    box-shadow: 0 0 var(--glow-md) rgb(var(--severity-warning-rgb) / 0.5) !important;
}
[data-bs-theme="dark"] .badge.bg-primary {
    box-shadow: 0 0 var(--glow-md) rgb(var(--severity-info-rgb) / 0.5) !important;
}

/* Active nav link glow */
[data-bs-theme="dark"] .nav-link.active {
    text-shadow: 0 0 var(--glow-md) rgb(var(--source-grid-rgb) / 0.5) !important;
}

/* ═══════════ Device status indicators — live blink signal ═══════════ */
/* The live online/offline blink is applied to the ACTUAL rendered dot classes
   (.status-dot-online/.status-dot-offline below); the former orphaned
   .tree-status-dot.* rules were never wired to any markup (OBS-RESMOKE-2) and
   have been removed. Stale/disabled/never-seen stay steady by design. The
   blink is gated by the global prefers-reduced-motion override. */

@keyframes blink-green {
    0%, 100% { opacity: 1; box-shadow: 0 0 var(--glow-sm) rgb(var(--status-online-rgb) / 0.6); }
    50% { opacity: 0.4; box-shadow: 0 0 2px rgb(var(--status-online-rgb) / 0.2); }
}

@keyframes blink-red {
    0%, 100% { opacity: 1; box-shadow: 0 0 var(--glow-sm) rgb(var(--status-offline-rgb) / 0.6); }
    50% { opacity: 0.4; box-shadow: 0 0 2px rgb(var(--status-offline-rgb) / 0.2); }
}

/* ═══════════ NavMenu ═══════════ */

.nav-link.active {
    background-color: rgba(255, 255, 255, 0.1) !important;
    border-radius: var(--radius-sm);
}

/* ═══════════════════════════════════════════════════════════════════════════
   Responsive app shell (mobile) — off-canvas navigation drawer
   The sidebar is a static rail on ≥ lg (≥992px), exactly as before. Below lg it
   becomes an off-canvas drawer: fixed and slid off-screen, revealed by the
   TopBar hamburger, over a dimmed backdrop. No Bootstrap JS dependency — open/
   close is Blazor state (AppState.IsMobileNavOpen) toggling a class; page content
   reflows to full width because the fixed drawer leaves the normal flex flow.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Backdrop is desktop-hidden by default (the hamburger uses Bootstrap's d-lg-none). */
.app-nav-backdrop { display: none; }

/* Let the content column shrink below its widest child. A flex item defaults to min-width:auto, so a
   wide descendant (a Syncfusion chart/grid, a data table) would otherwise stretch the whole column —
   and the page — past the viewport, causing horizontal overflow on narrow screens (the KPI right
   column and source pills getting pushed off-screen). min-width:0 lets the column stay at the
   available width so wide children scroll within their own container (.table-responsive) instead. */
.app-content-col { min-width: 0; }
.app-content-col > main { min-width: 0; }

/* Sidebar-header brand mark — the WattLedger rounded app-icon tile (same asset as the favicon /
   installed icon). Fixed square with the tile's own rounding; the flex header centres it with the text. */
.brand-tile {
    height: 2rem;
    width: 2rem;
    flex: none;
    border-radius: 22%;
}

/* Larger centred tile for the auth / onboarding cards (login, register, confirm-email, change-password).
   The tile carries its own navy ground, so it reads on both the light and dark card themes; a soft shadow
   lifts it on the light theme. */
.brand-tile-lg {
    width: 64px;
    height: 64px;
    border-radius: 22%;
    box-shadow: 0 6px 18px rgba(0, 0, 0, .22);
}

/* On the desktop rail, the collapsed state hides labels (icon-only). Scoped to ≥ lg
   so the mobile drawer always shows full labels regardless of the desktop collapse state. */
@media (min-width: 992px) {
    .app-sidebar-collapsed .nav-label { display: none; }
}

@media (max-width: 991.98px) {
    .app-sidebar {
        position: fixed;
        top: 0;
        bottom: 0;
        left: 0;
        z-index: 1045;
        width: 250px !important;   /* full-width drawer even if the desktop rail was collapsed */
        transform: translateX(-100%);
        transition: transform var(--motion-base) var(--easing-standard);
    }

    .app-sidebar.app-sidebar-open { transform: translateX(0); }

    .app-nav-backdrop.show {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 1040;
        background: rgb(0 0 0 / 0.5);
    }

    /* Keep the site selector from pushing the top bar past the viewport width. */
    .topbar-site { max-width: 42vw; }

    /* Trim the generous desktop content padding on phones — main's p-4 (1.5rem) stacks on top of each
       page's own container padding, wasting ~half a centimetre of a narrow screen on each side. */
    .app-content-col > main { padding: 1rem !important; }
}

/* Larger touch targets for compact buttons on touch / coarse-pointer devices — the WCAG 2.5.5 /
   platform ~44px guideline. Icon-only action buttons (e.g. the Configuration grid actions) also get a
   min-width so a lone glyph is comfortably tappable. Desktop mouse UI is untouched. */
@media (pointer: coarse) {
    .btn-sm { min-height: 44px; }
    .btn-group-sm > .btn, .btn-sm:has(> .bi:only-child) { min-width: 44px; }
}

/* ═══════════ Monitoring — Device list, status dots & source pills ═══════════ */

.device-list {
    max-height: 300px;
    overflow-y: auto;
}

.device-list .list-group-item {
    border: none;
    padding: var(--space-2) var(--space-3);
    cursor: pointer;
    transition: background-color var(--motion-fast);
}

.device-list .list-group-item:not(.active):hover {
    background-color: rgba(255, 255, 255, 0.05);
}

.status-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: var(--radius-round);
    flex-shrink: 0;
}

.status-dot-online {
    background-color: var(--severity-normal);
    box-shadow: 0 0 var(--glow-xs) rgb(var(--status-online-rgb) / 0.5);
    animation: blink-green var(--motion-blink) var(--easing-emphasis) infinite;
}

.status-dot-warning {
    background-color: var(--severity-warning);
    box-shadow: 0 0 var(--glow-xs) rgb(var(--status-warning-rgb) / 0.5);
}

.status-dot-offline {
    background-color: var(--severity-critical);
    box-shadow: 0 0 var(--glow-xs) rgb(var(--status-offline-rgb) / 0.5);
    animation: blink-red var(--motion-blink) var(--easing-emphasis) infinite;
}

.status-dot-disabled {
    background-color: var(--source-muted);
}

.source-pill-grid-active {
    background-color: var(--source-grid) !important;
    color: var(--text-on-dark-fill) !important;
}

.source-pill-genset-active {
    background-color: var(--source-genset) !important;
    color: var(--text-on-dark-fill) !important;
}

.source-pill-solar-active {
    background-color: var(--source-solar) !important;
    color: var(--text-on-light-fill) !important;
}

.source-pill-dimmed {
    background-color: rgb(var(--source-muted-rgb) / 0.2) !important;
    color: var(--source-muted) !important;
    opacity: 0.7;
}

.source-pill-inactive {
    background-color: rgb(var(--source-muted-rgb) / 0.3) !important;
    color: var(--source-muted) !important;
}

/* ═══════════════════════════════════════════════════════════════════════════
   Shared empty / loading / error states (P8-T3 — FR-8.7 / FR-7.9)
   Consumed by EmsEmptyState / EmsErrorState (EmsLoadingSkeleton owns its own
   pulse). Colors come from the severity design tokens; the icon glyph + copy
   always accompany the color, so meaning is never carried by color alone.
   ═══════════════════════════════════════════════════════════════════════════ */
.ems-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 3rem var(--space-3);
    min-height: 240px;
}

.ems-state-compact {
    padding: var(--space-3);
    min-height: auto;
}

.ems-state-icon {
    font-size: 2.5rem;
    line-height: 1;
    margin-bottom: var(--space-3);
    color: var(--source-muted); /* Neutral default */
}

.ems-state-compact .ems-state-icon {
    font-size: 1.75rem;
    margin-bottom: var(--space-2);
}

.ems-state-icon.sev-neutral { color: var(--source-muted); }
.ems-state-icon.sev-info { color: var(--severity-info); }
.ems-state-icon.sev-success { color: var(--severity-normal); }
.ems-state-icon.sev-warning { color: var(--severity-warning-fg); } /* P8-T4: contrast-safe amber as an icon color */
.ems-state-icon.sev-critical { color: var(--severity-critical); }

.ems-state-title {
    margin-bottom: var(--space-2);
    font-weight: var(--font-weight-semibold);
}

.ems-state-text {
    max-width: 42rem;
    margin-bottom: 0;
    color: var(--bs-secondary-color, var(--source-muted));
}

.ems-state-detail {
    max-width: 42rem;
    margin-top: var(--space-2);
    margin-bottom: 0;
}

.ems-state-body { margin-top: var(--space-3); }

.ems-state-action { margin-top: var(--space-3); }

/* ═══════════════════════════════════════════════════════════════════════════
   Reduced motion (P8-T4 — FR-8.9 / FR-8.13, WCAG 2.3.3)
   One media query honors the user's OS "reduce motion" preference across the app.
   It neutralizes every non-essential animation/transition — the status-dot blink
   (blink-green/blink-red), the loading-skeleton pulse (ems-skeleton-pulse, defined
   in EmsLoadingSkeleton), and the card border/shadow glow transitions — without
   removing any information (all state is also carried by color+icon+label and by
   the dots' static box-shadow glow, which is not animated away). Motion is never
   required to understand the data (FR-8.13).
   ═══════════════════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
        scroll-behavior: auto !important;
    }
}

/* ═══════════════════════════════════════════════════════════════════════════
   In-app notification toasts (PA-5 — FR-5.3)
   Transient, severity-styled surfacing of a newly-arrived notification. Colors come
   from the P8-T1 severity tokens (same scale as .ems-state-icon.sev-*), so meaning is
   carried by the accent bar + icon + text — never color alone (FR-8.9). The slide-in
   is neutralized by the global reduced-motion block above. The host is bottom-right,
   above content (z 1060) but pointer-transparent so it never blocks the page; each
   toast re-enables pointer events for its own click-through / dismiss.
   ═══════════════════════════════════════════════════════════════════════════ */
.ems-toast-host {
    position: fixed;
    bottom: var(--space-3);
    right: var(--space-3);
    z-index: 1060;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    width: min(360px, calc(100vw - 2 * var(--space-3)));
    pointer-events: none;
}

.ems-toast {
    pointer-events: auto;
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm);
    background: var(--bs-body-bg);
    border: 1px solid rgb(var(--source-muted-rgb) / 0.25);
    border-left: 4px solid var(--source-muted);
    box-shadow: 0 4px var(--glow-lg) rgb(0 0 0 / 0.3);
    animation: ems-toast-in var(--motion-base) var(--easing-standard);
}

.ems-toast-neutral  { border-left-color: var(--source-muted); }
.ems-toast-info     { border-left-color: var(--severity-info); }
.ems-toast-success  { border-left-color: var(--severity-normal); }
.ems-toast-warning  { border-left-color: var(--severity-warning); }
.ems-toast-critical { border-left-color: var(--severity-critical); }

.ems-toast-icon {
    font-size: 1.1rem;
    line-height: 1.4;
    flex-shrink: 0;
    color: var(--source-muted);
}
.ems-toast-info    .ems-toast-icon { color: var(--severity-info); }
.ems-toast-success .ems-toast-icon { color: var(--severity-normal); }
.ems-toast-warning .ems-toast-icon { color: var(--severity-warning-fg); } /* contrast-safe amber (P8-T4) */
.ems-toast-critical .ems-toast-icon { color: var(--severity-critical); }

.ems-toast-body {
    flex-grow: 1;
    min-width: 0;
    cursor: pointer;
}

.ems-toast-title {
    font-weight: var(--font-weight-semibold);
    font-size: var(--font-size-sm);
}

.ems-toast-message {
    color: var(--bs-secondary-color, var(--source-muted));
    font-size: var(--font-size-xs);
    white-space: pre-line;
    display: -webkit-box;
    -webkit-line-clamp: 3;
    line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.ems-toast-close {
    flex-shrink: 0;
    font-size: 0.65rem;
    margin-top: 0.15rem;
}

@keyframes ems-toast-in {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* ─────────────────────────────────────────────────────────────────────────────
   EmsAppUpdateBanner — the "a new version is available" cue (PA-3 / C3). A
   persistent (never auto-dismissing) top bar so a mid-task operator controls the
   reload. Token-styled via the P8-T1 Info severity idiom (same scale as
   .ems-toast-info) — meaning carried by icon + text + accent, not color alone.
   ───────────────────────────────────────────────────────────────────────────── */
.ems-update-banner {
    position: fixed;
    top: var(--space-3);
    left: 50%;
    transform: translateX(-50%);
    z-index: 1070;                       /* above the toast overlay (1060) and the mobile drawer */
    display: flex;
    align-items: center;
    gap: var(--space-3);
    max-width: min(560px, calc(100vw - 2 * var(--space-3)));
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm);
    background: var(--bs-body-bg);
    border: 1px solid rgb(var(--severity-info-rgb) / 0.35);
    border-left: 4px solid var(--severity-info);
    box-shadow: 0 4px var(--glow-lg) rgb(0 0 0 / 0.3);
    animation: ems-update-banner-in var(--motion-base) var(--easing-standard);
}

.ems-update-banner-icon {
    font-size: 1.2rem;
    flex-shrink: 0;
    color: var(--severity-info);
}

.ems-update-banner-body {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.ems-update-banner-title {
    font-weight: var(--font-weight-semibold);
    font-size: var(--font-size-sm);
}

.ems-update-banner-text {
    color: var(--bs-secondary-color, var(--source-muted));
    font-size: var(--font-size-xs);
}

.ems-update-banner-actions {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    flex-shrink: 0;
    margin-left: auto;
}

.ems-update-banner-dismiss {
    color: var(--bs-secondary-color, var(--source-muted));
    text-decoration: none;
}

@keyframes ems-update-banner-in {
    from { opacity: 0; transform: translate(-50%, -8px); }
    to   { opacity: 1; transform: translate(-50%, 0); }
}

/* ───────── Power-Flow on-demand vitals overlay (PF-3 / Spec 10 FR-10.6) ─────────
   A lightweight centered panel + dimmed backdrop for the per-meter vitals drill-down
   (no Bootstrap JS — a CSS overlay, mirroring the app-nav-backdrop convention). */
.ems-vitals-backdrop {
    position: fixed;
    inset: 0;
    background: rgb(0 0 0 / 0.5);
    z-index: 1050;
}

.ems-vitals-panel {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: min(720px, calc(100vw - 2rem));
    max-height: calc(100vh - 3rem);
    overflow-y: auto;
    z-index: 1051;
}
