/* ==========================================================================
   Design tokens — light default, dark by preference or explicit choice
   --------------------------------------------------------------------------
   Originally one dark theme only ("Command Deck", 2026-08-24 — see git
   history). Reworked here into a real light/dark pair: bare :root below is
   the LIGHT palette; the @media block redefines the same tokens for a
   system dark preference; the [data-theme] rules let an explicit toggle
   (NavMenu's theme button) override either way, and win over the media
   query in both directions. Every rule in this file still reads these
   tokens rather than a literal colour, which is what makes a theme switch
   a token-block change rather than a page-by-page rewrite — see "Bootstrap
   components, aligned to the design tokens" below.

   The Dashboard's .cmd-* vocabulary is the one deliberate exception: it
   stays visually dark regardless of this toggle (an "instrument panel" look
   chosen on its own merits, not derived from these tokens) — see the
   "Customer dashboard" comment further down for why its custom properties
   are literal values rather than aliases onto the tokens below.

   Contrast: the original dark values below carry the exact ratios measured
   when this was the only palette (see git history). The light values and
   the new --status-info pair are chosen for the same relationship — status
   colour vs. its own tinted background, "ink" text variant a shade further
   from that background than the mark/dot needs — but were not re-measured
   with a contrast tool in this pass, so treat them as a reasoned starting
   point rather than a verified number the way the dark ones are.
   ========================================================================== */
:root {
    color-scheme: light;

    --surface-page:   #f2f5f5;
    --surface-card:   #ffffff;
    --text-primary:   #0f2023;
    --text-secondary: #445658;
    --text-muted:     #6d7f81;
    --border:         rgba(15, 32, 35, 0.10);
    --border-strong:  rgba(15, 32, 35, 0.18);

    --accent-blue:    #0c8a7d;
    --accent-blue-dk: #0a6e63;
    --accent-green:   #188a5c;
    --accent-green-dk: #106b46;
    --accent-violet:  #6a5fd1;

    /* Text colour to place on a solid --accent-blue surface (the Dashboard's cmd-btn-primary
       and its "on" state). --accent-blue itself flips from a bright cyan (dark theme — needs
       dark text) to a medium-dark teal (light theme — needs light text), so which one reads
       depends on the theme, not just the accent. */
    --accent-blue-ink: #ffffff;

    --status-good:        #0c8a7d;
    --status-good-bg:     #e2f5f2;
    --status-warning:     #9a6b0a;
    --status-warning-bg:  #fdf1dd;
    --status-critical:    #c23b3b;
    --status-critical-bg: #fbe8e8;
    --status-neutral:     #5c6f71;
    --status-neutral-bg:  #eef2f2;

    /* Info is a fifth status hue (log-level "info", distinct from the teal accent) —
       previously a literal on .level-chip.level-info rather than a token at all. */
    --status-info:        #1a6fc4;
    --status-info-bg:     #e7f1fc;

    /* Darker "ink" variants for small status TEXT on a light background — the mark/dot
       above only needs to clear ~3:1 against the tinted background, text needs ~4.5:1,
       so ink moves further from that background than the base colour does. */
    --status-good-ink:     #0a6e63;
    --status-warning-ink:  #7a5608;
    --status-critical-ink: #9c2f2f;
    --status-neutral-ink:  #37474a;
    --status-info-ink:     #14508c;

    --shadow-card: 0 1px 2px rgba(15, 32, 35, 0.06), 0 4px 12px rgba(15, 32, 35, 0.06);

    /* One radius scale for every rounded corner in the app — previously each component
       picked its own value (8/10/12/14/16px) independently of the others despite there
       being no visual reason for the differences. Theme-invariant: rounding doesn't change
       with light/dark. */
    --radius-xs:   6px;
    --radius-sm:   8px;
    --radius-md:   10px;
    --radius-card: 12px;
    --radius-lg:   14px;
    --radius-xl:   16px;
    --radius-pill: 999px;

    /* Same idea as the radius scale above, for padding — previously a near-continuous spread
       of one-off rem values (0.2 through 1.75) with no shared steps. Applying this snaps each
       rule to its nearest step, so small drift (e.g. 0.95rem -> 1rem) is expected and was
       eyeballed rather than treated as a bug. .badge and .pill are deliberately left alone:
       both are small, widely-reused chip primitives already visually tuned, and .badge's own
       padding is em-based (proportional to its font-size) rather than a fixed spacing step. */
    --space-3xs: 0.2rem;
    --space-2xs: 0.35rem;
    --space-xs:  0.5rem;
    --space-sm:  0.75rem;
    --space-md:  1rem;
    --space-lg:  1.25rem;
    --space-xl:  1.75rem;

    /* Same idea again, for font-size — the shared component vocabulary below (cards, pills,
       tables, forms, automation) previously picked its own size independently of every other
       selector (0.72 through 1.6rem, no shared steps). Snapped to the nearest step here, so
       small drift (e.g. 0.9rem -> 0.95rem) is expected, the same tolerance the spacing pass
       above allowed. The Dashboard's .cmd-* vocabulary keeps its own sizes — a deliberately
       separate component language, not an oversight (see the "Customer dashboard" comment
       further down), so it is left untouched here. */
    --text-2xs:  0.72rem;
    --text-xs:   0.8rem;
    --text-sm:   0.85rem;
    --text-md:   0.95rem;
    --text-base: 1rem;
    --text-lg:   1.05rem;
    --text-xl:   1.25rem;
    --text-2xl:  1.6rem;

    --font-display: "Space Grotesk", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    --font-mono: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;

    /* Persistent chrome (sidebar) predates the Command Deck palette above and keeps its
       own brand gradient rather than the page surfaces — named here instead of left as
       literal hex in MainLayout/NavMenu's scoped CSS, so it's a value like any other.
       Theme-invariant on purpose: the sidebar is the app's constant brand chrome and does
       not switch with the page content behind it. */
    --sidebar-grad-start: #0b3d59;
    --sidebar-grad-end:   #0f6e4f;
    --sidebar-text:       #d7d7d7;
}

/* System dark preference, no explicit choice made yet — the :not() guard is what lets an
   explicit "light" choice below win over a dark OS setting instead of being fought by it. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        color-scheme: dark;

        --surface-page:   #0b1014;
        --surface-card:   #121a20;
        --text-primary:   #e7f1f2;
        --text-secondary: #b4c4ca;
        --text-muted:     #7c9099;
        --border:         rgba(231, 241, 242, 0.12);
        --border-strong:  rgba(231, 241, 242, 0.20);

        --accent-blue:    #39d6c8;
        --accent-blue-dk: #2bb0a4;
        --accent-green:   #35c98f;
        --accent-green-dk: #159362;
        --accent-violet:  #8f8bf0;
        --accent-blue-ink: #06231f;

        --status-good:        #39d6c8;  /* 9.73:1 on card */
        --status-good-bg:     #18383b;
        --status-warning:     #f2b544;  /* 9.60:1 on card */
        --status-warning-bg:  #363326;
        --status-critical:    #ef6f6f;  /* 6.01:1 on card */
        --status-critical-bg: #35282d;
        --status-neutral:     #7c9099;
        --status-neutral-bg:  #232d33;
        --status-info:        #63a9f2;
        --status-info-bg:     #16263a;

        /* Lighter "ink" variants for small status TEXT — see git history for the exact
           numbers that shaped it. Measured here: good 8.68:1, warning 8.44:1, critical
           7.13:1, neutral 7.87:1, each against its own tinted background. */
        --status-good-ink:     #7ee8dd;
        --status-warning-ink:  #f7cd7e;
        --status-critical-ink: #ff9e9e;
        --status-neutral-ink:  #b7c4ca;
        --status-info-ink:     #8fc0f5;

        --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.4), 0 4px 12px rgba(0, 0, 0, 0.3);
    }
}

/* Explicit choice from the NavMenu toggle, set as data-theme on <html> by the inline script
   in App.razor and persisted to localStorage — wins over both the OS preference above and
   (for "dark") over a light OS default below. */
:root[data-theme="dark"] {
    color-scheme: dark;

    --surface-page:   #0b1014;
    --surface-card:   #121a20;
    --text-primary:   #e7f1f2;
    --text-secondary: #b4c4ca;
    --text-muted:     #7c9099;
    --border:         rgba(231, 241, 242, 0.12);
    --border-strong:  rgba(231, 241, 242, 0.20);

    --accent-blue:    #39d6c8;
    --accent-blue-dk: #2bb0a4;
    --accent-green:   #35c98f;
    --accent-green-dk: #159362;
    --accent-violet:  #8f8bf0;
    --accent-blue-ink: #06231f;

    --status-good:        #39d6c8;
    --status-good-bg:     #18383b;
    --status-warning:     #f2b544;
    --status-warning-bg:  #363326;
    --status-critical:    #ef6f6f;
    --status-critical-bg: #35282d;
    --status-neutral:     #7c9099;
    --status-neutral-bg:  #232d33;
    --status-info:        #63a9f2;
    --status-info-bg:     #16263a;

    --status-good-ink:     #7ee8dd;
    --status-warning-ink:  #f7cd7e;
    --status-critical-ink: #ff9e9e;
    --status-neutral-ink:  #b7c4ca;
    --status-info-ink:     #8fc0f5;

    --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.4), 0 4px 12px rgba(0, 0, 0, 0.3);
}

html, body {
    font-family: var(--font-display);
    background-color: var(--surface-page);
    color: var(--text-primary);
}

a, .btn-link {
    color: var(--accent-blue);
}

.btn-primary {
    color: #fff;
    background-color: var(--accent-blue);
    border-color: var(--accent-blue-dk);
}

.btn-primary:hover, .btn-primary:focus {
    background-color: var(--accent-blue-dk);
    border-color: var(--accent-blue-dk);
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
  /* Inner ring cuts the focused control out from whatever is behind it — the same card or
     page surface either theme is currently painting — so the outer, always-blue ring reads
     clearly against both. */
  box-shadow: 0 0 0 0.1rem var(--surface-card), 0 0 0 0.25rem var(--accent-blue);
}

.content {
    padding: var(--space-xl) 0;
}

h1 {
    font-size: var(--text-2xl);
    font-weight: 650;
    margin-bottom: 1.25rem;
}

h1:focus {
    outline: none;
}

h4 {
    font-size: var(--text-lg);
    font-weight: 650;
    color: var(--text-primary);
    margin: 1.75rem 0 0.9rem;
}

h4:first-child {
    margin-top: 0;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid var(--status-good);
}

.invalid {
    outline: 1px solid var(--status-critical);
}

.validation-message {
    color: var(--status-critical);
}

.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: var(--border-strong);
}

/* ==========================================================================
   Cards
   ========================================================================== */
.card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 1rem;
    margin-bottom: 1.5rem;
}

.dash-card {
    background: var(--surface-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-card);
    padding: var(--space-lg) var(--space-lg);
}

.dash-card-title {
    font-size: var(--text-xs);
    font-weight: 650;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--text-muted);
    margin-bottom: 0.75rem;
}

.dash-card-wide {
    grid-column: 1 / -1;
}

/* ==========================================================================
   Battery gauge
   ========================================================================== */
.gauge-row {
    display: flex;
    align-items: center;
    gap: 1.25rem;
}

.gauge {
    flex-shrink: 0;
}

.gauge-track {
    fill: none;
    stroke: var(--border);
    stroke-width: 10;
}

.gauge-value {
    fill: none;
    stroke-width: 10;
    stroke-linecap: round;
    transition: stroke-dashoffset 0.4s ease, stroke 0.4s ease;
}

.gauge-value.status-good { stroke: var(--status-good); }
.gauge-value.status-warning { stroke: var(--status-warning); }
.gauge-value.status-critical { stroke: var(--status-critical); }
.gauge-value.status-neutral { stroke: var(--text-muted); }

.gauge-center-text {
    font-size: 1.5rem;
    font-weight: 700;
    fill: var(--text-primary);
    text-anchor: middle;
    dominant-baseline: middle;
}

.gauge-caption {
    color: var(--text-secondary);
    font-size: var(--text-md);
    line-height: 1.5;
}

.gauge-caption strong {
    color: var(--text-primary);
}

/* ==========================================================================
   Status pills / chips (icon + label, never color alone)
   ========================================================================== */
.pill {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.3rem 0.75rem;
    border-radius: var(--radius-pill);
    font-size: var(--text-sm);
    font-weight: 600;
}

.pill-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    flex-shrink: 0;
}

/* Label text uses the -ink variants, the dot keeps the bright colour. A dot is a mark,
   which only needs 3:1; the words are small text and need 4.5:1, which the bright
   colours miss (warning was 2.74:1 on its own tint). Same hue, legible weight. */
.pill.status-good { background: var(--status-good-bg); color: var(--status-good-ink); }
.pill.status-good .pill-dot { background: var(--status-good); }

.pill.status-warning { background: var(--status-warning-bg); color: var(--status-warning-ink); }
.pill.status-warning .pill-dot { background: var(--status-warning); }

.pill.status-critical { background: var(--status-critical-bg); color: var(--status-critical-ink); }
.pill.status-critical .pill-dot { background: var(--status-critical); }

.pill.status-neutral { background: var(--status-neutral-bg); color: var(--status-neutral-ink); }
.pill.status-neutral .pill-dot { background: var(--status-neutral); }

/* ==========================================================================
   Action buttons
   ========================================================================== */
.action-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
}

.action-row .btn {
    border-radius: var(--radius-sm);
    font-weight: 600;
    padding: var(--space-xs) var(--space-md);
}

.btn-ac-on {
    background-color: var(--accent-green);
    border-color: var(--accent-green);
    color: #fff;
}
.btn-ac-on:hover { background-color: var(--accent-green-dk); border-color: var(--accent-green-dk); }

.btn-ac-off {
    background-color: transparent;
    border-color: var(--status-critical);
    color: var(--status-critical);
}
.btn-ac-off:hover { background-color: var(--status-critical-bg); }

.btn-check-now {
    background-color: var(--accent-blue);
    border-color: var(--accent-blue-dk);
    color: #fff;
}
.btn-check-now:hover { background-color: var(--accent-blue-dk); }

/* ==========================================================================
   Activity table
   ========================================================================== */
.activity-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.9rem;
}

.activity-table th {
    text-align: left;
    color: var(--text-muted);
    font-weight: 650;
    font-size: var(--text-2xs);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    padding: var(--space-xs) var(--space-sm);
    border-bottom: 1px solid var(--border-strong);
}

.activity-table td {
    padding: var(--space-xs) var(--space-sm);
    border-bottom: 1px solid var(--border);
    color: var(--text-secondary);
    vertical-align: top;
}

.activity-table tbody tr:hover {
    background-color: var(--surface-page);
}

.activity-table td:first-child {
    white-space: nowrap;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

.level-chip {
    display: inline-block;
    padding: var(--space-3xs) var(--space-xs);
    border-radius: var(--radius-pill);
    font-size: var(--text-2xs);
    font-weight: 650;
    text-transform: uppercase;
    letter-spacing: 0.02em;
}

.level-chip.level-warning { background: var(--status-warning-bg); color: var(--status-warning-ink); }
.level-chip.level-error { background: var(--status-critical-bg); color: var(--status-critical-ink); }

/* ==========================================================================
   Settings form
   ========================================================================== */
/* No longer a box of its own — each section is a .collapsible-section now, which supplies the
   card chrome. .settings-section stays only as the scope for the field styling below, applied
   alongside .collapsible-section-body rather than replacing it. */
.settings-section h4 {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.settings-section .form-label {
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--text-secondary);
}

.settings-section .form-text {
    color: var(--text-muted);
    font-size: var(--text-xs);
}

.settings-section .form-control {
    border-color: var(--border-strong);
}

.settings-section .form-control:focus {
    border-color: var(--accent-blue);
}


/* ==========================================================================
   Telemetry stat tiles
   --------------------------------------------------------------------------
   The inverter readings are single headline numbers, so they are tiles rather
   than a chart — there is no series and no time axis to plot. Sits alongside
   .dash-card (which frames a section); this is the small-multiple inside one.

   Values wear text tokens, never a status colour. A reading is not "bad" for
   being low — what counts as bad is whatever the owner's rules say, and this
   app must not imply a verdict it was never given.
   ========================================================================== */
.stat-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 0.75rem;
}

.stat {
    background: var(--surface-page);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    padding: var(--space-sm) var(--space-md);
    min-width: 0;              /* long values wrap instead of stretching the grid */
}

.stat-label {
    font-size: var(--text-2xs);
    font-weight: 650;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--text-muted);
    margin-bottom: 0.25rem;
}

.stat-value {
    font-size: var(--text-xl);
    font-weight: 650;
    color: var(--text-primary);
    line-height: 1.2;
    /* Tabular figures stop the numbers shifting sideways as readings refresh. */
    font-variant-numeric: tabular-nums;
}

.stat-value .unit {
    font-size: var(--text-xs);
    font-weight: 500;
    color: var(--text-secondary);
    margin-left: 0.15rem;
}

/* Shared/DeviceTile.razor — a read-only per-device "glance" row (icon, name, state slot).
   Section U item 7 of docs/UI_UX_REDESIGN_PLAN.md. Plain tokens, not .cmd-* aliases, so it reads
   correctly wherever it's used — same approach .stat above already takes. */
.device-tile-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
}

.device-tile {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-xs) var(--space-sm);
    border-radius: var(--radius-md);
    border: 1px solid var(--border);
    background: var(--surface-page);
}

.device-tile-icon {
    width: 2rem;
    height: 2rem;
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-sm);
    background: var(--surface-card);
    border: 1px solid var(--border);
    font-size: var(--text-lg);
}

.device-tile-body {
    flex: 1;
    min-width: 0;
}

.device-tile-name {
    font-weight: 650;
    font-size: var(--text-sm);
    color: var(--text-primary);
}

.device-tile-state {
    margin-top: 0.2rem;
}

/* A figure this inverter doesn't report. Muted and italic so it reads as absent
   rather than as a number — the "null is not zero" rule, made visible. */
.stat-value.is-absent {
    font-size: var(--text-md);
    font-weight: 500;
    font-style: italic;
    color: var(--text-muted);
}

/* Heading with controls pushed to the right (e.g. Activity + its period buttons). */
.section-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem 1rem;
    margin-bottom: 0.9rem;
}

/* ==========================================================================
   Bootstrap components, aligned to the design tokens
   --------------------------------------------------------------------------
   The Rules, Admin and Login pages are built from stock Bootstrap .card /
   .table / .badge. Restyling them here rather than rewriting their markup
   keeps one visual language across every page, touches no Razor structure,
   and means a page added later inherits the look without being told to.
   ========================================================================== */

.card {
    background: var(--surface-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-card);
}

.card-body {
    padding: var(--space-lg) var(--space-lg);
}

.card-title {
    font-size: var(--text-lg);
    font-weight: 650;
    color: var(--text-primary);
}

/* Tables — same treatment as the activity log, so a table reads the same
   wherever it appears. */
.table {
    font-size: var(--text-md);
    color: var(--text-secondary);
    --bs-table-bg: transparent;
}

.table > thead > tr > th {
    font-size: var(--text-2xs);
    font-weight: 650;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--text-muted);
    border-bottom: 1px solid var(--border-strong);
}

.table > tbody > tr > td {
    border-bottom: 1px solid var(--border);
    vertical-align: middle;
}

/* Bootstrap's contextual badges carry meaning here ("Active", a rule problem),
   so they get the same accessible ink treatment as .pill: tinted background,
   dark text. The stock versions are white-on-saturated, which is both louder
   than this palette and, for the warning variant, under-contrasted. */
.badge.text-bg-success {
    background-color: var(--status-good-bg) !important;
    color: var(--status-good-ink) !important;
}

.badge.text-bg-warning {
    background-color: var(--status-warning-bg) !important;
    color: var(--status-warning-ink) !important;
}

.badge.text-bg-danger {
    background-color: var(--status-critical-bg) !important;
    color: var(--status-critical-ink) !important;
}

.badge.text-bg-secondary {
    background-color: var(--status-neutral-bg) !important;
    color: var(--status-neutral-ink) !important;
}

.badge {
    border-radius: var(--radius-pill);
    font-weight: 650;
    padding: 0.3em 0.7em;
}

/* Buttons keep Bootstrap's semantics but take the app's corner radius and
   weight, so they sit with .btn-check-now / .btn-ac-on rather than beside them. */
.btn {
    border-radius: var(--radius-sm);
    font-weight: 600;
}

.btn-outline-primary {
    color: var(--accent-blue);
    border-color: var(--accent-blue);
}

.btn-outline-primary:hover {
    background-color: var(--accent-blue);
    border-color: var(--accent-blue);
}

.form-label {
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--text-secondary);
}

.form-text {
    color: var(--text-muted);
    font-size: var(--text-xs);
}

.form-control, .form-select {
    border-color: var(--border-strong);
}

.form-control:focus, .form-select:focus {
    border-color: var(--accent-blue);
}

/* ==========================================================================
   Section headings
   --------------------------------------------------------------------------
   Section headings are <h2> so the document outline runs h1 > h2 > h3 without
   gaps — screen-reader users navigate by level, and jumping h1 to h4 reads as
   missing sections. Their size is set here rather than by picking a smaller
   tag, which is the whole point: visual weight and semantic level are separate
   decisions.
   ========================================================================== */
.section-title {
    font-size: var(--text-lg);
    font-weight: 650;
    color: var(--text-primary);
    margin: 0 0 0.9rem;
}

.callout {
    background: var(--surface-page);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    padding: var(--space-md) var(--space-lg);
}

.callout-title {
    font-size: var(--text-md);
    font-weight: 650;
    color: var(--text-primary);
    margin: 0;
}

/* One row of a stacked mobile card below the table breakpoint (d-md-none) — a label/value pair,
   used wherever a dense table gets a phone-width card fallback (Billing's payment history,
   Devices, the admin Dealers table): this app is meant to be used from a phone standing next to
   the hardware, and an many-column table with embedded actions doesn't work there. */
.payment-card-row {
    display: flex;
    justify-content: space-between;
    gap: var(--space-sm);
    padding: var(--space-2xs) 0;
    border-top: 1px solid var(--border);
}

.payment-card-row:first-of-type {
    border-top: none;
}

/* Wrapper for an aria-live region that is empty most of the time: it must stay
   in the DOM for updates to be announced, but must not occupy space until it
   has something to say. */
.visually-hidden-when-empty:empty {
    display: none;
}

/* ==========================================================================
   Touch targets
   --------------------------------------------------------------------------
   This app is used from a phone, standing in front of a plug. 44px is the
   smallest reliable target, and several controls sat under it — including the
   per-row Edit/Delete buttons on the Rules page, which are exactly the ones
   pressed in a hurry.
   ========================================================================== */
.btn,
.form-control,
.form-select {
    min-height: 44px;
}

/* Buttons inside a table row can be denser — the row itself gives them
   vertical space — but not so dense they become hard to hit. */
.table .btn-sm {
    min-height: 36px;
    padding-inline: var(--space-sm);
}

/* The activity period switcher is a segmented control, so it reads as compact
   even at full height. No reason to shrink it below the minimum. */
.btn-group .btn {
    min-height: 44px;
}

/* Row-level actions are the exception, and only on a precise pointer. On touch
   they go back to the full target — a mis-tap on a Delete button in a table row
   is exactly the kind of mistake this guards against. */
@media (pointer: coarse) {
    .table .btn-sm {
        min-height: 44px;
    }
}

/* ==========================================================================
   Dark-surface component overrides
   --------------------------------------------------------------------------
   Formerly scoped inside @media (prefers-color-scheme: dark), back when this
   file also had a light palette to override. Command Deck is the only theme
   now (see the :root block up top), so these apply unconditionally — Bootstrap
   still paints its own light surfaces underneath, and these are what point
   them at the tokens instead.
   ========================================================================== */
.form-control,
.form-select {
    background-color: var(--surface-page);
    color: var(--text-primary);
    border-color: var(--border-strong);
}

.form-control:focus,
.form-select:focus {
    background-color: var(--surface-page);
    color: var(--text-primary);
}

/* Bootstrap's own disabled/readonly rule paints a light gray background
   (#e9ecef) without touching color, so it fights the dark theme's
   near-white text above and leaves it unreadable — e.g. the locked
   Currency field on Edit Dealer. */
.form-control:disabled,
.form-control[readonly],
.form-select:disabled {
    background-color: var(--surface-card);
    color: var(--text-muted);
    border-color: var(--border);
}

.table {
    --bs-table-color: var(--text-secondary);
}

.activity-table tbody tr:hover,
.table > tbody > tr:hover {
    background-color: rgba(231, 241, 242, 0.04);
}

.level-chip.level-info {
    background: var(--status-info-bg);
    color: var(--status-info-ink);
}

/* ==========================================================================
   Collapsible sections
   --------------------------------------------------------------------------
   Built on <details>/<summary> rather than a JS accordion: it's keyboard-
   operable and exposes its own expanded/collapsed state to assistive tech for
   free, with no script and no ARIA to get wrong by hand. The summary carries
   the same visual weight as .dash-card-title, so a collapsed page still reads
   as the same design system, not a different control bolted on.
   ========================================================================== */
.collapsible-section {
    background: var(--surface-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-card);
    margin-bottom: 1.25rem;
}

.collapsible-section > summary {
    list-style: none;
    cursor: pointer;
    padding: var(--space-md) var(--space-lg);
    font-size: 1.05rem;
    font-weight: 650;
    color: var(--text-primary);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
}

/* Suppress the browser's own marker so the custom chevron is the only one. */
.collapsible-section > summary::-webkit-details-marker { display: none; }
.collapsible-section > summary::marker { content: ""; }

.collapsible-section > summary:focus-visible {
    outline: 2px solid var(--accent-blue);
    outline-offset: -2px;
}

.collapsible-section-chevron {
    flex: none;
    transition: transform 160ms ease;
    color: var(--text-muted);
}

.collapsible-section[open] > summary .collapsible-section-chevron {
    transform: rotate(90deg);
}

.collapsible-section-body {
    padding: 0 var(--space-lg) var(--space-lg);
    border-top: 1px solid var(--border);
    padding-top: var(--space-md);
}

@media (prefers-reduced-motion: reduce) {
    .collapsible-section-chevron { transition: none; }
}

/* ==========================================================================
   Row being edited
   --------------------------------------------------------------------------
   When a rule's (or device's) edit form is open below the table, its source
   row is marked so it's unambiguous which row the form belongs to — a tinted
   background plus a left accent bar, rather than relying on scroll position
   or memory alone.
   ========================================================================== */
.row-being-edited {
    background-color: color-mix(in srgb, var(--accent-blue) 8%, transparent);
    box-shadow: inset 3px 0 0 var(--accent-blue);
}

.row-being-edited td:first-child {
    position: relative;
}

/* ==========================================================================
   Device automation — sides, conditions and the resolved daily schedule
   --------------------------------------------------------------------------
   The schedule is the part worth designing carefully: it is the customer's
   proof of what their device will actually do, so each segment states its
   window, both sides' effective thresholds, and where each side came from.
   Provenance is text ("Default" / "Override"), never colour alone — the whole
   point is that an inherited value is distinguishable from an overridden one.
   ========================================================================== */
.automation-side {
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    padding: var(--space-md) var(--space-md);
    background: color-mix(in srgb, var(--surface-page) 55%, transparent);
    height: 100%;
}

.automation-side-title {
    font-weight: 700;
    font-size: var(--text-sm);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-secondary);
    margin-bottom: 0.6rem;
}

.automation-condition {
    margin: 0.5rem 0;
    padding: var(--space-xs);
    border-radius: var(--radius-sm);
    background: var(--surface-card);
    border: 1px solid var(--border);
}

/* ==========================================================================
   Automation — the branch visual (Level 3)
   --------------------------------------------------------------------------
   docs/UI_UX_MAKE_INSPIRED_PROPOSAL.md §6: condition chips (still .automation-
   condition, unchanged above) → an arrow → a coloured action badge → the
   device name, so cause and effect are drawn as connected rather than left in
   one box with no visual relationship. A pure re-skin around RenderSide's
   existing radios/inputs — nothing here is a new input mechanism.
   ========================================================================== */
.automation-branch {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 0.25rem;
    padding-top: var(--space-xs);
}

.automation-branch-chips {
    align-self: stretch;
    text-align: left;
}

.automation-branch-arrow {
    color: var(--text-muted);
    font-size: 1.1rem;
    line-height: 1;
}

.automation-branch-action {
    display: inline-flex;
    align-items: center;
    padding: 0.35rem 1rem;
    border-radius: var(--radius-pill);
    font-weight: 700;
    font-size: var(--text-sm);
    letter-spacing: 0.02em;
}

.automation-branch-action.is-on {
    background: var(--status-good-bg);
    color: var(--status-good-ink);
    border: 1px solid color-mix(in srgb, var(--accent-green) 45%, transparent);
}

.automation-branch-action.is-off {
    background: var(--status-critical-bg);
    color: var(--status-critical-ink);
    border: 1px solid color-mix(in srgb, var(--status-critical) 45%, transparent);
}

.automation-branch-device {
    font-weight: 650;
    color: var(--text-primary);
    padding: 0.25rem 0.75rem;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border-strong);
    background: var(--surface-card);
}

/* Configured but inert (Never) or nothing to show here (Inherit) — muted and dashed rather
   than left looking like an override that happens to have no conditions, per the same
   "provenance is text, never colour alone" rule the resolved schedule already follows. */
.automation-branch.is-inert,
.automation-branch.is-inherit {
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius-md);
    padding: var(--space-sm);
}

.automation-branch-label {
    color: var(--text-muted);
    font-style: italic;
}

/* ==========================================================================
   Automation — the 24h timeline (Level 3)
   --------------------------------------------------------------------------
   One bar per configured time range, stacked in list order (top = first =
   highest priority) and positioned by its own start/end minute — geometry
   only, over the same data the table below already reads.
   ========================================================================== */
.automation-timeline-viz {
    margin-bottom: var(--space-md);
}

.automation-timeline-viz-track {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-height: 2.75rem;
    padding: 2px;
    border-radius: var(--radius-md);
    background: var(--surface-page);
    border: 1px solid var(--border);
}

.automation-timeline-viz-now {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 2px;
    background: var(--accent-blue);
    z-index: 2;
    pointer-events: none;
}

.automation-timeline-viz-bar {
    appearance: none;
    cursor: pointer;
    position: relative;
    display: flex;
    align-items: center;
    gap: 0.35rem;
    height: 2rem;
    min-width: 2.25rem;
    padding: 0 0.5rem;
    border: 1px solid color-mix(in srgb, var(--accent-blue) 45%, transparent);
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--accent-blue) 14%, var(--surface-card));
    color: var(--text-primary);
    font-size: var(--text-2xs);
    overflow: hidden;
    white-space: nowrap;
    text-align: left;
}

.automation-timeline-viz-bar.is-editing {
    border-color: var(--accent-blue);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent-blue) 30%, transparent);
}

.automation-timeline-viz-bar.is-disabled {
    opacity: 0.5;
    border-style: dashed;
}

.automation-timeline-viz-bar-priority {
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.1rem;
    height: 1.1rem;
    border-radius: 50%;
    background: var(--accent-blue);
    color: var(--surface-page);
    font-weight: 700;
    font-size: 0.65rem;
}

.automation-timeline-viz-bar-name {
    overflow: hidden;
    text-overflow: ellipsis;
}

.automation-timeline-viz-ruler {
    display: flex;
    justify-content: space-between;
    margin-top: 0.25rem;
    color: var(--text-muted);
    font-size: var(--text-2xs);
    font-variant-numeric: tabular-nums;
}

.automation-range-editor {
    padding: var(--space-md);
    border-radius: var(--radius-md);
    background: color-mix(in srgb, var(--accent-blue) 4%, transparent);
}

/* ---- The resolved day ---- */
.automation-timeline {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}

.automation-segment {
    border: 1px solid var(--border);
    border-left: 4px solid var(--border-strong);
    border-radius: var(--radius-md);
    padding: var(--space-sm) var(--space-md);
    background: var(--surface-card);
}

/* The segment in force right now. Marked with an accent bar AND a "now" pill in
   the markup, so it isn't colour-only. */
.automation-segment.is-now {
    border-left-color: var(--accent-green);
    background: color-mix(in srgb, var(--accent-green) 5%, var(--surface-card));
}

.automation-segment-window {
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    margin-bottom: 0.5rem;
}

.automation-segment-sides {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
    gap: 0.75rem;
}

.automation-resolved-label {
    font-size: var(--text-2xs);
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-muted);
}

.automation-resolved-value {
    font-weight: 600;
    color: var(--text-primary);
}

/* A side that can never act is stated plainly rather than left looking like a
   threshold that happens to be unusual. */
.automation-resolved-value.is-inert {
    color: var(--text-muted);
    font-style: italic;
    font-weight: 500;
}

.automation-resolved-source {
    font-size: var(--text-xs);
    color: var(--text-secondary);
}

/* ==========================================================================
   Customer dashboard — "Command Deck" (2026-08-24)
   --------------------------------------------------------------------------
   Was "deliberately dark regardless of the visitor's system theme" when this
   comment was first written — there was no light palette yet, so that was
   the only theme in the app, full stop. Once a real light/dark toggle
   existed (see the token block up top and NavMenu's theme button), keeping
   this one view exempt meant the toggle visibly did nothing to most of the
   page a customer lands on by default — reported back as "the light mode
   button isn't working," which is the correct read of it from the outside
   even though it was working exactly as first written. --cd-* alias the
   shared tokens again so this page rides the same toggle as everywhere else.

   What stays deliberate, not drift: the cmd-* CLASSES (cmd-card, cmd-btn,
   cmd-sub-card, ...) are still their own component vocabulary, parallel to
   .card/.btn/.badge/.stat/.callout — flatter cards (no box-shadow), stat
   rows that read as a mono-font instrument panel, chips instead of pills.
   Only the colour values now follow the toggle; the shape of the page does
   not change.

   Reuses the app's existing semantic vocabulary — .pill.status-good/warning/
   critical/neutral, the same classes used everywhere else — so the meaning
   of a colour doesn't change on this page, only its paint.
   ========================================================================== */
.cmd-deck {
    --cd-bg: var(--surface-page);
    --cd-card: var(--surface-card);
    --cd-border: var(--border-strong);
    --cd-text: var(--text-primary);
    --cd-muted: var(--text-muted);
    --cd-cyan: var(--accent-blue);
    --cd-cyan-soft: color-mix(in srgb, var(--accent-blue) 12%, transparent);
    --cd-amber: var(--status-warning);
    --cd-amber-soft: color-mix(in srgb, var(--status-warning) 12%, transparent);
    --cd-red: var(--status-critical);
    --cd-red-soft: color-mix(in srgb, var(--status-critical) 14%, transparent);
    --cd-violet: var(--accent-violet);
    --cd-violet-soft: color-mix(in srgb, var(--accent-violet) 12%, transparent);
    --cd-mono: var(--font-mono);
    --cd-display: var(--font-display);

    background: var(--cd-bg);
    color: var(--cd-text);
    font-family: var(--cd-display);
    border-radius: var(--radius-xl);
    padding: var(--space-md) var(--space-md) var(--space-xl);
    margin: 0 -0.1rem 1.5rem;
}

.cmd-deck h2 { font-family: var(--cd-display); }

.cmd-hero {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    padding: var(--space-3xs) var(--space-3xs) var(--space-md);
}

.cmd-eyebrow {
    font-size: 0.8rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--cd-muted);
    font-weight: 600;
}

.cmd-headline {
    font-size: 1.55rem;
    font-weight: 600;
    margin-top: 0.2rem;
}

.cmd-status {
    font-family: var(--cd-mono);
    font-size: 0.85rem;
    font-weight: 600;
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: var(--space-2xs) var(--space-sm);
    border-radius: var(--radius-sm);
    border: 1px solid transparent;
    white-space: nowrap;
}

.cmd-status.status-good { background: var(--cd-cyan-soft); color: var(--cd-cyan); border-color: color-mix(in srgb, var(--cd-cyan) 35%, transparent); }
.cmd-status.status-warning { background: var(--cd-amber-soft); color: var(--cd-amber); border-color: color-mix(in srgb, var(--cd-amber) 35%, transparent); }
.cmd-status.status-critical { background: var(--cd-red-soft); color: var(--cd-red); border-color: color-mix(in srgb, var(--cd-red) 35%, transparent); }

.cmd-blip {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
    box-shadow: 0 0 0 3px currentColor;
    opacity: 0.9;
    flex: none;
}

.cmd-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    flex-wrap: wrap;
    background: var(--cd-amber-soft);
    border: 1px solid color-mix(in srgb, var(--cd-amber) 35%, transparent);
    color: var(--cd-text);
    border-radius: var(--radius-card);
    padding: var(--space-sm) var(--space-md);
    margin-bottom: 1.1rem;
    font-size: 0.98rem;
}

.cmd-alert {
    background: var(--cd-red-soft);
    border: 1px solid color-mix(in srgb, var(--cd-red) 35%, transparent);
    color: var(--cd-text);
    border-radius: var(--radius-card);
    padding: var(--space-sm) var(--space-md);
    font-size: 0.95rem;
    margin-top: 0.75rem;
}

/* A small echo of the Automation page's branch visual, in the Dashboard's own instrument-panel
   vocabulary rather than borrowing .automation-* — see the "Automation" card in Dashboard.razor. */
.cmd-mini-branch {
    margin-top: var(--space-sm);
    padding-top: var(--space-sm);
    border-top: 1px solid var(--cd-border);
}

.cmd-mini-branch-device {
    font-family: var(--cd-mono);
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--cd-muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin-bottom: 0.4rem;
}

.cmd-mini-branch-sides {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.cmd-deck .cmd-mini-branch-sides .pill {
    white-space: normal;
    text-align: left;
}

.cmd-rings {
    display: flex;
    gap: 0.75rem;
    margin: 0 0.2rem 1.1rem;
}

.cmd-ring-card {
    flex: 1;
    background: var(--cd-card);
    border: 1px solid var(--cd-border);
    border-radius: var(--radius-lg);
    padding: var(--space-sm) var(--space-xs);
    text-align: center;
}

.cmd-ring { display: block; margin: 0 auto 0.4rem; }
.cmd-ring-track { fill: none; stroke: var(--cd-border); stroke-width: 9; }
.cmd-ring-value { fill: none; stroke-width: 9; stroke-linecap: round; transition: stroke-dashoffset 0.4s ease; }
.cmd-ring-value.status-good { stroke: var(--cd-cyan); }
.cmd-ring-value.status-warning { stroke: var(--cd-amber); }
.cmd-ring-value.status-critical { stroke: var(--cd-red); }
.cmd-ring-value.status-neutral { stroke: var(--cd-muted); }
.cmd-ring-text {
    font-family: var(--cd-mono);
    font-size: 1.3rem;
    font-weight: 600;
    fill: var(--cd-text);
    text-anchor: middle;
    dominant-baseline: middle;
}

.cmd-ring-label {
    font-size: 0.8rem;
    color: var(--cd-muted);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-weight: 600;
}

.cmd-section { margin: 0 0.2rem 1.1rem; }

.cmd-section-title {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.6rem;
    margin-bottom: 0.55rem;
}

.cmd-section-title h2 {
    font-size: 0.9rem;
    font-weight: 600;
    margin: 0;
    color: var(--cd-muted);
    text-transform: uppercase;
    letter-spacing: 0.09em;
}

.cmd-card {
    background: var(--cd-card);
    border: 1px solid var(--cd-border);
    border-radius: var(--radius-lg);
    padding: var(--space-3xs) var(--space-md);
}

.cmd-muted-line {
    color: var(--cd-muted);
    font-size: 0.98rem;
    padding: var(--space-md) 0;
    margin: 0;
}

.cmd-caption {
    color: var(--cd-muted);
    font-size: 0.88rem;
    padding: var(--space-xs) 0 var(--space-3xs);
    margin: 0;
}

.cmd-stat-list { display: flex; flex-direction: column; }

.cmd-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: var(--space-sm) 0;
}

.cmd-row + .cmd-row { border-top: 1px solid var(--cd-border); }

.cmd-row-who { display: flex; align-items: center; gap: 0.65rem; min-width: 0; }
.cmd-row-icon { font-size: 1.1rem; width: 24px; text-align: center; flex: none; }
.cmd-row-label { font-size: 1rem; color: var(--cd-text); }

.cmd-row-val {
    font-family: var(--cd-mono);
    font-size: 1.05rem;
    font-weight: 600;
    color: var(--cd-cyan);
    font-variant-numeric: tabular-nums;
    text-align: right;
    white-space: nowrap;
}

.cmd-row-val.cmd-row-absent {
    font-family: var(--cd-display);
    font-style: italic;
    font-weight: 500;
    color: var(--cd-muted);
}

.cmd-unit { font-weight: 500; font-size: 0.78em; margin-left: 0.15em; color: var(--cd-muted); }

.cmd-sub-card {
    background: var(--cd-card);
    border: 1px solid var(--cd-border);
    border-radius: var(--radius-lg);
    padding: var(--space-md) var(--space-md);
}

.cmd-sub-top { display: flex; align-items: center; justify-content: space-between; gap: 0.6rem; margin-bottom: 0.6rem; }
.cmd-sub-plan { font-weight: 600; font-size: 1.08rem; }

.cmd-sub-badge {
    font-family: var(--cd-mono);
    font-size: 0.8rem;
    font-weight: 600;
    background: var(--cd-amber-soft);
    color: var(--cd-amber);
    border: 1px solid color-mix(in srgb, var(--cd-amber) 35%, transparent);
    padding: var(--space-2xs) var(--space-xs);
    border-radius: var(--radius-xs);
    white-space: nowrap;
}

.cmd-sub-badge.is-good { background: var(--cd-cyan-soft); color: var(--cd-cyan); border-color: color-mix(in srgb, var(--cd-cyan) 35%, transparent); }
.cmd-sub-badge.is-critical { background: var(--cd-red-soft); color: var(--cd-red); border-color: color-mix(in srgb, var(--cd-red) 35%, transparent); }

.cmd-bar-track { height: 6px; border-radius: 4px; background: var(--cd-border); overflow: hidden; margin: 0.5rem 0 0.45rem; }
.cmd-bar-fill { height: 100%; background: var(--cd-cyan); }

.cmd-sub-meta {
    display: flex;
    justify-content: space-between;
    gap: 0.6rem;
    font-size: 0.84rem;
    color: var(--cd-muted);
    font-family: var(--cd-mono);
}

.cmd-renewal-list { display: flex; flex-direction: column; }

.cmd-renewal-item { display: flex; gap: 0.75rem; padding: var(--space-sm) 0; }
.cmd-renewal-item + .cmd-renewal-item { border-top: 1px dashed var(--cd-border); }

.cmd-rdot { width: 8px; height: 8px; border-radius: 50%; background: var(--cd-violet); margin-top: 0.4rem; flex: none; }
.cmd-rmain { flex: 1; min-width: 0; }
.cmd-rdate { font-family: var(--cd-mono); font-weight: 600; font-size: 0.95rem; }
.cmd-rdesc { font-size: 0.88rem; color: var(--cd-muted); }
.cmd-ramt { font-family: var(--cd-mono); font-size: 0.88rem; color: var(--cd-violet); white-space: nowrap; align-self: center; }

.cmd-btn {
    appearance: none;
    border-radius: var(--radius-sm);
    font-weight: 600;
    font-size: 0.95rem;
    padding: var(--space-xs) var(--space-md);
    min-height: 44px;
    border: 1px solid transparent;
    cursor: pointer;
    font-family: var(--cd-display);
}

.cmd-btn:disabled { opacity: 0.5; cursor: not-allowed; }

.cmd-btn-primary { background: var(--cd-cyan); color: var(--accent-blue-ink); }
.cmd-btn-primary:hover:not(:disabled) { background: color-mix(in srgb, var(--cd-cyan) 85%, white); }

.cmd-btn-ghost { background: transparent; color: var(--cd-text); border-color: var(--cd-border); }
.cmd-btn-ghost:hover:not(:disabled) { border-color: var(--cd-cyan); color: var(--cd-cyan); }

.cmd-btn-on { background: var(--cd-cyan); color: var(--accent-blue-ink); }
.cmd-btn-on:hover:not(:disabled) { background: color-mix(in srgb, var(--cd-cyan) 85%, white); }

.cmd-btn-off { background: transparent; color: var(--cd-red); border-color: color-mix(in srgb, var(--cd-red) 45%, transparent); }
.cmd-btn-off:hover:not(:disabled) { background: var(--cd-red-soft); }

.cmd-link-btn {
    appearance: none;
    background: none;
    border: none;
    color: var(--cd-cyan);
    font-family: var(--cd-mono);
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
    padding: 0;
}
.cmd-link-btn:disabled { opacity: 0.5; cursor: not-allowed; }

.cmd-deck .form-select {
    background-color: var(--cd-card);
    color: var(--cd-text);
    border-color: var(--cd-border);
}
.cmd-deck .form-select:focus {
    background-color: var(--cd-card);
    color: var(--cd-text);
    border-color: var(--cd-cyan);
    box-shadow: 0 0 0 0.2rem var(--cd-cyan-soft);
}
.cmd-deck .form-label { color: var(--cd-muted); font-family: var(--cd-mono); font-size: 0.88rem; text-transform: uppercase; letter-spacing: 0.05em; }

.cmd-segment {
    display: inline-flex;
    background: var(--cd-card);
    border: 1px solid var(--cd-border);
    border-radius: var(--radius-sm);
    padding: var(--space-3xs);
    gap: 0.2rem;
}

.cmd-segment button {
    appearance: none;
    background: transparent;
    border: none;
    color: var(--cd-muted);
    font-family: var(--cd-mono);
    font-size: 0.86rem;
    font-weight: 600;
    padding: var(--space-xs) var(--space-sm);
    border-radius: var(--radius-xs);
    cursor: pointer;
    min-height: 36px;
}

.cmd-segment button.is-active { background: var(--cd-cyan-soft); color: var(--cd-cyan); }

.cmd-deck .pill {
    font-family: var(--cd-mono);
    border-radius: var(--radius-sm);
    border: 1px solid transparent;
    padding: 0.3rem 0.6rem;
    font-size: 0.85rem;
}
.cmd-deck .pill.status-good { background: var(--cd-cyan-soft); color: var(--cd-cyan); border-color: color-mix(in srgb, var(--cd-cyan) 35%, transparent); }
.cmd-deck .pill.status-warning { background: var(--cd-amber-soft); color: var(--cd-amber); border-color: color-mix(in srgb, var(--cd-amber) 35%, transparent); }
.cmd-deck .pill.status-critical { background: var(--cd-red-soft); color: var(--cd-red); border-color: color-mix(in srgb, var(--cd-red) 35%, transparent); }
.cmd-deck .pill.status-neutral { background: color-mix(in srgb, var(--cd-muted) 14%, transparent); color: var(--cd-muted); border-color: color-mix(in srgb, var(--cd-muted) 30%, transparent); }
.cmd-deck .pill-dot { background: currentColor; }
.cmd-deck a { color: var(--cd-cyan); }

.cmd-deck .activity-table { font-family: var(--cd-mono); font-size: 0.92rem; }
.cmd-deck .activity-table th {
    color: var(--cd-muted);
    border-bottom: 1px solid var(--cd-border);
    font-family: var(--cd-display);
}
.cmd-deck .activity-table td { color: var(--cd-text); border-bottom: 1px solid var(--cd-border); }
.cmd-deck .activity-table td:first-child { color: var(--cd-muted); }
.cmd-deck .activity-table tbody tr:hover { background: color-mix(in srgb, var(--cd-text) 3%, transparent); }

.cmd-deck .level-chip.level-info { background: var(--cd-cyan-soft); color: var(--cd-cyan); }
.cmd-deck .level-chip.level-warning { background: var(--cd-amber-soft); color: var(--cd-amber); }
.cmd-deck .level-chip.level-error { background: var(--cd-red-soft); color: var(--cd-red); }

@media (max-width: 640px) {
    .cmd-rings { flex-direction: column; }
    .cmd-ring-card { display: flex; align-items: center; gap: 0.9rem; text-align: left; padding: var(--space-sm) var(--space-md); }
    .cmd-ring { margin: 0; }
}

/* ==========================================================================
   Dashboard flow visual (§U item 8 of docs/UI_UX_REDESIGN_PLAN.md)
   --------------------------------------------------------------------------
   A glanceable solar → battery → home/grid read, ahead of the detailed
   telemetry stat list below it — "is my system working right now" answered
   in one row before the numbers. Deliberately re-presents figures the stat
   list already shows (Solar/Battery %/Consumption/Grid), nothing computed
   here that isn't computed there — this is presentation only, and battery
   charge/discharge direction is not claimed since the sign convention isn't
   established elsewhere in this codebase either.
   ========================================================================== */
.cmd-flow {
    display: flex;
    align-items: stretch;
    gap: 0.5rem;
    margin: 0 0.2rem 1.1rem;
    flex-wrap: wrap;
}

.cmd-flow-node {
    flex: 1 1 7rem;
    min-width: 7rem;
    background: var(--cd-card);
    border: 1px solid var(--cd-border);
    border-radius: var(--radius-lg);
    padding: var(--space-sm);
    text-align: center;
}

.cmd-flow-icon { font-size: 1.3rem; line-height: 1; }

.cmd-flow-value {
    font-family: var(--cd-display);
    font-size: 1.1rem;
    font-weight: 650;
    color: var(--cd-text);
    margin-top: var(--space-3xs);
}

.cmd-flow-value.cmd-row-absent { color: var(--cd-muted); font-weight: 500; font-size: 0.95rem; }

.cmd-flow-label {
    font-size: 0.78rem;
    color: var(--cd-muted);
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

.cmd-flow-arrow {
    display: flex;
    align-items: center;
    color: var(--cd-muted);
    font-size: 1.2rem;
    padding: 0 0.1rem;
}

@media (max-width: 640px) {
    .cmd-flow { flex-direction: column; }
    .cmd-flow-arrow { transform: rotate(90deg); align-self: center; padding: 0.2rem 0; }
}

/* ==========================================================================
   Loading skeleton
   --------------------------------------------------------------------------
   Replaces the bare "Loading…" text every page used while its first fetch is
   in flight. role="status"/aria-live carries the same announcement a screen
   reader got from the text before; the bars are decorative (aria-hidden) and
   freeze instead of animating under prefers-reduced-motion.
   ========================================================================== */
.skeleton-block {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    padding: 0.2rem 0;
}

.skeleton-line {
    height: 0.85rem;
    border-radius: var(--radius-xs);
    background: linear-gradient(90deg, var(--surface-card) 25%, var(--border-strong) 37%, var(--surface-card) 63%);
    background-size: 400% 100%;
    animation: skeleton-shimmer 1.4s ease infinite;
}

@keyframes skeleton-shimmer {
    0% { background-position: 100% 50%; }
    100% { background-position: 0 50%; }
}

@media (prefers-reduced-motion: reduce) {
    .skeleton-line { animation: none; background: var(--surface-card); }
}

/* ==========================================================================
   Empty state
   --------------------------------------------------------------------------
   One shared shape for "there is nothing here yet", replacing the several
   different one-off sentences (some plain, some italic, some inside a card)
   scattered across Devices, Dealers, Packages, Wallet and others. Text and
   any action stay whatever copy the page already used — only the frame is
   shared.
   ========================================================================== */
.empty-state {
    text-align: center;
    padding: var(--space-xl) var(--space-lg);
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius-md);
    background: color-mix(in srgb, var(--surface-page) 55%, transparent);
}

.empty-state-text {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--text-md);
}

.empty-state-text a {
    font-weight: 600;
}

/* ==========================================================================
   Page header — Shared/PageHeader.razor
   --------------------------------------------------------------------------
   Title + description + primary action, replacing each list page's own
   <h1>/<p>/<p><a>...</a></p> sequence. The action sits beside the title
   instead of below the description, closer to what a SaaS list page does.
   ========================================================================== */
.page-header-row {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-md);
    flex-wrap: wrap;
}

.page-header-text { min-width: 0; }

.page-header-title {
    margin-bottom: var(--space-3xs);
}

.page-header-description {
    margin-bottom: 0;
}

.page-header-extra {
    margin-top: var(--space-sm);
}

/* ==========================================================================
   Automation — set tabs and device rail
   --------------------------------------------------------------------------
   Level 2 of docs/UI_UX_MAKE_INSPIRED_PROPOSAL.md §5/§7/§8: a tab strip
   replaces the set dropdown (every set visible at once, the running one
   marked without opening anything), and a device rail replaces the device
   dropdown (each entry carries its own live-state pill — the same
   IDeviceStateCache reading the Devices page uses — so the picker doubles
   as an overview before anything is clicked).
   ========================================================================== */
.automation-set-tabs, .automation-device-rail {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-bottom: 0.75rem;
}

.automation-set-tab, .automation-device-rail-item {
    appearance: none;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 1rem;
    border-radius: var(--radius-md);
    border: 1px solid var(--border-strong);
    background: var(--surface-card);
    color: var(--text-secondary);
    font-weight: 600;
    min-height: 44px;
}

.automation-set-tab.is-selected, .automation-device-rail-item.is-selected {
    border-color: var(--accent-blue);
    color: var(--text-primary);
    background: color-mix(in srgb, var(--accent-blue) 10%, var(--surface-card));
}

.automation-set-tab.is-running:not(.is-selected) {
    border-color: var(--status-good);
}

.automation-set-tab-add {
    border-style: dashed;
    color: var(--text-muted);
}

/* Overflow menu for Rename/Duplicate/Delete — native <details>, matching the app's
   .collapsible-section convention, since this app ships Bootstrap's CSS only, not its JS
   bundle (no dropdown/modal behaviour to reach for). */
.automation-set-menu {
    position: relative;
    display: inline-block;
}

.automation-set-menu > summary {
    list-style: none;
    cursor: pointer;
}

.automation-set-menu > summary::-webkit-details-marker { display: none; }
.automation-set-menu > summary::marker { content: ""; }

.automation-set-menu-panel {
    position: absolute;
    z-index: 20;
    top: calc(100% + 0.25rem);
    left: 0;
    display: flex;
    flex-direction: column;
    min-width: 9rem;
    background: var(--surface-card);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-card);
    padding: var(--space-2xs);
}

.automation-set-menu-panel button {
    appearance: none;
    background: none;
    border: none;
    text-align: left;
    padding: var(--space-xs) var(--space-sm);
    border-radius: var(--radius-sm);
    color: var(--text-primary);
    min-height: 40px;
}

.automation-set-menu-panel button:hover:not(:disabled) {
    background: var(--surface-page);
}

.automation-set-menu-panel button:disabled {
    color: var(--text-muted);
    cursor: not-allowed;
}

.automation-set-menu-panel button.is-danger {
    color: var(--status-critical);
}

/* Preset picker — one card per AutomationPreset, showing its actual thresholds instead of a
   name-only <option>, plus a "Blank" card for starting from scratch. */
.automation-preset-card {
    appearance: none;
    cursor: pointer;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    text-align: left;
    min-width: 10rem;
    padding: var(--space-xs) var(--space-sm);
    border-radius: var(--radius-md);
    border: 1px solid var(--border-strong);
    background: var(--surface-page);
    color: var(--text-muted);
    font-size: var(--text-2xs);
}

.automation-preset-card strong {
    color: var(--text-primary);
    font-size: var(--text-sm);
}

.automation-preset-card.is-selected {
    border-color: var(--accent-blue);
    background: color-mix(in srgb, var(--accent-blue) 8%, var(--surface-page));
}

/* ==========================================================================
   Theme toggle icon
   --------------------------------------------------------------------------
   Which of the two icons in NavMenu's button is visible is decided entirely by CSS, keyed
   off the data-theme attribute App.razor's inline script sets on <html> — no JS runs to
   swap them, which is what keeps the right icon showing across Blazor's enhanced navigation
   without anything needing to re-run per page. Icon shown = the theme a click switches TO
   (sun visible while dark is active, meaning "tap for light"), matching the toggle's own
   aria-label.
   ========================================================================== */
.icon-sun { display: none; }
.icon-moon { display: block; }

@media (prefers-color-scheme: dark) {
    html:not([data-theme="light"]) .icon-sun { display: block; }
    html:not([data-theme="light"]) .icon-moon { display: none; }
}

html[data-theme="dark"] .icon-sun { display: block; }
html[data-theme="dark"] .icon-moon { display: none; }

html[data-theme="light"] .icon-sun { display: none; }
html[data-theme="light"] .icon-moon { display: block; }

/* ==========================================================================
   Narrow-field width utilities
   --------------------------------------------------------------------------
   Replaces repeated inline style="max-width: Nrem" on short numeric inputs
   (thresholds, priorities, time-range fields) with a shared, named scale.
   ========================================================================== */
.w-xs { max-width: 5rem; }
.w-sm { max-width: 6rem; }
.w-md { max-width: 7rem; }
.w-lg { max-width: 9rem; }

/* Replaces repeated inline style="margin-top:2rem" ahead of a section heading that follows a
   table/card block above it — ties the gap to the token scale instead of a literal. */
.mt-section { margin-top: var(--space-xl); }
