/* =====================================================================
 * ETL Datalogger - visual language matched to embeddedc.co.uk:
 *   near-black surfaces, ETL green accent (#229D47), cyan highlights,
 *   DM Sans body + JetBrains Mono for technical labels, generous spacing.
 * ===================================================================== */

:root {
    /* surfaces */
    --bg-primary:    #0a0c10;
    --bg-secondary:  #0e1117;
    --bg-card:       #12151c;
    --bg-card-hover: #181c25;
    --bg-surface:    #1a1e28;
    /* Alias used by storage / programs / dlog rows. Without this they
     * fall through to transparent because no `--bg` was defined. */
    --bg:            var(--bg-primary);

    /* text */
    --text-primary:   #e8eaf0;
    --text-secondary: #9da3b0;
    --text-muted:     #6b7280;

    /* accents */
    --accent:         #229D47;            /* ETL brand green */
    --accent-dim:     rgba(34,157,71,.15);
    --accent-glow:    rgba(34,157,71,.30);
    --accent-2:       #00d4ff;            /* cyan (links / active) */
    --accent-2-dim:   rgba(0,212,255,.12);
    --good:           #34d399;
    --bad:            #6b7280;
    --danger:         #f87171;

    /* type */
    --font-sans: 'DM Sans', 'Segoe UI', system-ui, -apple-system, sans-serif;
    --font-mono: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace;

    /* scale */
    --space-xs:  0.5rem;
    --space-sm:  0.75rem;
    --space-md:  1rem;
    --space-lg:  1.5rem;
    --space-xl:  2rem;
    --space-2xl: 3rem;
    --space-3xl: 4rem;

    --radius-sm: 6px;
    --radius-md: 10px;
    --radius-lg: 16px;

    --max-width:  1280px;
    --nav-height: 64px;

    /* Translucent borders / hovers - tokenised so the light theme can
     * flip them to dark-on-white instead of light-on-dark. */
    --bd-soft:     rgba(255,255,255,.06);
    --bd-soft2:    rgba(255,255,255,.04);
    --bd-mid:      rgba(255,255,255,.08);
    --bd-strong:   rgba(255,255,255,.10);
    --bd-strong2:  rgba(255,255,255,.12);
    --hover-soft:  rgba(255,255,255,.05);
    --hover-soft2: rgba(255,255,255,.03);
    --hover-soft3: rgba(255,255,255,.02);

    --header-bg:   rgba(10, 12, 16, 0.85);
    --noise-opacity: 0.03;
}

/* Tiny adjustments only the light theme needs (terminal background
 * stays dark - xterm.js looks weird on white; everything else
 * follows the variables). */
:root[data-theme="light"] .btn.primary { color: white; }
:root[data-theme="light"] .btn.primary:hover { color: white; }
:root[data-theme="light"] .btn.primary { color: white; }
:root[data-theme="light"] .modal::backdrop { background: rgba(0,0,0,.35); }
:root[data-theme="light"] .empty-state    { border-color: rgba(0,0,0,.12); }
:root[data-theme="light"] #terminal,
:root[data-theme="light"] .chart-card .chart-wrap canvas {
    /* keep terminal/chart on dark bg for readability */
    background: #0d1117;
}

/* ============================================================== *
 *  Light theme - applied via data-theme="light" on <html>.
 *  Same accent green (a touch darker for AAA contrast on white),
 *  surfaces flipped, borders become dark-translucent.
 * ============================================================== */
:root[data-theme="light"] {
    --bg-primary:    #f5f6f8;
    --bg-secondary:  #ebedf0;
    --bg-card:       #ffffff;
    --bg-card-hover: #f0f1f4;
    --bg-surface:    #e8eaed;
    --bg:            var(--bg-primary);

    --text-primary:   #1a1d24;
    --text-secondary: #4b5260;
    --text-muted:     #7a8190;

    --accent:         #1a8038;
    --accent-dim:     rgba(26,128,56,.10);
    --accent-glow:    rgba(26,128,56,.25);
    --accent-2:       #0094b3;
    --accent-2-dim:   rgba(0,148,179,.08);
    --good:           #1a8038;
    --bad:            #6b7280;
    --danger:         #c63030;

    --bd-soft:     rgba(0,0,0,.07);
    --bd-soft2:    rgba(0,0,0,.05);
    --bd-mid:      rgba(0,0,0,.10);
    --bd-strong:   rgba(0,0,0,.13);
    --bd-strong2:  rgba(0,0,0,.16);
    --hover-soft:  rgba(0,0,0,.04);
    --hover-soft2: rgba(0,0,0,.03);
    --hover-soft3: rgba(0,0,0,.02);

    --header-bg:   rgba(245, 246, 248, 0.85);
    --noise-opacity: 0.015;
}

/* ---- Skip-to-main link (WCAG 2.4.1) ---------------------------- *
 * Visually hidden until focused. Keyboard users hitting Tab on a
 * fresh page see this first; activating it jumps focus past the
 * site-header + nav to the page's main content. */
.skip-link {
    position: absolute;
    left: -10000px;
    top: 0;
    z-index: 10002;
    padding: .5rem .9rem;
    background: var(--bg-card);
    color: var(--text-primary);
    border: 1px solid var(--accent);
    border-radius: var(--radius-sm);
    text-decoration: none;
    font-family: var(--font-mono);
    font-size: .8rem;
}
.skip-link:focus {
    left: var(--space-md);
    top: var(--space-md);
}
#main-content:focus { outline: none; }

/* ---- reduced motion ------------------------------------------- *
 * WCAG 2.3.3: respect users with vestibular sensitivities. We zero
 * (rather than remove) durations so cause/effect transitions still
 * happen instantly without visual movement, and pause our looping
 * animations (the indeterminate progress shimmer in particular).
 * Chart.js per-instance animation:false is set in the chart configs;
 * this rule is the CSS-side companion. */
@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;
    }
    .log-viewer-prog:not([value]) {
        animation: none !important;
        background: var(--accent) !important;
    }
}

/* ---- Admin audit log feed ------------------------------------- */
.admin-audit-list {
    list-style: none;
    margin: var(--space-sm) 0 0;
    padding: 0;
    display: flex; flex-direction: column;
    gap: .25rem;
}
.admin-audit-row {
    display: grid;
    grid-template-columns: 6rem 10rem 1fr;
    gap: var(--space-sm);
    padding: .25rem .5rem;
    border-bottom: 1px solid var(--bd-soft);
    font-size: .8rem;
}
.admin-audit-row:last-child { border-bottom: 0; }
.admin-audit-kind { color: var(--accent); font-size: .7rem; }
.admin-audit-msg  { color: var(--text-primary); }
@media (max-width: 640px) {
    .admin-audit-row { grid-template-columns: 1fr; gap: .15rem; }
}

/* ---- Firmware library: retired-row treatment ------------------- *
 * Retired images stay in the table but are visually de-emphasised
 * so an operator scanning the list doesn't waste time on them. The
 * status pill ("retired") is the authoritative signal; the dim
 * background just reinforces it. */
.fw-grid tr.fw-retired td { opacity: .55; }
.fw-grid tr.fw-retired td .status-pill { opacity: 1; }
.fw-actions {
    display: flex; gap: var(--space-sm); justify-content: flex-end;
    white-space: nowrap;
}

/* ---- "Recent devices" quick-jump strip ------------------------- *
 * Sits above the fleet grid on /dashboard. Pulled from localStorage
 * (written by device.html on every visit). Lets the user thumb back
 * into a device they were just on without scanning the grid. */
.recent-devices {
    display: flex; align-items: center; gap: var(--space-md);
    margin-bottom: var(--space-md);
}
.recent-devices-label {
    font-size: .65rem;
    text-transform: uppercase;
    letter-spacing: .05em;
    flex: 0 0 auto;
}
.recent-devices-chips {
    display: flex; flex-wrap: wrap; gap: .35rem;
    flex: 1 1 auto;
}
.recent-chip {
    display: inline-flex; align-items: center; gap: .5rem;
    padding: .3rem .65rem;
    background: var(--bg-card);
    border: 1px solid var(--bd-soft);
    border-radius: 999px;
    color: var(--text-primary);
    text-decoration: none;
    font-size: .8rem;
    transition: border-color .15s, background .15s;
}
.recent-chip:hover {
    border-color: var(--accent);
    background: var(--bg-card-hover);
}
.recent-chip-id {
    font-size: .7rem;
    opacity: .8;
}

/* ---- Cross-fleet alarms page ----------------------------------- *
 * Sortable table with one-click dismiss per row. Re-uses .status-pill
 * for severity colours so it matches the per-device status surface. */
.alarms-wrap {
    background: var(--bg-card);
    border: 1px solid var(--bd-soft);
    border-radius: var(--radius-md);
    overflow: hidden;
}
.alarms-table {
    width: 100%;
    border-collapse: collapse;
    font-size: .85rem;
}
.alarms-table thead th {
    text-align: left;
    padding: .55rem .75rem;
    background: var(--bg-secondary);
    border-bottom: 1px solid var(--bd-soft);
    color: var(--text-secondary);
    font-family: var(--font-mono);
    text-transform: uppercase;
    font-size: .65rem;
    letter-spacing: .04em;
    cursor: pointer;
    user-select: none;
    white-space: nowrap;
}
.alarms-table thead th:hover { color: var(--accent); }
.alarms-table tbody td {
    padding: .55rem .75rem;
    border-bottom: 1px solid var(--bd-soft);
    vertical-align: middle;
}
.alarms-table tbody tr:last-child td { border-bottom: 0; }
.alarms-table tbody tr:hover td { background: var(--bg-secondary); }
.alarm-row.is-dismissed td { opacity: .55; }
.alarm-dev-link {
    color: var(--text-primary);
    text-decoration: none;
}
.alarm-dev-link:hover { color: var(--accent-2); text-decoration: underline; }
.alarms-empty { padding: var(--space-2xl) var(--space-md); }

/* ---- Command palette (Ctrl/Cmd + K) ---------------------------- *
 * Fullscreen overlay + centered card. Filter as you type, arrow keys
 * to walk results, Enter to navigate. Hidden by default; loaded on
 * every page via palette.js + base.html.                            */
.palette-host {
    position: fixed; inset: 0;
    z-index: 10001;
    display: flex; justify-content: center; align-items: flex-start;
    padding-top: 12vh;
}
.palette-host[hidden] { display: none !important; }
.palette-backdrop {
    position: absolute; inset: 0;
    background: rgba(0,0,0,.55);
    backdrop-filter: blur(4px);
}
.palette {
    position: relative;
    width: min(640px, 92vw);
    max-height: 70vh;
    display: flex; flex-direction: column;
    background: var(--bg-card);
    border: 1px solid var(--bd-strong);
    border-radius: var(--radius-md);
    box-shadow: 0 24px 64px rgba(0,0,0,.6);
    overflow: hidden;
}
.palette-head {
    display: flex; align-items: center; gap: var(--space-sm);
    padding: .75rem var(--space-md);
    border-bottom: 1px solid var(--bd-soft);
}
.palette-icon {
    font-family: var(--font-mono);
    font-size: .7rem;
    padding: .15rem .4rem;
    border: 1px solid var(--bd-strong);
    border-radius: 4px;
    color: var(--text-muted);
    flex: 0 0 auto;
}
.palette-head input {
    flex: 1 1 auto;
    background: transparent;
    border: 0;
    outline: none;
    color: var(--text-primary);
    font-family: inherit;
    font-size: 1rem;
    padding: .25rem 0;
}
.palette-close {
    background: transparent;
    border: 1px solid var(--bd-soft);
    color: var(--text-muted);
    border-radius: 4px;
    padding: .15rem .4rem;
    cursor: pointer;
    font-family: var(--font-mono);
    font-size: .7rem;
}
.palette-close:hover { color: var(--text-primary); border-color: var(--bd-mid); }

.palette-list {
    list-style: none;
    margin: 0;
    padding: .25rem;
    overflow-y: auto;
    flex: 1 1 auto;
}
.palette-empty {
    padding: var(--space-md);
    text-align: center;
    font-size: .85rem;
}
.palette-row {
    display: flex; align-items: center; gap: var(--space-sm);
    padding: .5rem .65rem;
    border-radius: var(--radius-sm);
    cursor: pointer;
}
.palette-row.is-active {
    background: var(--bg-secondary);
    outline: 1px solid var(--accent);
}
.palette-row-glyph {
    flex: 0 0 1.6em;
    width: 1.6em; height: 1.6em;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 4px;
    background: var(--bg-secondary);
    color: var(--text-muted);
    font-family: var(--font-mono);
    font-size: .8rem;
}
.palette-row-glyph.palette-page   { color: var(--accent); }
.palette-row-glyph.palette-device { color: var(--accent-2); }
.palette-row-glyph.palette-alarm  { color: var(--danger); }
.palette-row-body {
    display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto;
    line-height: 1.3;
}
.palette-row-label {
    color: var(--text-primary);
    font-size: .9rem;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.palette-row-sub {
    font-size: .7rem;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.palette-row-type {
    font-size: .65rem;
    opacity: .7;
    flex: 0 0 auto;
}

.palette-foot {
    display: flex; gap: var(--space-md);
    padding: .4rem var(--space-md);
    border-top: 1px solid var(--bd-soft);
    font-size: .65rem;
}
.palette-foot kbd {
    font-family: var(--font-mono);
    font-size: .65rem;
    background: var(--bg-secondary);
    border: 1px solid var(--bd-soft);
    border-bottom-width: 2px;
    border-radius: 3px;
    padding: 0 .25rem;
    margin-right: .25rem;
}

/* ---- Toast notifications --------------------------------------- *
 * Stacked fixed bottom-right. Each toast slides in from the right and
 * fades out. The host is created lazily by toast.js. */
.toast-host {
    position: fixed;
    bottom: var(--space-md, 1rem);
    right: var(--space-md, 1rem);
    display: flex; flex-direction: column; gap: .5rem;
    z-index: 10000;
    pointer-events: none;
    max-width: min(420px, calc(100vw - 2rem));
}
.toast {
    pointer-events: auto;
    background: var(--bg-card, #12151c);
    color: var(--text-primary, #e8eaf0);
    border: 1px solid var(--bd-strong, rgba(255,255,255,.10));
    border-left: 3px solid var(--text-muted, #6b7280);
    border-radius: var(--radius-sm, 6px);
    padding: .65rem .85rem;
    font-size: .85rem;
    line-height: 1.35;
    box-shadow: 0 12px 32px rgba(0,0,0,.45);
    cursor: pointer;
    opacity: 0;
    transform: translateX(20px);
    transition: opacity .2s ease, transform .25s ease;
}
.toast.show { opacity: 1; transform: translateX(0); }
.toast-success { border-left-color: var(--accent, #229D47); }
.toast-error   { border-left-color: var(--danger, #c63030); }
.toast-warn    { border-left-color: #d97706; }
/* Lighter shadow on light theme - the dark-theme 45% black drops a
 * grey haze that looks dirty on a near-white background. */
:root[data-theme="light"] .toast {
    box-shadow: 0 8px 24px rgba(0,0,0,.12);
}

/* promptModal() uses the existing .modal styling; this just sizes
 * the input so it's not a tiny default-width text box. */
.prompt-modal { min-width: 320px; max-width: 480px; }
.prompt-modal .prompt-input {
    width: 100%;
    padding: .55rem .7rem;
    font-family: var(--font-mono);
    font-size: .9rem;
    background: var(--bg-secondary);
    color: var(--text-primary);
    border: 1px solid var(--bd-strong);
    border-radius: var(--radius-sm);
}
.prompt-modal .prompt-err { font-size: .8rem; }

/* ---- reset / base ---------------------------------------------- */
* { box-sizing: border-box; }
html, body { height: 100%; }

/* Prevent any tab-panel content from forcing the page wider than the
 * viewport - which previously made the sticky `.site-header` (logo +
 * primary nav) track the new width and visibly grow when switching
 * to tabs whose toolbars have lots of inline-flex children (Programs,
 * Live Stream). `overflow-x: clip` is more robust than `hidden` here:
 * it doesn't establish a scroll container, so position:sticky on
 * descendants (the site header itself) keeps working. */
html, body { max-width: 100vw; overflow-x: clip; }
body {
    margin: 0;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    font-family: var(--font-sans);
    font-size: 15px;
    line-height: 1.55;
    color: var(--text-primary);
    background-color: var(--bg-primary);
    background-repeat: repeat;
    background-size: 256px 256px;
    /* very subtle noise; opacity flips per theme */
    background-image:
        url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.04'/%3E%3C/svg%3E");
    transition: background-color .2s, color .2s;
    -webkit-font-smoothing: antialiased;
}
a { color: var(--accent-2); text-decoration: none; }
a:hover { color: var(--accent); }
code, .mono {
    font-family: var(--font-mono);
    font-size: .875em;
}
h1, h2, h3 { font-weight: 600; letter-spacing: -0.01em; }

.muted { color: var(--text-muted); }
.error {
    color: var(--danger);
    font-size: .9rem;
    margin: var(--space-sm) 0;
}
.spacer { flex: 1; }

/* Mono "section label" used as eyebrow above headings, inspired
 * directly by embeddedc's // SECTION_NAME style. */
.eyebrow {
    font-family: var(--font-mono);
    font-size: .75rem;
    font-weight: 500;
    color: var(--accent);
    text-transform: uppercase;
    letter-spacing: .05em;
    margin-bottom: var(--space-xs);
}

/* Header row that pairs the section eyebrow with a usage/quota pill
 * (used by the Programs and Functions sections in the Programs tab). */
.program-section-head {
    display: flex;
    align-items: baseline;
    gap: var(--space-sm);
    margin-bottom: var(--space-xs);
}
.program-section-head .eyebrow {
    margin-bottom: 0;
}

/* ---- Header ---------------------------------------------------- */
.site-header {
    position: sticky; top: 0; z-index: 100;
    display: flex; align-items: center;
    height: var(--nav-height);
    padding: 0 var(--space-xl);
    background-color: var(--header-bg);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border-bottom: 1px solid var(--bd-soft);
}
.brand {
    display: flex; align-items: center; gap: var(--space-sm);
    font-family: var(--font-mono);
    font-weight: 700;
    color: var(--text-primary);
    letter-spacing: -.02em;
}
.brand:hover { color: var(--text-primary); }
.brand-mark {
    display: inline-flex; align-items: center; justify-content: center;
    width: 32px; height: 32px;
    border: 1.5px solid var(--accent);
    border-radius: var(--radius-sm);
    color: var(--accent);
    font-family: var(--font-mono);
    font-size: .7rem;
    font-weight: 800;
}
.brand-name      { font-size: .95rem; }
.brand-name span { color: var(--accent); }   /* "Portal" */
.brand-sub {
    font-family: var(--font-mono);
    color: var(--text-muted);
    font-size: .75rem;
    margin-left: var(--space-sm);
    padding-left: var(--space-sm);
    border-left: 1px solid var(--bd-mid);
}
.primary-nav {
    margin-left: var(--space-2xl);
    display: flex; align-items: center; gap: var(--space-lg);
    font-family: var(--font-mono);
    font-size: .8rem;
}
.primary-nav a {
    color: var(--text-secondary);
    padding: .35rem .25rem;
    border-bottom: 2px solid transparent;
}
.primary-nav a:hover {
    color: var(--text-primary);
    border-bottom-color: var(--accent);
}

.user-bar {
    margin-left: auto;
    display: flex; align-items: center; gap: var(--space-md);
    font-size: .85rem;
}
.user-bar .email {
    font-family: var(--font-mono);
    color: var(--text-secondary);
    font-size: .8rem;
}

/* "Set up new device" call-to-action - lives in the right-hand bar
 * so it's separated from the contextual nav and reads as an action,
 * not just another section. */
.setup-cta {
    display: inline-flex; align-items: center; gap: .4rem;
    padding: .35rem .75rem;
    border: 1px solid var(--accent);
    border-radius: 999px;
    color: var(--accent);
    background: var(--accent-dim);
    font-family: var(--font-mono);
    font-size: .75rem;
    font-weight: 500;
    letter-spacing: .02em;
    text-transform: uppercase;
    transition: background .15s, color .15s, transform .12s;
}
.setup-cta:hover {
    background: var(--accent);
    color: #04140a;
    text-decoration: none;
    transform: translateY(-1px);
}
:root[data-theme="light"] .setup-cta:hover { color: white; }

@media (max-width: 720px) {
    .setup-cta span { display: none; }      /* icon-only on narrow screens */
    .setup-cta { padding: .35rem .5rem; }
}

/* Theme toggle: shows sun in dark mode, moon in light mode. */
.theme-toggle .ti-sun  { display: inline; }
.theme-toggle .ti-moon { display: none;   }
:root[data-theme="light"] .theme-toggle .ti-sun  { display: none;   }
:root[data-theme="light"] .theme-toggle .ti-moon { display: inline; }
.inline { display: inline; }

/* ---- main wrapper --------------------------------------------- */
main {
    flex: 1 0 auto;            /* pushes footer to the viewport bottom */
    width: 100%;
    max-width: var(--max-width);
    margin: 0 auto;
    padding: var(--space-2xl) var(--space-xl);
}

.site-footer {
    flex-shrink: 0;
    display: flex; align-items: center; gap: var(--space-md);
    width: 100%;
    max-width: var(--max-width);
    margin: var(--space-2xl) auto 0;
    padding: var(--space-md) var(--space-xl);
    border-top: 1px solid var(--bd-soft);
    font-family: var(--font-mono);
    font-size: .75rem;
    color: var(--text-muted);
}
.site-footer a { color: var(--text-muted); }
.site-footer a:hover { color: var(--accent); }

/* ---- Buttons / forms ------------------------------------------ */
.btn {
    display: inline-flex; align-items: center; justify-content: center;
    gap: .4rem;
    background: transparent;
    color: var(--text-primary);
    border: 1px solid var(--bd-strong2);
    border-radius: var(--radius-sm);
    padding: .55rem 1rem;
    font-size: .85rem;
    font-family: inherit;
    font-weight: 500;
    cursor: pointer;
    transition: border-color .15s, background .15s, color .15s;
}
.btn:hover {
    border-color: var(--accent);
    color: var(--accent);
    background: var(--accent-dim);
}
.btn.primary {
    background: var(--accent);
    border-color: var(--accent);
    color: #04140a;
    font-weight: 600;
}
.btn.primary:hover {
    background: #28b352;
    border-color: #28b352;
    color: #04140a;
}
.btn.danger:hover {
    border-color: var(--danger);
    color: var(--danger);
    background: rgba(248,113,113,.08);
}

.link-btn {
    background: none; border: none;
    /* Tap target: ~32px tall (.4rem * 2 + line-height ~1.4em).
     * Below 44px iOS minimum but acceptable for inline secondary
     * actions (download / view) packed inside a row. The 8px row gap
     * handles the spacing-between-targets requirement. */
    padding: .4rem .55rem;
    margin: -.4rem 0;          /* keep visual rhythm tight in inline rows */
    border-radius: var(--radius-sm);
    color: var(--accent-2);
    cursor: pointer;
    font-size: .85rem;
    font-family: inherit;
    transition: background .15s, color .15s;
}
.link-btn:hover {
    text-decoration: underline;
    background: var(--accent-2-dim);
}
.link-btn.danger { color: var(--danger); }
.link-btn.danger:hover { background: rgba(248,113,113,.08); }

/* ---- Focus rings ----------------------------------------------- *
 * `:focus-visible` only paints when the user is navigating by
 * keyboard - mouse clicks don't trigger it, so the visual "glow"
 * pollution mouse users used to see (the original reason focus rings
 * get suppressed) doesn't happen. Keyboard users get a clearly
 * visible 2px accent ring with 2px offset on every interactive
 * element, satisfying WCAG 2.4.7. */
.btn:focus-visible,
.link-btn:focus-visible,
.snav:focus-visible,
.tab:focus-visible,
.icon-btn:focus-visible,
.dl-chip:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}
/* Inputs/dropdowns already have a focus state via border-color, but
 * supplement with the same outline so the cue is consistent across
 * all interactive elements. */
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

.dropdown {
    background: var(--bg-secondary);
    color: var(--text-primary);
    border: 1px solid var(--bd-strong);
    border-radius: var(--radius-sm);
    padding: .35rem .6rem;
    font-size: .8rem;
    font-family: var(--font-mono);
    cursor: pointer;
}
.dropdown:hover, .dropdown:focus {
    outline: none;
    border-color: var(--accent);
}

form.stacked { display: flex; flex-direction: column; gap: var(--space-md); }
form.stacked label {
    display: flex; flex-direction: column; gap: .3rem;
    font-family: var(--font-mono);
    font-size: .75rem;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: .04em;
}
form.stacked input {
    background: var(--bg-secondary);
    color: var(--text-primary);
    border: 1px solid var(--bd-strong);
    border-radius: var(--radius-sm);
    padding: .65rem .8rem;
    font-size: .95rem;
    font-family: var(--font-sans);
    transition: border-color .15s, box-shadow .15s;
}
form.stacked input:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-dim);
}

/* ---- Auth card ------------------------------------------------ */
.auth-card {
    max-width: 400px;
    margin: var(--space-3xl) auto;
    background: var(--bg-card);
    border: 1px solid var(--bd-soft);
    border-radius: var(--radius-lg);
    padding: var(--space-2xl);
    box-shadow: 0 8px 32px rgba(0,0,0,.4);
}
.auth-card h2 {
    margin: 0 0 var(--space-lg);
    font-size: 1.5rem;
}
.auth-card p { color: var(--text-secondary); font-size: .9rem; }
.auth-card .btn { width: 100%; padding: .75rem; }

/* ---- Page header (dashboard, device) -------------------------- */
.page-head {
    display: flex; justify-content: space-between; align-items: flex-end;
    flex-wrap: wrap; gap: var(--space-md);
    margin-bottom: var(--space-xl);
    padding-bottom: var(--space-md);
    border-bottom: 1px solid var(--bd-soft);
}
.page-head h2 {
    margin: 0;
    font-size: 1.75rem;
    letter-spacing: -.02em;
}

.dash-actions { display: flex; gap: var(--space-sm); align-items: center; flex-wrap: wrap; }

/* ---- Dashboard - device grid --------------------------------- */
.device-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
    gap: var(--space-lg);
}

.device-card {
    display: block;
    position: relative;
    background: var(--bg-card);
    border: 1px solid var(--bd-soft);
    border-radius: var(--radius-md);
    padding: var(--space-lg);
    color: var(--text-primary);
    transition: background .15s, border-color .15s, transform .15s;
    overflow: hidden;
}
.device-card::before {
    content: "";
    position: absolute; inset: 0;
    border-radius: inherit;
    background: linear-gradient(135deg, transparent 60%, var(--accent-dim) 140%);
    opacity: 0;
    transition: opacity .2s;
    pointer-events: none;
}
.device-card:hover {
    background: var(--bg-card-hover);
    border-color: var(--accent-glow);
    text-decoration: none;
    transform: translateY(-2px);
}
.device-card:hover::before { opacity: 1; }

.device-card h3 {
    margin: 0;
    font-size: 1.1rem;
    color: var(--text-primary);
}
.device-card .device-id {
    font-family: var(--font-mono);
    font-size: .8rem;
    color: var(--text-muted);
    margin-top: .15rem;
}
.device-card .device-fw {
    display: flex; align-items: center; gap: .35rem;
    margin-top: var(--space-md);
    font-size: .75rem;
}
.device-card .fw-tag {
    font-family: var(--font-mono);
    font-size: .65rem;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--accent);
    border: 1px solid var(--accent-glow);
    background: var(--accent-dim);
    padding: .05rem .35rem;
    border-radius: 3px;
}

.device-card .card-led-row {
    display: flex; align-items: center; justify-content: space-between;
    margin-top: .55rem;
    padding-top: .55rem;
    border-top: 1px solid var(--bd-soft2);
    font-size: .75rem;
}

.device-card .device-meta {
    margin-top: var(--space-sm);
    padding-top: var(--space-md);
    border-top: 1px solid var(--hover-soft);
    display: flex; align-items: center; justify-content: space-between;
    font-family: var(--font-mono);
    font-size: .75rem;
    color: var(--text-muted);
}
.device-card .head-row {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: var(--space-md);
}
.device-card .head-row-right {
    display: inline-flex; align-items: center; gap: .4rem;
    flex: 0 0 auto;
}
/* Per-tile "is this device on the auth-token path?" indicator.
 * Green padlock when secure, muted unlock when still in TOFU window.
 * Title attribute carries the longer explanation. */
.device-secure {
    display: inline-flex; align-items: center; justify-content: center;
    width: 22px; height: 22px;
    border-radius: 50%;
    border: 1px solid currentColor;
    line-height: 1;
}
.device-secure.is-secure { color: var(--accent); background: var(--accent-dim); }
.device-secure.is-open   { color: var(--text-muted); opacity: .75; }

.empty-state {
    grid-column: 1 / -1;
    text-align: center;
    padding: var(--space-3xl);
    background: var(--bg-card);
    border: 1px dashed var(--bd-strong);
    border-radius: var(--radius-md);
    color: var(--text-secondary);
}

/* status pill (used both on cards and device-page header) */
.status {
    display: inline-flex; align-items: center; gap: .4rem;
    font-family: var(--font-mono);
    font-size: .7rem;
    text-transform: uppercase;
    letter-spacing: .05em;
}
.status .dot {
    width: 8px; height: 8px; border-radius: 50%;
    background: var(--bad);
    flex-shrink: 0;
}
.status.online  { color: var(--good); }
.status.online  .dot { background: var(--good); box-shadow: 0 0 6px var(--good); }
.status.offline { color: var(--text-muted); }

/* ---- Modal (claim dialog) ------------------------------------ */
.modal {
    border: 1px solid var(--bd-mid);
    border-radius: var(--radius-lg);
    background: var(--bg-card);
    color: var(--text-primary);
    padding: var(--space-2xl);
    min-width: 360px; max-width: 92vw;
    box-shadow: 0 20px 60px rgba(0,0,0,.6);
}
.modal::backdrop { background: rgba(0,0,0,.65); backdrop-filter: blur(4px); }
.modal h3 { margin: 0 0 var(--space-sm); font-size: 1.25rem; }
.modal p  { color: var(--text-secondary); font-size: .9rem; margin-top: 0; }
.modal .actions {
    display: flex; gap: var(--space-sm); justify-content: flex-end;
    margin-top: var(--space-md);
}

/* ---- Device page (sidebar layout) ---------------------------- */
.back-link {
    font-family: var(--font-mono);
    font-size: .8rem;
    color: var(--text-muted);
}
.back-link:hover { color: var(--accent); }
.top-back { display: inline-block; margin-bottom: var(--space-md); }

.device-layout {
    --sidebar-w: 232px;
    display: grid;
    /* `minmax(0, 1fr)` is the fix for the chart-tab-makes-header-grow
     * bug. By default, a `1fr` grid track has `min-width: auto`, which
     * means it will grow to fit the intrinsic min-content of its
     * children. Chart.js canvases and long file-path rows in the
     * Storage tab have wide intrinsic min-content, so the column grew
     * past the viewport, the body widened, and the sticky site-header
     * tracked the new width. `minmax(0, 1fr)` lets the track shrink
     * to 0 if needed and keeps the layout pinned to the viewport. */
    grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
    gap: var(--space-md);
    align-items: start;
    transition: grid-template-columns .2s ease;
}
.device-layout.collapsed { --sidebar-w: 60px; }

/* sidebar */
.device-sidebar {
    position: sticky;
    top: calc(var(--nav-height) + var(--space-md));
    background: var(--bg-card);
    border: 1px solid var(--bd-soft);
    border-radius: var(--radius-md);
    display: flex; flex-direction: column;
    overflow: hidden;
    max-height: calc(100vh - var(--nav-height) - var(--space-md) * 2);
}
.sidebar-head {
    display: flex; justify-content: space-between; align-items: center;
    padding: .55rem .75rem;
    border-bottom: 1px solid var(--bd-soft2);
    min-height: 38px;
}
.sidebar-title {
    font-size: .7rem;
    color: var(--text-muted);
    text-transform: lowercase;
    letter-spacing: .05em;
}
.collapsed .sidebar-title { display: none; }

.icon-btn {
    background: none; border: none;
    color: var(--text-muted);
    /* Min 40x40 touch target. Icon stays visually small via the inner
     * SVG dimensions; the button just expands its hit area. */
    min-width: 40px; min-height: 40px;
    padding: .5rem;
    border-radius: 4px;
    cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center;
    transition: color .12s, background .12s, transform .2s;
}
.icon-btn:hover { color: var(--text-primary); background: var(--hover-soft); }
.collapsed .sidebar-head .icon-btn { transform: rotate(180deg); }

.sidebar-nav {
    display: flex; flex-direction: column; gap: 2px;
    padding: .5rem;
    flex: 1;
}
.snav {
    background: none; border: none; cursor: pointer;
    color: var(--text-secondary);
    padding: .55rem .65rem;
    border-radius: var(--radius-sm);
    text-align: left;
    font-size: .9rem;
    font-family: inherit;
    display: flex; align-items: center; gap: .65rem;
    transition: background .12s, color .12s;
    white-space: nowrap;
}
.snav:hover:not([disabled]) {
    color: var(--text-primary);
    background: var(--bd-soft2);
}
.snav.active {
    color: var(--accent);
    background: var(--accent-dim);
}
.snav[disabled] { opacity: .35; cursor: not-allowed; }
.snav-icon {
    width: 18px; height: 18px;
    flex-shrink: 0;
    display: inline-flex; align-items: center; justify-content: center;
    color: inherit;
}
.collapsed .snav { justify-content: center; padding: .55rem; }
/* Hide the label visually when sidebar is collapsed, but keep it in
 * the accessibility tree so screen readers still announce each tab.
 * `display: none` would remove it from AT entirely. */
.collapsed .snav-label {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* Group separator inside the sidebar nav. The button gap (2px above)
 * gives groups some breathing room; the rule reinforces it visually. */
.snav-divider {
    border: 0;
    border-top: 1px solid var(--bd-soft);
    margin: .4rem .35rem;
}
.collapsed .snav-divider { margin: .4rem .15rem; }

.sidebar-meta {
    border-top: 1px solid var(--bd-soft2);
    padding: .8rem .75rem;
    font-size: .8rem;
    color: var(--text-muted);
    overflow-y: auto;
}
.collapsed .sidebar-meta { display: none; }
.sidebar-meta .meta-row { margin: .15rem 0; word-break: break-all; }
.sidebar-actions {
    margin-top: var(--space-md);
    padding-top: var(--space-md);
    border-top: 1px solid var(--bd-soft2);
    display: flex; flex-direction: column; gap: .4rem;
    align-items: flex-start;
}

/* main area */
.device-main { min-width: 0; }     /* prevents grid item from overflowing */
.device-topbar {
    display: flex; align-items: center; gap: var(--space-md);
    padding: .65rem .9rem;
    background: var(--bg-card);
    border: 1px solid var(--bd-soft);
    border-radius: var(--radius-md);
    margin-bottom: var(--space-md);
    flex-wrap: wrap;
}
.device-topbar h2 { margin: 0; font-size: 1.1rem; }
.device-content { min-width: 0; }

/* tab class still applies to .snav buttons; .tab as standalone retained
 * for any future horizontal usage but no longer rendered on this page. */
.tab-panel { display: none; }
.tab-panel.active { display: block; }

/* Horizontal tab strip used on /admin (different layout from device page). */
nav.tabs.admin-tabs {
    display: flex;
    background: var(--bg-card);
    border: 1px solid var(--bd-soft);
    border-radius: var(--radius-md);
    padding: .25rem;
    margin-bottom: var(--space-lg);
}
.admin-tabs .tab {
    background: none; border: none;
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    padding: .55rem 1.1rem;
    cursor: pointer;
    font-size: .85rem;
    font-family: inherit;
    font-weight: 500;
    transition: color .12s, background .12s;
}
.admin-tabs .tab:hover  { color: var(--text-primary); background: var(--hover-soft2); }
.admin-tabs .tab.active { color: var(--accent); background: var(--accent-dim); }

/* Admin nav link tinted so it's visually distinct in the top bar. */
.primary-nav a.admin-link { color: var(--accent); }
.primary-nav a.admin-link:hover { color: #28b352; }

/* Role / state pills in admin tables. */
.role-pill {
    display: inline-block;
    font-family: var(--font-mono);
    font-size: .65rem;
    text-transform: uppercase;
    letter-spacing: .05em;
    padding: .1rem .45rem;
    border-radius: 999px;
    background: var(--hover-soft);
    color: var(--text-secondary);
    border: 1px solid var(--bd-mid);
}
.role-pill.admin { color: var(--accent); background: var(--accent-dim); border-color: var(--accent-glow); }
.role-pill.warn  { color: #fbbf24;       background: rgba(251,191,36,.08); border-color: rgba(251,191,36,.3); }

.user-actions { display: flex; gap: .8rem; flex-wrap: wrap; }

.check-row { flex-direction: row !important; align-items: center; gap: .5rem !important; }

@media (max-width: 720px) {
    /* On phones, drop the side rail and stack: device meta + a
     * horizontal scrolling tab strip on top, content below. The
     * sidebar collapse toggle is irrelevant in this layout. */
    .device-layout {
        grid-template-columns: 1fr;
        gap: var(--space-sm);
    }
    .device-sidebar {
        position: static;
        max-height: none;
        padding: var(--space-sm);
    }
    .sidebar-head .icon-btn { display: none; }      /* collapse-toggle has no meaning here */
    .sidebar-nav {
        display: flex;
        flex-direction: row;
        overflow-x: auto;
        overflow-y: hidden;
        gap: .25rem;
        scrollbar-width: thin;
        -webkit-overflow-scrolling: touch;
        padding-bottom: .25rem;
    }
    .snav-divider { display: none; }                /* hr's between groups don't make sense in a horizontal strip */
    .snav {
        flex: 0 0 auto;
        flex-direction: column;
        align-items: center;
        gap: .15rem;
        min-width: 64px;
        padding: .5rem .55rem;
        font-size: .65rem;
    }
    .snav-label { font-size: .65rem; }
    .device-layout.collapsed .sidebar-nav,
    .device-layout.collapsed .sidebar-meta { display: none; }
    /* Cancel the desktop collapsed visually-hidden trick: on mobile
     * the labels need to be visible inside the horizontal strip even
     * if the layout is technically still in `collapsed` state. */
    .collapsed .snav-label {
        position: static !important;
        width: auto !important; height: auto !important;
        clip: auto !important;
        overflow: visible !important;
    }
}

.shell-toolbar {
    display: flex; align-items: center; gap: var(--space-sm);
    margin-bottom: var(--space-sm);
    padding: .45rem .75rem;
    background: var(--bg-card);
    border: 1px solid var(--bd-soft);
    border-radius: var(--radius-sm);
    font-family: var(--font-mono);
    font-size: .75rem;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: .04em;
}

#terminal {
    height: calc(100vh - 320px);
    min-height: 360px;
    background: var(--bg-secondary);
    border: 1px solid var(--bd-soft);
    border-radius: var(--radius-md);
    padding: var(--space-sm);
}

.hint {
    font-family: var(--font-mono);
    font-size: .75rem;
    color: var(--text-muted);
    margin-top: var(--space-sm);
}

/* ---- Status tab ---------------------------------------------- */
.status-summary {
    position: relative;
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-md);
    padding: var(--space-sm) var(--space-md) var(--space-sm) calc(var(--space-md) + 4px);
    background: linear-gradient(135deg, var(--bg-card) 0%, var(--bg-card-hover) 100%);
    border: 1px solid var(--bd-soft);
    border-left: 4px solid var(--text-muted);
    border-radius: var(--radius-md);
    margin-bottom: var(--space-md);
    flex-wrap: wrap;
    transition: border-left-color .3s, box-shadow .3s;
}
.status-summary.online  { border-left-color: var(--good);
    box-shadow: 0 0 0 1px rgba(52,211,153,.08), 0 4px 14px -10px rgba(52,211,153,.30); }
.status-summary.offline { border-left-color: var(--text-muted); }
.status-summary .eyebrow {
    margin: 0;
    font-size: .65rem;
}
.status-summary h3 {
    margin: 0;
    font-size: 1.15rem;
    letter-spacing: -.01em;
    line-height: 1.2;
}
.status-summary p.muted {
    margin: .1rem 0 0;
    font-size: .75rem;
    line-height: 1.2;
}
.status-summary-side { display: flex; align-items: center; }
.status-summary-side .status { font-size: .75rem; padding: .2rem .6rem;
    background: var(--bd-soft2); border-radius: 999px; }
.status-summary-side .status.online  { color: var(--good); }
.status-summary-side .status.offline { color: var(--text-muted); }

/* Section title above the stat grid - rhythms the page between
 * the device-control cards (LED, Loads, Alarms) and the metric grid. */
.status-section-title {
    font-family: var(--font-mono);
    font-size: .72rem;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: var(--text-muted);
    margin: var(--space-lg) 0 var(--space-sm);
    font-weight: 600;
}

.status-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: var(--space-md);
}

.stat-card {
    background: var(--bg-card);
    border: 1px solid var(--bd-soft);
    border-radius: var(--radius-md);
    padding: var(--space-md) var(--space-lg);
    transition: border-color .15s, transform .15s;
    position: relative;
    overflow: hidden;
}
.stat-card::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(135deg, transparent 60%, var(--accent-dim) 140%);
    opacity: 0;
    transition: opacity .2s;
    pointer-events: none;
    border-radius: inherit;
}
.stat-card:hover {
    border-color: var(--accent-glow);
    transform: translateY(-2px);
}
.stat-card:hover::after { opacity: 1; }

.stat-head {
    display: flex; align-items: center; gap: .5rem;
    margin-bottom: .55rem;
}
.stat-icon {
    color: var(--accent);
    display: inline-flex; align-items: center; justify-content: center;
    width: 28px; height: 28px;
    background: var(--accent-dim);
    border-radius: 6px;
    flex-shrink: 0;
    transition: background .2s, color .2s;
}
/* Tonal grouping for the metric grid: live state in green, network
 * counters in cyan, identity / counters in muted grey. The icon
 * tints; the rest of the card stays neutral so the page doesn't get
 * loud. */
.stat-card.tone-live .stat-icon { color: var(--good);     background: rgba(52,211,153,.14); }
.stat-card.tone-net  .stat-icon { color: var(--accent-2); background: var(--accent-2-dim);   }
.stat-card.tone-meta .stat-icon { color: var(--text-secondary); background: var(--bd-mid);   }
.stat-label {
    font-family: var(--font-mono);
    font-size: .7rem;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: .05em;
}
.stat-value {
    font-family: var(--font-mono);
    font-size: 1.4rem;
    font-weight: 700;
    color: var(--text-primary);
    line-height: 1.2;
    word-break: break-word;
}
.stat-sub {
    margin-top: .35rem;
    font-family: var(--font-mono);
    font-size: .7rem;
    color: var(--text-muted);
}
.stat-sub code {
    font-family: var(--font-mono);
    color: var(--text-primary);
    background: var(--accent-dim);
    padding: 1px 6px;
    border-radius: 4px;
    font-size: .7rem;
}
.ip-copy {
    cursor: pointer;
    border-bottom: 1px dashed var(--text-muted);
    transition: color .15s, border-color .15s;
}
.ip-copy:hover {
    color: var(--accent);
    border-bottom-color: var(--accent);
}

/* ---- OTA progress block (Status tab) ------------------------- */
.ota-block {
    background: var(--bg-card);
    border: 1px solid var(--bd-mid);
    border-left: 4px solid var(--accent);
    border-radius: var(--radius-md);
    padding: var(--space-md) var(--space-lg);
    margin-bottom: var(--space-md);
}
.ota-block.tone-warn { border-left-color: #fbbf24; }
.ota-block.tone-busy { border-left-color: var(--accent-2); }
.ota-block.tone-good { border-left-color: var(--good); }
.ota-block.tone-bad  { border-left-color: var(--danger); }

.ota-head {
    display: flex; align-items: center; gap: .55rem;
    margin-bottom: .55rem;
    flex-wrap: wrap;
}
.ota-tag {
    font-family: var(--font-mono);
    font-size: .65rem;
    text-transform: uppercase;
    letter-spacing: .07em;
    color: var(--accent);
    background: var(--accent-dim);
    border: 1px solid var(--accent-glow);
    padding: .1rem .45rem;
    border-radius: 3px;
}

.ota-bar {
    height: 8px;
    background: var(--bg-secondary);
    border-radius: 999px;
    overflow: hidden;
    border: 1px solid var(--bd-soft);
}
.ota-bar-fill {
    height: 100%;
    background: linear-gradient(90deg, var(--accent) 0%, var(--accent-2) 100%);
    transition: width .25s ease;
    border-radius: 999px;
}
.ota-bar-meta {
    display: flex; justify-content: space-between;
    margin-top: .35rem;
    font-family: var(--font-mono);
    font-size: .75rem;
    color: var(--text-muted);
}
.ota-msg { margin: 0; font-size: .85rem; color: var(--text-secondary); }

/* ---- LED card on Status tab ---------------------------------- */
.led-card {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-md);
    background: var(--bg-card);
    border: 1px solid var(--bd-soft);
    border-left: 4px solid var(--bad);
    border-radius: var(--radius-md);
    padding: var(--space-md) var(--space-lg);
    margin-bottom: var(--space-md);
    transition: border-left-color .2s, background .2s, box-shadow .25s;
}
.led-card.on  { border-left-color: var(--good); background: linear-gradient(110deg, var(--bg-card) 60%, rgba(52,211,153,.10) 100%);
    box-shadow: 0 0 0 1px rgba(52,211,153,.06); }
.led-card.off { border-left-color: var(--text-muted); }
/* Same lamp aesthetic as the load tiles so the two cards rhyme. */
.led-lamp {
    width: 14px; height: 14px; border-radius: 50%;
    background: var(--text-muted);
    box-shadow: inset 0 0 2px rgba(0,0,0,.25);
    flex-shrink: 0;
    transition: background .2s, box-shadow .2s;
}
.led-card.on  .led-lamp { background: var(--good); box-shadow: 0 0 12px var(--good), inset 0 0 2px rgba(0,0,0,.2); }
.led-card.off .led-lamp { background: var(--text-muted); }

/* ---- Loads card on Status tab -------------------------------- */
.loads-card {
    background: var(--bg-card);
    border: 1px solid var(--bd-soft);
    border-left: 4px solid var(--accent);
    border-radius: var(--radius-md);
    padding: var(--space-md) var(--space-lg);
    margin-bottom: var(--space-md);
}
.loads-head {
    display: flex; align-items: center; gap: var(--space-md);
    margin-bottom: var(--space-md);
}
.loads-head-text { display: flex; flex-direction: column; gap: .15rem; }

/* Loads tile grid: each load is its own card with a status lamp,
 * label, big state readout, and toggle switch. Auto-fits so 4 tiles
 * sit in one row on wide screens, 2 columns on tablets, 1 on phones. */
.loads-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: var(--space-md);
}
.load-tile {
    position: relative;
    display: grid;
    grid-template-rows: auto 1fr auto;
    gap: .5rem;
    padding: var(--space-md) var(--space-md) var(--space-sm);
    background: var(--bg-secondary);
    border: 1px solid var(--bd-soft);
    border-radius: var(--radius-md);
    transition: border-color .2s, background .2s, transform .15s;
}
.load-tile:hover { transform: translateY(-1px); border-color: var(--bd-mid); }
.load-tile.on  { border-color: var(--accent-glow); background: linear-gradient(160deg, var(--bg-secondary) 0%, var(--accent-dim) 130%); }
.load-tile.off { border-color: var(--bd-soft); }

.load-tile-top {
    display: flex; align-items: center; gap: .5rem;
}
.load-lamp {
    width: 10px; height: 10px; border-radius: 50%;
    background: var(--text-muted);
    box-shadow: inset 0 0 2px rgba(0,0,0,.25);
    flex-shrink: 0;
    transition: background .2s, box-shadow .2s;
}
.load-tile.on  .load-lamp { background: var(--good); box-shadow: 0 0 8px var(--good), inset 0 0 2px rgba(0,0,0,.2); }
.load-tile.off .load-lamp { background: var(--text-muted); }
.load-tile.unknown .load-lamp { background: transparent; border: 1px solid var(--bd-mid); }

.load-name {
    font-family: var(--font-sans);
    font-size: .85rem;
    color: var(--text-secondary);
    font-weight: 500;
}
.load-tile.on .load-name { color: var(--text-primary); }

.load-tile-state {
    font-size: 1.3rem;
    font-weight: 700;
    color: var(--text-muted);
    letter-spacing: .02em;
    line-height: 1;
}
.load-tile.on  .load-tile-state { color: var(--accent); }
.load-tile.off .load-tile-state { color: var(--text-muted); }

.load-switch { justify-self: end; align-self: end; }

.led-left { display: flex; align-items: center; gap: var(--space-md); }
.led-icon { color: var(--text-muted); }
.led-card.on .led-icon { color: var(--good); }
.led-state-text {
    font-family: var(--font-mono);
    font-size: 1rem;
    font-weight: 700;
    color: var(--text-primary);
    margin: .15rem 0;
}

/* iOS-style toggle switch */
.switch { display: inline-block; cursor: pointer; }
.switch input { display: none; }
.switch-track {
    position: relative;
    display: inline-block;
    width: 44px; height: 24px;
    background: var(--bg-secondary);
    border: 1px solid var(--bd-strong);
    border-radius: 999px;
    transition: background .15s, border-color .15s;
}
.switch-thumb {
    position: absolute;
    top: 2px; left: 2px;
    width: 18px; height: 18px;
    background: var(--text-secondary);
    border-radius: 50%;
    transition: left .15s, background .15s;
}
.switch input:checked + .switch-track {
    background: var(--accent-dim);
    border-color: var(--accent);
}
.switch input:checked + .switch-track .switch-thumb {
    left: 22px;
    background: var(--accent);
    box-shadow: 0 0 6px var(--accent-glow);
}
.switch input:disabled + .switch-track { opacity: .4; cursor: not-allowed; }

/* ---- Data tab ------------------------------------------------ */
.data-toolbar {
    display: flex; flex-wrap: wrap; align-items: end; gap: var(--space-md);
    padding: var(--space-md);
    background: var(--bg-card);
    border: 1px solid var(--bd-soft);
    border-radius: var(--radius-md);
    margin-bottom: var(--space-md);
}
.data-toolbar label {
    display: flex; flex-direction: column; gap: .25rem;
    font-family: var(--font-mono);
    font-size: .7rem;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--text-muted);
}
.data-toolbar .btn { align-self: end; }

.metric-cards {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: var(--space-sm);
    margin-bottom: var(--space-md);
}
.metric-card {
    background: var(--bg-card);
    border: 1px solid var(--bd-soft);
    border-radius: var(--radius-md);
    padding: var(--space-md);
    cursor: pointer;
    transition: border-color .15s, background .15s;
}
.metric-card:hover  { background: var(--bg-card-hover); }
.metric-card.active { border-color: var(--accent); background: var(--accent-dim); }
.metric-name {
    font-family: var(--font-mono);
    font-size: .75rem;
    color: var(--text-muted);
    text-transform: lowercase;
}
.metric-value {
    font-family: var(--font-mono);
    font-size: 1.6rem;
    font-weight: 700;
    color: var(--text-primary);
    margin: .25rem 0;
}
.metric-meta {
    font-family: var(--font-mono);
    font-size: .7rem;
    color: var(--text-muted);
}

.chart-card {
    background: var(--bg-card);
    border: 1px solid var(--bd-soft);
    border-radius: var(--radius-md);
    padding: var(--space-md);
}
/* Chart.js sizes the canvas to its parent. The parent must have an
 * explicit height or the layout grows unbounded as the chart redraws. */
.chart-wrap {
    position: relative;
    width: 100%;
    height: 280px;
}
.chart-wrap canvas { cursor: zoom-in; }
@media (min-width: 1100px) { .chart-wrap { height: 340px; } }

/* ---- Firmware library ---------------------------------------- */
.fw-table {
    background: var(--bg-card);
    border: 1px solid var(--bd-soft);
    border-radius: var(--radius-md);
    overflow: hidden;
}
.fw-grid {
    width: 100%;
    border-collapse: collapse;
    font-size: .85rem;
}
.fw-grid th {
    background: var(--bg-secondary);
    text-align: left;
    padding: .75rem 1rem;
    font-family: var(--font-mono);
    font-size: .7rem;
    font-weight: 500;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: .05em;
    border-bottom: 1px solid var(--hover-soft);
}
.fw-grid td {
    padding: .85rem 1rem;
    border-bottom: 1px solid var(--bd-soft2);
    vertical-align: middle;
}
.fw-grid tr:last-child td { border-bottom: none; }
.fw-grid tr:hover td { background: var(--hover-soft3); }
.fw-grid .trunc { max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---- Push-firmware picker ----------------------------------- */
.fw-picker {
    max-height: 50vh;
    overflow-y: auto;
    margin: var(--space-sm) 0;
    border: 1px solid var(--bd-soft);
    border-radius: var(--radius-sm);
    background: var(--bg-secondary);
}
.fw-row {
    display: flex; align-items: flex-start; gap: var(--space-sm);
    padding: .65rem .85rem;
    cursor: pointer;
    border-bottom: 1px solid var(--bd-soft2);
    transition: background .1s;
}
.fw-row:last-child { border-bottom: none; }
.fw-row:hover { background: var(--hover-soft2); }
.fw-row input[type=radio] { margin-top: .25rem; accent-color: var(--accent); }
.fw-row .fw-name { font-size: .9rem; }
.fw-row .fw-meta {
    font-size: .75rem;
    color: var(--text-muted);
    margin-top: .15rem;
}

/* ---- Config tab ---------------------------------------------- */
.cfg-toolbar {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-md);
    padding: .5rem .75rem;
    background: var(--bg-card);
    border: 1px solid var(--bd-soft);
    border-radius: var(--radius-md);
    margin-bottom: var(--space-md);
}
.cfg-card {
    background: var(--bg-card);
    border: 1px solid var(--bd-soft);
    border-radius: var(--radius-md);
    padding: var(--space-lg);
    margin-bottom: var(--space-md);
}
.cfg-rows { display: flex; flex-direction: column; gap: var(--space-lg); }

.cfg-row { display: flex; flex-direction: column; gap: .35rem; }
.cfg-row-head {
    display: flex; align-items: center; gap: .55rem;
}
.cfg-row label {
    font-family: var(--font-mono);
    font-size: .75rem;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--text-muted);
}
.cfg-tag {
    font-size: .65rem;
    padding: .05rem .35rem;
    background: var(--bg-secondary);
    color: var(--text-muted);
    border: 1px solid var(--bd-soft);
    border-radius: 3px;
    text-transform: uppercase;
    letter-spacing: .05em;
}
.cfg-readonly {
    background: var(--bg-secondary);
    border: 1px solid var(--bd-soft);
    border-radius: var(--radius-sm);
    padding: .55rem .75rem;
    color: var(--text-primary);
    font-size: .9rem;
    word-break: break-all;
}
.cfg-input-row {
    display: flex; gap: var(--space-sm);
    align-items: stretch;
}
.cfg-input-row input,
.cfg-input-row select {
    flex: 1;
    background: var(--bg-secondary);
    color: var(--text-primary);
    border: 1px solid var(--bd-strong);
    border-radius: var(--radius-sm);
    padding: .55rem .75rem;
    font-family: var(--font-mono);
    font-size: .9rem;
}
.cfg-input-row input:focus,
.cfg-input-row select:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-dim);
}
.cfg-help {
    margin: 0;
    font-size: .75rem;
    color: var(--text-muted);
}
.cfg-err  { margin: 0; font-size: .8rem; color: var(--danger); }
.cfg-ok   { margin: 0; font-size: .8rem; color: var(--good);   }

/* ---- Setup wizard (Web Serial) ------------------------------- */
.setup-wizard {
    display: flex; flex-direction: column;
    gap: var(--space-md);
    max-width: 720px;
}
.setup-step {
    background: var(--bg-card);
    border: 1px solid var(--bd-soft);
    border-radius: var(--radius-md);
    padding: var(--space-lg);
}
.setup-step-head {
    display: flex; align-items: center; gap: var(--space-sm);
    margin-bottom: var(--space-md);
}
.setup-step-head h3 { margin: 0; font-size: 1.05rem; }
.step-num {
    display: inline-flex; align-items: center; justify-content: center;
    width: 26px; height: 26px;
    background: var(--accent-dim);
    color: var(--accent);
    border: 1px solid var(--accent-glow);
    border-radius: 50%;
    font-family: var(--font-mono);
    font-size: .8rem;
    font-weight: 700;
}
.step-num.done { background: var(--good); color: #04140a; border-color: var(--good); }

.setup-actions {
    display: flex; gap: var(--space-sm); align-items: center;
    margin-top: var(--space-md);
    flex-wrap: wrap;
}

.setup-card {
    background: var(--bg-card);
    border: 1px solid rgba(248,113,113,.4);
    border-radius: var(--radius-md);
    padding: var(--space-lg);
    max-width: 720px;
}

.device-id-row {
    display: flex; align-items: center; gap: .55rem;
    margin: var(--space-sm) 0 var(--space-md);
}

.ap-list {
    display: flex; flex-direction: column;
    gap: .3rem;
    margin: var(--space-md) 0;
    max-height: 380px;
    overflow-y: auto;
    border: 1px solid var(--hover-soft);
    border-radius: var(--radius-sm);
    background: var(--bg-secondary);
}
.ap-row {
    display: grid;
    grid-template-columns: 80px 1fr auto;
    align-items: center;
    gap: var(--space-md);
    padding: .6rem .9rem;
    background: none;
    border: none;
    border-bottom: 1px solid var(--bd-soft2);
    color: var(--text-primary);
    text-align: left;
    cursor: pointer;
    font-family: inherit;
    transition: background .12s;
}
.ap-row:last-child { border-bottom: none; }
.ap-row:hover     { background: var(--hover-soft2); }
.ap-rssi {
    font-family: var(--font-mono);
    font-size: .8rem;
    color: var(--accent);
}
.ap-ssid { font-size: .9rem; }
.ap-auth { font-size: .75rem; }

.claim {
    display: inline-block;
    margin-top: .5rem;
    padding: .25rem .55rem;
    border-radius: 4px;
    font-size: .8rem;
}
.claim.ok   { color: var(--good);   background: rgba(46,160,67,.1);  border: 1px solid rgba(46,160,67,.35); }
.claim.warn { color: var(--danger); background: rgba(248,113,113,.1); border: 1px solid rgba(248,113,113,.35); }

.setup-log {
    margin-top: var(--space-md);
    background: var(--bg-card);
    border: 1px solid var(--bd-soft);
    border-radius: var(--radius-md);
    padding: var(--space-sm) var(--space-md);
}
.setup-log summary {
    cursor: pointer;
    font-family: var(--font-mono);
    font-size: .75rem;
    color: var(--text-muted);
    padding: .25rem 0;
}
.setup-log pre {
    margin: var(--space-sm) 0 0;
    padding: var(--space-sm);
    background: var(--bg-secondary);
    border-radius: var(--radius-sm);
    max-height: 280px;
    overflow: auto;
    font-family: var(--font-mono);
    font-size: .75rem;
    color: var(--text-secondary);
    white-space: pre-wrap;
}

/* ---- Audit tab ---------------------------------------------- */
.audit-toolbar {
    display: flex; align-items: center; gap: var(--space-md);
    padding: .5rem .75rem;
    background: var(--bg-card);
    border: 1px solid var(--bd-soft);
    border-radius: var(--radius-md);
    margin-bottom: var(--space-md);
}
.audit-filter {
    display: flex; align-items: center; gap: .5rem;
    font-family: var(--font-mono);
    font-size: .75rem;
    color: var(--text-muted);
    text-transform: uppercase;
}
.audit-list {
    list-style: none;
    margin: 0;
    padding: 0;
    background: var(--bg-card);
    border: 1px solid var(--bd-soft);
    border-radius: var(--radius-md);
    overflow: hidden;
}
.audit-day {
    padding: .55rem .9rem;
    font-family: var(--font-mono);
    font-size: .7rem;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: .05em;
    background: var(--accent-dim);
    border-top: 1px solid var(--bd-soft);
    border-bottom: 1px solid var(--bd-soft);
}
.audit-day:first-child { border-top: 0; }

.audit-row {
    display: flex;
    gap: .8rem;
    padding: .65rem .9rem;
    border-top: 1px solid var(--bd-soft);
    align-items: flex-start;
}
.audit-row:first-child { border-top: 0; }

.audit-dot {
    width: 22px; height: 22px;
    flex-shrink: 0;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-family: var(--font-mono);
    font-size: .8rem;
    font-weight: 700;
    line-height: 1;
    border: 1.5px solid;
    background: var(--bg-card);
}
.audit-row.tone-good .audit-dot { color: var(--accent);   border-color: var(--accent);   background: var(--accent-dim); }
.audit-row.tone-bad  .audit-dot { color: #dc2626;          border-color: #dc2626;         background: rgba(220,38,38,.10); }
.audit-row.tone-warn .audit-dot { color: #d97706;          border-color: #d97706;         background: rgba(217,119,6,.10); }
.audit-row.tone-busy .audit-dot { color: var(--accent-2); border-color: var(--accent-2); background: var(--accent-2-dim); }
.audit-row.tone-info .audit-dot { color: var(--text-muted); border-color: var(--bd-mid); background: var(--bg-card); }
.audit-glyph { display: block; transform: translateY(-1px); }

.audit-body { flex: 1 1 auto; min-width: 0; }
.audit-head {
    display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap;
}
.audit-kind {
    font-family: var(--font-mono);
    font-size: .8rem;
    color: var(--text-primary);
    font-weight: 600;
}
.audit-when { font-size: .7rem; }
.audit-msg {
    margin-top: .15rem;
    font-size: .85rem;
    color: var(--text-secondary);
    word-break: break-word;
}
.audit-actor {
    margin-left: .35rem;
    font-family: var(--font-mono);
    font-size: .75rem;
    color: var(--text-muted);
}
.audit-empty {
    padding: 1.2rem;
    text-align: center;
    list-style: none;
}
.audit-empty.error { color: #dc2626; }
.audit-more-wrap {
    display: flex; justify-content: center; margin-top: var(--space-md);
}

/* ---- Storage tab -------------------------------------------- */
.storage-state {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-md);
}
.status-pill {
    display: inline-flex; align-items: center; gap: .35rem;
    font-family: var(--font-mono);
    font-size: .75rem;
    padding: .15rem .55rem;
    border-radius: 999px;
    border: 1px solid;
}
/* Glyph supplement so meaning isn't conveyed by colour alone (WCAG
 * 1.4.1). aria-hidden on the pseudo-element via `speak: never` would
 * be ideal but isn't supported; instead we keep the glyph short and
 * decorative since the surrounding label text already states the
 * status in words. */
.status-pill::before {
    font-weight: 700;
    line-height: 1;
}
.status-pill.tone-good { color: var(--accent);   border-color: var(--accent);   background: var(--accent-dim); }
.status-pill.tone-good::before { content: "✓"; }
.status-pill.tone-warn { color: #d97706;          border-color: #d97706;         background: rgba(217,119,6,.10); }
.status-pill.tone-warn::before { content: "!"; }
.status-pill.tone-bad  { color: #dc2626;          border-color: #dc2626;         background: rgba(220,38,38,.10); }
.status-pill.tone-bad::before  { content: "✕"; }
.status-pill.tone-soft { color: var(--text-muted); border-color: var(--bd-mid);   background: var(--bg-card); }
.status-pill.tone-soft::before { content: ""; }

.storage-bar {
    height: 14px;
    background: var(--bd-soft);
    border-radius: 7px;
    overflow: hidden;
    margin: .35rem 0 .25rem;
}
.storage-bar-fill {
    height: 100%;
    background: linear-gradient(90deg, var(--accent) 0%, var(--accent-2) 100%);
    transition: width .3s ease;
}
.storage-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: var(--space-md);
}

/* SD file browser */
.storage-fs-toolbar {
    display: flex; align-items: center; gap: var(--space-md);
    margin-bottom: .5rem;
}
.storage-fs-path {
    margin: 0 0 .5rem;
    font-size: .8rem;
    word-break: break-all;
}
.storage-fs-list {
    display: flex; flex-direction: column;
    gap: .25rem;
}
.storage-fs-row {
    display: flex; align-items: center; gap: var(--space-md);
    /* Min row height covers the 44px touch-target requirement for the
     * primary tap area (the row itself). Action buttons inside use
     * 8px gap (var(--space-xs)) to satisfy the touch-spacing rule. */
    min-height: 44px;
    padding: .35rem .55rem;
    border: 1px solid var(--bd-soft);
    border-radius: 4px;
    background: var(--bg);
}
.storage-fs-row .link-btn + .link-btn {
    margin-left: var(--space-xs);
}
.storage-fs-row.is-dir { background: var(--accent-dim); }
.storage-fs-icon { width: 1.5em; text-align: center; }
.storage-fs-name { flex: 1 1 auto; word-break: break-all; }
.storage-fs-size { min-width: 5em; text-align: right; }

.storage-audio-bar {
    display: flex; align-items: center; gap: var(--space-md);
    min-height: 44px;
    margin-bottom: .5rem;
    padding: .35rem .55rem;
    border: 1px solid var(--accent);
    border-radius: 4px;
    background: var(--accent-dim);
}
.storage-audio-bar[hidden] { display: none; }
.storage-audio-icon { color: var(--accent); }

.storage-file-text {
    flex: 1 1 auto;
    min-height: 360px;
    padding: var(--space-md);
    font-family: var(--font-mono);
    font-size: .8rem;
    background: var(--bg);
    border: 0;
    border-top: 1px solid var(--bd-soft);
    color: var(--text-primary);
    resize: vertical;
}
.storage-file-warn {
    margin: 0;
    padding: .5rem var(--space-md);
    border-top: 1px solid var(--bd-soft);
    font-size: .75rem;
}

/* ---- Dashboard loading overlay --------------------------------- *
 * Centred card that fades over the page during the first fetch on
 * page load + on explicit Reload. Polling refreshes don't trigger
 * it (those are silent so the chart never visibly flickers).
 * `aria-live=polite + aria-busy=true` so screen readers announce
 * the loading state without stealing focus. */
.dash-loading {
    position: fixed; inset: 0;
    z-index: 9000;
    display: flex; align-items: center; justify-content: center;
    background: rgba(10, 12, 16, .55);
    backdrop-filter: blur(2px);
    opacity: 1;
    transition: opacity .2s ease;
}
.dash-loading[hidden]       { display: none !important; }
.dash-loading.is-fading-out { opacity: 0; pointer-events: none; }
.dash-loading-card {
    display: flex; align-items: center; gap: var(--space-md);
    padding: var(--space-md) var(--space-lg);
    background: var(--bg-card);
    border: 1px solid var(--bd-soft);
    border-radius: var(--radius-md);
    box-shadow: 0 24px 64px rgba(0,0,0,.45);
    min-width: 240px;
}
.dash-loading-title { font-weight: 600; font-size: .95rem; color: var(--text-primary); }
.dash-loading-sub   { font-size: .7rem; margin-top: .2rem; }

/* Spinner: 18px ring with the accent color as the moving arc. */
.dash-spinner {
    flex: 0 0 auto;
    width: 18px; height: 18px;
    border: 2px solid var(--bd-strong);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: dash-spin .9s linear infinite;
}
@keyframes dash-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
    .dash-spinner { animation: none; border-top-color: var(--accent); }
}

/* ---- Multi-device dashboard (/charts) ---------------------- *
 * Responsive widget grid. Cards auto-fit at 420px+ minimum width
 * (one per row on phones, two on small laptops, more on desktop). */
.dash-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(420px, 1fr));
    gap: var(--space-md);
}
.dash-widget {
    background: var(--bg-card);
    border: 1px solid var(--bd-soft);
    border-radius: var(--radius-md);
    padding: var(--space-md);
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    min-height: 280px;
}
.dash-widget-head {
    display: flex; align-items: baseline; gap: var(--space-sm);
}
.dash-widget-title {
    margin: 0;
    font-size: 1rem;
    font-weight: 600;
    color: var(--text-primary);
}
.dash-widget-meta {
    font-size: .7rem;
}
.dash-widget-chart {
    position: relative;
    flex: 1 1 auto;
    min-height: 200px;
    width: 100%;
}
.dash-widget-chart canvas {
    width: 100% !important;
    height: 100% !important;
    /* Hint to the user that the chart accepts a drag gesture for
     * zoom. Double-click resets. */
    cursor: zoom-in;
}

/* Floating "Reset zoom" button - sits in the top-right corner of any
 * chart container, hidden until the chart actually has a zoom or pan
 * applied. Toggled via the .is-zoomed class on the parent container
 * (set/cleared by the chart's onZoomComplete / onPanComplete hooks).
 * Discoverable visual + the dblclick gesture both still work. */
.chart-reset-btn {
    position: absolute;
    top: .5rem;
    right: .5rem;
    z-index: 5;
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    padding: .25rem .55rem;
    font-family: inherit;
    font-size: .7rem;
    color: var(--text-secondary);
    background: var(--bg-card);
    border: 1px solid var(--bd-strong);
    border-radius: var(--radius-sm);
    cursor: pointer;
    opacity: 0;
    transform: translateY(-2px);
    pointer-events: none;
    transition: opacity .15s ease, transform .15s ease, color .15s, border-color .15s;
}
.is-zoomed > .chart-reset-btn {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
}
.chart-reset-btn:hover {
    color: var(--text-primary);
    border-color: var(--accent);
}
.chart-reset-btn svg {
    flex: 0 0 auto;
}
.dash-widget-blank {
    margin: 0;
    padding: var(--space-md);
    text-align: center;
    align-self: center;
    width: 100%;
    font-size: .85rem;
}
.dash-widget-legend {
    display: flex; flex-wrap: wrap; gap: .35rem;
    padding-top: .25rem;
    border-top: 1px solid var(--bd-soft);
    margin-top: .25rem;
}
.dash-chip {
    display: inline-flex; align-items: center; gap: .35rem;
    padding: .15rem .5rem;
    border: 1px solid var(--bd-soft);
    border-radius: 999px;
    background: var(--bg-secondary);
    font-size: .7rem;
    line-height: 1.3;
}
.dash-swatch {
    width: 9px; height: 9px;
    border-radius: 50%;
    flex: 0 0 auto;
}
.dash-chip-met {
    font-size: .65rem;
    opacity: .85;
}

/* Edit modal -------------------------------------------------- */
.dash-edit[open] {
    width: min(92vw, 720px);
    max-height: min(92vh, 720px);
    padding: 0;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    margin: auto; inset: 0;
}
.dash-edit-form {
    display: flex; flex-direction: column;
    height: 100%;
    width: 100%;
    overflow: hidden;
}
.dash-edit-head {
    display: flex; align-items: center; gap: var(--space-md);
    padding: .75rem var(--space-lg);
    border-bottom: 1px solid var(--bd-soft);
    background: var(--bg-secondary);
}
.dash-edit-head h3 { margin: 0; font-size: 1rem; }
.dash-edit-body {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: var(--space-md) var(--space-lg);
    display: flex; flex-direction: column; gap: var(--space-md);
}

.dash-series-list {
    display: flex; flex-direction: column;
    gap: .4rem;
    margin: var(--space-sm) 0 var(--space-sm);
}
.dash-series-empty {
    margin: 0;
    padding: var(--space-md);
    text-align: center;
    font-size: .85rem;
    border: 1px dashed var(--bd-soft);
    border-radius: var(--radius-sm);
}
.dash-series-row {
    display: grid;
    grid-template-columns: 32px minmax(0, 1.4fr) minmax(0, 1.4fr) minmax(0, 1fr) 32px;
    gap: .35rem;
    align-items: center;
}
.dash-series-row .dash-color {
    width: 32px; height: 32px;
    padding: 0;
    border: 1px solid var(--bd-soft);
    border-radius: var(--radius-sm);
    background: transparent;
    cursor: pointer;
}
.dash-series-row .dropdown {
    width: 100%;
    min-width: 0;
}
.dash-series-row .dash-series-rm {
    justify-self: center;
    font-weight: 600;
}

@media (max-width: 640px) {
    .dash-series-row {
        grid-template-columns: 32px 1fr 32px;
        grid-template-areas:
            "color dev   rm"
            "met   met   met"
            "label label label";
        row-gap: .35rem;
    }
    .dash-series-row .dash-color    { grid-area: color; }
    .dash-series-row .dash-series-dev   { grid-area: dev; }
    .dash-series-row .dash-series-met   { grid-area: met; }
    .dash-series-row .dash-series-label { grid-area: label; }
    .dash-series-row .dash-series-rm    { grid-area: rm; }
}

.empty-icon {
    color: var(--accent);
    margin: 0 0 var(--space-sm);
    text-align: center;
}
.empty-state h3 { margin-top: 0; }
.empty-state    { text-align: center; }

/* ---- Datalogger tab ----------------------------------------- */
.dl-channels {
    display: flex; flex-direction: column;
    gap: var(--space-md);
    margin-top: .5rem;
}
.dl-group-h {
    margin: 0 0 .35rem;
    font-family: var(--font-mono);
    font-size: .7rem;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--text-muted);
}
.dl-group-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: .35rem .75rem;
}
.dl-ch {
    display: flex; align-items: center; gap: .5rem;
    padding: .25rem .4rem;
    border: 1px solid var(--bd-soft);
    border-radius: 4px;
    background: var(--bg);
    cursor: pointer;
}
.dl-ch input { cursor: pointer; }
.dl-ch-name { flex: 1 1 auto; font-size: .85rem; }
.dl-ch-unit { font-size: .75rem; }

.btn.danger {
    color: #dc2626;
    border-color: #dc2626;
}
.btn.danger:hover {
    background: rgba(220, 38, 38, .08);
}

/* Log viewer modal: full-screen-ish, vertical stack of head / chart /
 * table / footer. Chart and table share the available height.
 *
 * The `[open]` scoping is intentional: setting `display: flex` on the
 * raw selector would override the user-agent's `dialog:not([open]) {
 * display: none }` rule, leaving the modal permanently visible at the
 * bottom of the page. We only flex when the dialog is actually open. */
.log-viewer { padding: 0; }
.log-viewer[open] {
    width: min(96vw, 1280px);
    height: min(92vh, 900px);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    /* Centre on the viewport regardless of where it lives in the DOM
     * (the <dialog> default is top-aligned in some browsers). */
    margin: auto;
    inset: 0;
}
.log-viewer-head {
    display: flex; align-items: center; gap: var(--space-md);
    padding: .75rem var(--space-lg);
    border-bottom: 1px solid var(--bd-soft);
    background: var(--bg-secondary);
}
.log-viewer-head h3 {
    font-size: 1rem; word-break: break-all;
}
/* Slim load progress bar that lives between the header and the
 * controls row. Hidden when not loading. Native <progress> styling is
 * inconsistent across browsers, so we restyle both pseudo-elements. */
.log-viewer-prog {
    width: 100%;
    height: 4px;
    border: 0;
    background: var(--bg-secondary);
    appearance: none;
    -webkit-appearance: none;
    display: block;
}
.log-viewer-prog[hidden] { display: none; }
.log-viewer-prog::-webkit-progress-bar  { background: var(--bg-secondary); }
.log-viewer-prog::-webkit-progress-value {
    background: linear-gradient(90deg, var(--accent) 0%, var(--accent-2) 100%);
    transition: width .15s ease;
}
.log-viewer-prog::-moz-progress-bar {
    background: linear-gradient(90deg, var(--accent) 0%, var(--accent-2) 100%);
}
/* Indeterminate (no `value` attr): subtle barber-pole. */
.log-viewer-prog:not([value]) {
    background: linear-gradient(90deg,
        var(--bg-secondary) 0%, var(--accent) 50%, var(--bg-secondary) 100%);
    background-size: 200% 100%;
    animation: dlog-prog-slide 1.1s linear infinite;
}
@keyframes dlog-prog-slide {
    from { background-position: 0%   0; }
    to   { background-position: 200% 0; }
}

.log-viewer-controls {
    display: flex; align-items: center; gap: var(--space-md);
    flex-wrap: wrap;
    padding: .55rem var(--space-lg);
    border-bottom: 1px solid var(--bd-soft);
}
.dl-series {
    display: flex; gap: .35rem; flex-wrap: wrap;
}
.dl-chip {
    display: inline-flex; align-items: center; gap: .35rem;
    padding: .15rem .5rem;
    border: 1px solid var(--bd-soft);
    border-radius: 999px;
    background: var(--bg);
    font-family: var(--font-mono);
    font-size: .72rem;
    cursor: pointer;
    user-select: none;
}
.dl-chip .dl-swatch {
    width: 10px; height: 10px; border-radius: 50%;
    background: var(--bd-mid);
    flex: 0 0 auto;
}
.dl-chip.on {
    border-color: var(--bd-mid);
    background: var(--bg-card);
    color: var(--text-primary);
}
.dl-chip.off { color: var(--text-muted); opacity: .65; }
.dl-chip.off .dl-swatch { background: var(--bd-soft); }

.log-viewer-chart {
    flex: 0 0 38%;
    min-height: 220px;
    padding: var(--space-md) var(--space-lg);
    border-bottom: 1px solid var(--bd-soft);
    position: relative;
}
.log-viewer-chart canvas {
    width: 100% !important;
    height: 100% !important;
    cursor: zoom-in;
}
.log-viewer-tablewrap {
    flex: 1 1 auto;
    overflow: auto;
    padding: 0 var(--space-lg);
}
.log-viewer-table {
    width: 100%;
    border-collapse: collapse;
    font-family: var(--font-mono);
    font-size: .75rem;
}
.log-viewer-table thead th {
    position: sticky;
    top: 0;
    background: var(--bg-card);
    border-bottom: 1px solid var(--bd-soft);
    text-align: left;
    padding: .4rem .55rem;
    font-weight: 500;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: .04em;
    font-size: .65rem;
    white-space: nowrap;
}
.log-viewer-table tbody td {
    padding: .25rem .55rem;
    border-bottom: 1px solid var(--bd-soft);
    white-space: nowrap;
}
.log-viewer-table tbody tr:hover td {
    background: var(--bg-secondary);
}
.log-viewer-foot {
    display: flex; align-items: center; gap: var(--space-md);
    padding: .55rem var(--space-lg);
    border-top: 1px solid var(--bd-soft);
    background: var(--bg-secondary);
}

/* ---- Programs tab ------------------------------------------- */
.program-card {
    background: var(--bg-card);
    border: 1px solid var(--bd-soft);
    border-left: 3px solid var(--bd-mid);
    border-radius: var(--radius-md);
    padding: .65rem var(--space-md);
    margin-bottom: .5rem;
}
.program-card-head {
    display: flex; align-items: center; gap: var(--space-md);
    flex-wrap: wrap;
}
.enable-toggle {
    display: inline-flex; align-items: center; gap: .5rem;
    cursor: pointer;
    padding: .15rem .55rem;
    border-radius: 999px;
    border: 1px solid var(--bd-soft);
    background: var(--bg);
    transition: background .15s, border-color .15s, color .15s;
    user-select: none;
}
.enable-toggle:has(input:checked) {
    background: var(--accent-dim);
    border-color: var(--accent);
}
.enable-toggle:has(input:checked) .enable-toggle-label {
    color: var(--accent);
}
.enable-toggle-label {
    font-family: var(--font-mono);
    font-size: .72rem;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--text-muted);
    min-width: 4.2em;
}
.program-card-meta {
    font-size: .75rem;
    margin-top: .25rem;
}

/* Per-program "physical devices used" disclosure.  Lives directly
 * underneath the runtime-state line so the user can fold it open to
 * see exactly which LEDs / loads / sensors a rule touches without
 * opening the editor. */
.program-devices {
    margin-top: .4rem;
    border-top: 1px dashed var(--bd-soft);
    padding-top: .35rem;
}
.program-devices > summary {
    list-style: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: .55rem;
    flex-wrap: wrap;
    font-size: .75rem;
    user-select: none;
}
.program-devices > summary::-webkit-details-marker { display: none; }
.program-devices > summary::before {
    content: "▸";
    display: inline-block;
    color: var(--text-muted);
    transition: transform .15s ease;
    width: .8em;
    font-size: .7rem;
}
.program-devices[open] > summary::before { transform: rotate(90deg); }
.program-devices-eyebrow {
    font-family: var(--font-mono);
    text-transform: uppercase;
    letter-spacing: .05em;
    font-size: .68rem;
    color: var(--text-muted);
}
.program-devices-summary {
    font-family: var(--font-mono);
    color: var(--text);
}
.program-devices-body {
    margin-top: .45rem;
    display: flex;
    flex-direction: column;
    gap: .3rem;
    padding-left: 1.2em;
    font-size: .78rem;
}
.program-devices-row {
    display: flex;
    align-items: center;
    gap: .55rem;
    flex-wrap: wrap;
}

.program-editor {
    width: min(1200px, 96vw);
    max-height: 92vh;
    border: none;
    background: transparent;
    padding: 0;
}
.program-editor::backdrop { background: rgba(0,0,0,.4); }
.program-editor-form {
    background: var(--bg-card);
    border: 1px solid var(--bd-soft);
    border-radius: var(--radius-md);
    display: flex; flex-direction: column;
    max-height: 92vh;
    overflow: hidden;
}
.program-editor-head {
    display: flex; align-items: center; gap: var(--space-md);
    padding: var(--space-md);
    border-bottom: 1px solid var(--bd-soft);
}
.program-editor-head h3 { margin: 0; }
.program-editor-body {
    padding: var(--space-md);
    overflow: auto;
    flex: 1 1 60%;
    min-width: 0;
}

/* Split layout: form on the left, live pseudo-code preview on the
 * right. Each side scrolls independently. Stacks vertically below
 * 900px so the preview doesn't squeeze the form. */
.program-editor-split {
    display: flex;
    flex: 1 1 auto;
    min-height: 0;       /* lets children shrink properly inside flex */
    overflow: hidden;
}
.program-preview {
    flex: 1 1 40%;
    min-width: 0;
    display: flex; flex-direction: column;
    border-left: 1px solid var(--bd-soft);
    background: var(--bg-secondary);
    overflow: hidden;
}
.program-preview-head {
    display: flex; align-items: center; gap: var(--space-sm);
    padding: .55rem var(--space-md);
    border-bottom: 1px solid var(--bd-soft);
}
.program-preview-code {
    margin: 0;
    padding: var(--space-md);
    flex: 1 1 auto;
    overflow: auto;
    font-family: var(--font-mono);
    font-size: .8rem;
    line-height: 1.55;
    color: var(--text-primary);
    white-space: pre;
    tab-size: 2;
}
/* Token colours - readable on dark, recoloured for light below. */
.pp-kw   { color: var(--accent);    font-weight: 600; }     /* IF / EVERY / SET / ... */
.pp-src  { color: var(--accent-2); }                        /* sources: ambient.temp_c, accel.x */
.pp-var  { color: #f59e0b; }                                /* user-named variables */
.pp-nvs  { color: #c084fc; }                                /* persistent NVS keys */
.pp-num  { color: #f472b6; }
.pp-str  { color: #fbbf24; }
.pp-bool { color: #f472b6; }
.pp-fn   { color: #34d399; }                                /* function names + builtins */
.pp-op   { color: var(--text-muted); }
.pp-comment, .pp-meta { color: var(--text-muted); font-style: italic; }
.pp-empty { color: var(--text-muted); padding: var(--space-md); font-style: italic; }

:root[data-theme="light"] .pp-num,
:root[data-theme="light"] .pp-bool { color: #be185d; }
:root[data-theme="light"] .pp-str  { color: #b45309; }
:root[data-theme="light"] .pp-nvs  { color: #7c3aed; }

@media (max-width: 900px) {
    .program-editor-split { flex-direction: column; }
    .program-preview      { border-left: 0; border-top: 1px solid var(--bd-soft); max-height: 40vh; }
    .program-editor-body  { flex: 1 1 60%; }
}
.program-editor-result {
    background: var(--bg-card);
    border-top: 1px solid var(--bd-soft);
    padding: .75rem 1rem;
    margin: 0;
    max-height: 200px;
    overflow: auto;
    font-family: var(--font-mono);
    font-size: .75rem;
    color: var(--text-secondary);
    white-space: pre-wrap;
}
.program-trigger {
    display: flex; gap: .5rem; align-items: center;
}
.period-warning {
    font-family: var(--font-mono);
    font-size: .72rem;
    margin-top: .35rem;
    line-height: 1.4;
}
.period-warning.muted { color: var(--text-muted); }
.period-warning.bad   {
    color: #dc2626;
    background: rgba(220, 38, 38, .08);
    padding: .25rem .5rem;
    border-radius: 4px;
    border: 1px solid rgba(220, 38, 38, .25);
}
.program-steps {
    display: flex; flex-direction: column;
    gap: .5rem;
    margin-bottom: .5rem;
}
.program-add-step {
    display: flex; gap: .5rem; align-items: center;
    margin-top: .5rem;
}

.step-card {
    background: var(--bg);
    border: 1px solid var(--bd-soft);
    border-left: 3px solid var(--accent-2);
    border-radius: var(--radius-sm, .35rem);
    padding: .5rem .75rem;
}
.step-card-head {
    display: flex; align-items: center; gap: .5rem;
    margin-bottom: .35rem;
}
.step-card-body { display: flex; flex-direction: column; gap: .35rem; }
.step-field { display: flex; flex-direction: column; gap: .2rem; }
.step-field > label {
    font-family: var(--font-mono);
    font-size: .7rem;
    color: var(--text-muted);
    text-transform: uppercase;
}
.step-arg-row { display: flex; gap: .4rem; align-items: flex-start; }
.step-arg-row > div { flex: 1 1 auto; }

.expr-card {
    background: var(--bg-card);
    border: 1px solid var(--bd-soft);
    border-radius: 4px;
    padding: .35rem .5rem;
}
.expr-head {
    display: flex; align-items: center; gap: .4rem; flex-wrap: wrap;
}
.expr-body {
    display: flex; align-items: center; gap: .4rem; flex-wrap: wrap;
}
.expr-card .expr-card { /* nested */
    background: var(--bg);
}

/* Import-from-other-device modal */
.import-row {
    display: flex; align-items: center; gap: var(--space-md);
    padding: .35rem .55rem;
    border-top: 1px solid var(--bd-soft);
    cursor: pointer;
}
.import-row:first-of-type { border-top: 0; }
.import-row:hover         { background: var(--bg); }
.import-row-name {
    font-family: var(--font-mono);
    font-weight: 600;
    color: var(--text-primary);
    min-width: 12em;
}

/* ---- Alarms (Status tab) ------------------------------------ */
.alarms-card {
    background: var(--bg-card);
    border: 1px solid var(--bd-soft);
    border-left: 4px solid #dc2626;
    border-radius: var(--radius-md);
    padding: var(--space-md) var(--space-lg);
    margin-bottom: var(--space-md);
}
.alarms-head {
    display: flex; align-items: center; gap: var(--space-md);
    margin-bottom: .5rem;
}
.alarms-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .35rem; }
.alarms-row {
    display: flex; align-items: flex-start; gap: var(--space-md);
    padding: .5rem .65rem;
    border: 1px solid var(--bd-soft);
    border-left: 3px solid var(--bd-mid);
    border-radius: 4px;
    background: var(--bg);
}
.alarms-row.tone-bad  { border-left-color: #dc2626; background: rgba(220, 38, 38, .08); }
.alarms-row.tone-warn { border-left-color: #d97706; background: rgba(217, 119, 6, .08); }
.alarms-row.tone-info { border-left-color: var(--accent-2); }
.alarms-sev {
    font-family: var(--font-mono);
    font-size: .65rem;
    text-transform: uppercase;
    letter-spacing: .05em;
    padding: .1rem .4rem;
    border-radius: 3px;
    background: var(--bd-soft);
    color: var(--text-muted);
    flex-shrink: 0;
}
.alarms-row.tone-bad  .alarms-sev { background: #dc2626; color: white; }
.alarms-row.tone-warn .alarms-sev { background: #d97706; color: white; }
.alarms-body { flex: 1 1 auto; min-width: 0; }
.alarms-name { font-size: .85rem; font-weight: 600; color: var(--text-primary); }
.alarms-msg  { font-size: .8rem; color: var(--text-secondary); margin-top: .15rem; }
.alarms-meta { font-size: .7rem; margin-top: .15rem; }

/* Fleet alarms (Dashboard) */
.fleet-alarms-card { border-left: 4px solid #dc2626; margin-bottom: var(--space-md); }
.fleet-alarms-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .35rem; }
.fleet-alarms-row {
    display: grid;
    grid-template-columns: minmax(120px, 1fr) auto minmax(0, 2fr) auto;
    align-items: center;
    gap: var(--space-md);
    padding: .35rem .55rem;
    border-left: 3px solid var(--bd-mid);
    border-radius: 4px;
}
.fleet-alarms-row.tone-bad  { border-left-color: #dc2626; background: rgba(220, 38, 38, .06); }
.fleet-alarms-row.tone-warn { border-left-color: #d97706; background: rgba(217, 119, 6, .06); }
.fleet-alarms-row.tone-info { border-left-color: var(--accent-2); }
.fleet-alarms-device { font-weight: 600; color: var(--accent); text-decoration: none; }
.fleet-alarms-device:hover { text-decoration: underline; }
.fleet-alarms-name { color: var(--text-primary); }
.fleet-alarms-msg  { color: var(--text-secondary); font-size: .85rem; }

/* ---- responsive ---------------------------------------------- */
@media (max-width: 640px) {
    .site-header { padding: 0 var(--space-md); }
    main         { padding: var(--space-lg) var(--space-md); }
    .brand-sub   { display: none; }
    .device-head h2 { font-size: 1.2rem; }
    nav.tabs { overflow-x: auto; }
}

/* ---- Easy-mode rule builder ---------------------------------------
 * The Easy editor reuses .cfg-card / .cfg-rows / .switch primitives,
 * so this block only adds the layouts unique to Easy: the mode bar,
 * the When row, the Then-list rows, and the template gallery tiles. */
.easy-mode-bar {
    display: flex;
    align-items: center;
    gap: .5rem;
    margin-bottom: .75rem;
    padding: .55rem .75rem;
    border: 1px solid var(--bd-mid);
    border-radius: 6px;
    background: var(--bg-card);
}
.easy-mode-bar .easy-mode-label { font-size: .9rem; }

.easy-when-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
    padding: .5rem 0;
}
.easy-when-row .easy-when-label {
    font-weight: 600;
    text-transform: uppercase;
    font-size: .75rem;
    letter-spacing: .05em;
    color: var(--text-muted);
}
.easy-when-row select, .easy-when-row input[type="number"] {
    padding: .3rem .5rem;
}
.easy-when-row .easy-when-value { width: 7em; }
.easy-when-row .easy-when-period { width: 8em; }
.easy-when-row .easy-when-unit { font-size: .85rem; }

.easy-then > .easy-then-row + .easy-then-row { margin-top: .5rem; }
.easy-then-row {
    border: 1px solid var(--bd-soft);
    border-radius: 6px;
    background: var(--bg-card);
    padding: .5rem .75rem;
}
.easy-then-head {
    display: flex;
    align-items: center;
    gap: .5rem;
    margin-bottom: .35rem;
}
.easy-then-head .easy-then-kind { padding: .25rem .5rem; }
.easy-then-body {
    display: flex;
    flex-direction: column;
    gap: .35rem;
    padding-left: .25rem;
}

.easy-field-row {
    display: flex;
    align-items: center;
    gap: .5rem;
    flex-wrap: wrap;
}
.easy-field-row .easy-field-label {
    min-width: 8.5rem;
    font-size: .85rem;
    color: var(--text-muted);
}
.easy-field-row .easy-field-control {
    display: flex;
    align-items: center;
    gap: .5rem;
    flex-wrap: wrap;
}
.easy-field-row input[type="text"]   { min-width: 14rem; padding: .25rem .5rem; }
.easy-field-row input[type="number"] { width: 7em;       padding: .25rem .5rem; }
.easy-field-row select               { padding: .25rem .5rem; }
.easy-field-row .easy-slider         { flex: 1; min-width: 12rem; max-width: 22rem; }
.easy-field-row .easy-slider-value   { min-width: 2.5em; text-align: right; }

.easy-then-add {
    display: flex;
    align-items: center;
    gap: .5rem;
    margin-top: .75rem;
    padding-top: .75rem;
    border-top: 1px dashed var(--bd-soft);
}
.easy-then-add .easy-then-add-kind { padding: .3rem .5rem; }

/* ---- English preview ---- */
.easy-preview {
    font-family: var(--font-sans);
    font-size: .9rem;
    line-height: 1.5;
    padding: .5rem .25rem;
}
.easy-preview .easy-preview-name        { margin-bottom: .35rem; font-size: .75rem; }
.easy-preview .easy-preview-when        { margin-bottom: .35rem; }
.easy-preview .easy-preview-then-label  { font-weight: 600; margin: .35rem 0 .15rem; }
.easy-preview ol.easy-preview-then      { margin: 0; padding-left: 1.2rem; }
.easy-preview ol.easy-preview-then li   { margin: .1rem 0; }

/* ---- Template gallery dialog ---- */
.program-template-gallery {
    width: min(720px, 95vw);
    padding: 1rem 1.25rem 1.25rem;
}
.program-template-gallery .template-gallery-head {
    display: flex; align-items: center; gap: .5rem; margin-bottom: .5rem;
}
.template-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: .75rem;
    margin-top: .75rem;
}
.template-tile {
    display: flex;
    flex-direction: column;
    text-align: left;
    gap: .35rem;
    padding: .75rem;
    border: 1px solid var(--bd-mid);
    border-radius: 8px;
    background: var(--bg-card);
    cursor: pointer;
    font: inherit;
    color: inherit;
    transition: border-color .15s, background .15s;
}
.template-tile:hover, .template-tile:focus-visible {
    border-color: var(--accent);
    background: var(--accent-dim);
    outline: none;
}
.template-tile .template-tile-title {
    font-weight: 600;
    font-size: .95rem;
}
.template-tile .template-tile-desc {
    font-size: .8rem;
    color: var(--text-muted);
    line-height: 1.4;
}

/* ---- Save-progress dialog (program PUT + push + readback verify) ---- */
.program-save-dialog {
    width: min(480px, 95vw);
    padding: 1rem 1.25rem 1.25rem;
}
.program-save-dialog .program-save-head {
    display: flex; align-items: center; gap: .5rem; margin-bottom: .75rem;
}
.program-save-dialog .program-save-head h3 { margin: 0; }
ol.save-steps {
    list-style: none;
    margin: .25rem 0 .75rem;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: .55rem;
}
.save-step {
    display: flex;
    align-items: flex-start;
    gap: .65rem;
    padding: .55rem .65rem;
    border: 1px solid var(--bd-mid);
    border-radius: 8px;
    background: var(--bg-card);
}
.save-step-icon {
    flex: 0 0 18px;
    width: 18px;
    height: 18px;
    margin-top: .15rem;
    border-radius: 50%;
    border: 2px solid var(--bd-strong);
    background: transparent;
    position: relative;
    box-sizing: border-box;
}
.save-step-body { flex: 1 1 auto; min-width: 0; }
.save-step-title { font-weight: 600; font-size: .9rem; }
.save-step-detail {
    font-size: .8rem;
    line-height: 1.35;
    margin-top: .15rem;
    white-space: pre-wrap;
    word-wrap: break-word;
}
/* Pending: dotted ring */
.save-step[data-state="pending"] .save-step-icon {
    border-style: dotted;
    border-color: var(--bd-mid);
}
/* Active: spinner */
.save-step[data-state="active"] .save-step-icon {
    border-color: var(--accent);
    border-top-color: transparent;
    animation: save-step-spin 0.9s linear infinite;
}
@keyframes save-step-spin { to { transform: rotate(360deg); } }
/* Success: filled tick */
.save-step[data-state="ok"] .save-step-icon {
    background: var(--ok, #4caf50);
    border-color: var(--ok, #4caf50);
}
.save-step[data-state="ok"] .save-step-icon::after {
    content: "";
    position: absolute;
    left: 3px; top: 0;
    width: 5px; height: 9px;
    border: solid #fff;
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}
/* Warn: filled amber circle with ! */
.save-step[data-state="warn"] .save-step-icon {
    background: var(--warn, #ffb300);
    border-color: var(--warn, #ffb300);
}
.save-step[data-state="warn"] .save-step-icon::after {
    content: "!";
    position: absolute;
    inset: 0;
    color: #000;
    font-weight: 700;
    font-size: 12px;
    line-height: 14px;
    text-align: center;
}
/* Error: filled red circle with cross */
.save-step[data-state="err"] .save-step-icon {
    background: var(--err, #e53935);
    border-color: var(--err, #e53935);
}
.save-step[data-state="err"] .save-step-icon::after {
    content: "\00d7";
    position: absolute;
    inset: 0;
    color: #fff;
    font-weight: 700;
    font-size: 16px;
    line-height: 14px;
    text-align: center;
}
/* Skipped: dashed grey ring */
.save-step[data-state="skip"] .save-step-icon {
    border-style: dashed;
    border-color: var(--bd-mid);
    opacity: .6;
}
.save-step[data-state="skip"] .save-step-title,
.save-step[data-state="skip"] .save-step-detail { opacity: .55; }

.program-save-dialog .save-summary {
    margin: .5rem 0 .75rem;
    font-size: .85rem;
    min-height: 1.2em;
}
.program-save-dialog .save-summary.is-ok    { color: var(--ok,   #4caf50); }
.program-save-dialog .save-summary.is-warn  { color: var(--warn, #ffb300); }
.program-save-dialog .save-summary.is-err   { color: var(--err,  #e53935); }
