/* ═══════════════════════════════════════════════════════════════════════════
   DK Productions Timelapse Platform — Design System
   ═══════════════════════════════════════════════════════════════════════════

   Single stylesheet for both APP_HTML (the authenticated SPA) and LOGIN_HTML.
   Light mode is the default; [data-theme="dark"] on <html> flips into dark.

   Conventions:
     - All colour/shadow/radius values come from CSS variables. Never hardcode.
     - All interactive elements get a visible :focus-visible ring.
     - Typography is Geist (sans) + Geist Mono — Google Fonts, OFL.
     - No uppercase/widely-spaced labels. Sentence case, calm rhythm.

   Layering order (top → bottom of this file):
     1. Font import
     2. Design tokens (:root = light, [data-theme="dark"] = dark)
     3. Legacy-token aliases (transitional — removed in PR 2 after all views migrate)
     4. Reset + base
     5. Typography utilities
     6. Layout primitives (.app-shell, sidebar, topbar, content)
     7. Responsive rules
     8. Components (buttons, cards, inputs, badges, status-dot, tables, modal,
        empty-state, toast)
     9. Projects list (the one converted view)
    10. Theme toggle

   ═══════════════════════════════════════════════════════════════════════════ */

/* Bunny mirror of Geist + Geist Mono — GDPR-friendly (no Google tracking).
   Matches the design-system intent at docs/design-system.md "Where it lives";
   prior `fonts.googleapis.com` import was drift, replaced 2026-04-29. */
@import url('https://fonts.bunny.net/css?family=geist:300,400,500,600,700|geist-mono:400,500,600&display=swap');

/* ── 2. DESIGN TOKENS ───────────────────────────────────────────────────── */

:root {
  /* Surfaces — "Site Notes" pastel-construction palette.
     Three-step value ladder, all warm (no cool grays):
       --bg       = page    (paper)
       --surface  = card    (bone)
       --surface-2= hover   (canvas)
     --border is a warm rule line, not gray. Cards lean on the
     border; shadows are reserved for floating things. */
  --bg:           #ede5ce;   /* aged paper, ~5% darker than #f4eee2 */
  --surface:      #f6efdc;   /* card surface, still pops vs the darker bg */
  --surface-2:    #e6dcbc;   /* hover/rail */
  --border:       #c8b990;   /* warm rule, deepened to stay visible */
  --border-hover: #a89870;   /* rule-strong */

  /* Text — warm graphite, not pure black. --text-faint bumped from #9c9384
     to #7d745f on 2026-04-29 to clear WCAG AA contrast at small mono sizes
     (most --text-faint consumers are 10-12px labels — date-meta, "ago"
     timestamps, file-size meta — which fail AA at the previous value). */
  --text:         #2a2620;
  --text-dim:     #6b6256;
  --text-faint:   #7d745f;

  /* Primary — faded clay (replaces indigo) */
  --primary:       #c46a45;
  --primary-hover: #a85730;
  --primary-soft:  #f0d9c6;

  /* Semantic — pastel construction (sun-washed hi-vis) */
  --warn:         #d4943f;
  --warn-soft:    #f5e2c0;
  --success:      #87a06b;
  --success-soft: #d8e2c8;
  --danger:       #b25a4d;
  --danger-soft:  #ebcfc8;

  /* Shadows — retinted from cool slate to warm graphite
     (rgba(42,38,32,…)) so hover lifts read warm against the paper,
     not as a grey fog. */
  --shadow-sm: 0 1px 3px 0 rgba(42,38,32,0.06);
  --shadow:    0 1px 3px 0 rgba(42,38,32,0.08),
               0 2px 6px -1px rgba(42,38,32,0.06);
  --shadow-md: 0 4px 12px -2px rgba(42,38,32,0.10),
               0 2px 6px -2px rgba(42,38,32,0.06);

  /* Radii */
  --radius-sm: 4px;
  --radius:    8px;
  --radius-md: 12px;

  /* Typography scale.
     Adjacent steps (xs↔sm, sm↔base, base↔lg) sit at 13–17% — below the
     ~25% threshold from Design for Hackers Ch 7 ("Type size changes are
     proportionally significant"). Reach for *non-adjacent* steps when
     using size to establish hierarchy: e.g. xs ↔ base (33%) or sm ↔ xl
     (71%), not sm ↔ base (14%). The token names exist so consumers can
     pick a register, not so consumers can stack adjacent sizes for
     visual rank. If you find yourself reaching for both --text-sm and
     --text-base in the same hierarchy, you want register variation
     (mono caps / weight / color) instead of size variation. */
  --text-xs:   0.75rem;
  --text-sm:   0.875rem;
  --text-base: 1rem;
  --text-lg:   1.125rem;
  --text-xl:   1.5rem;
  --text-2xl:  1.875rem;

  /* Font stacks — Geist + Geist Mono (Google Fonts, OFL) */
  --font-sans: 'Geist', -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI",
               Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-mono: 'Geist Mono', ui-monospace, "JetBrains Mono", "SF Mono", Menlo,
               Monaco, Consolas, monospace;
}

[data-theme="dark"] {
  --bg:           #1a1714;   /* warm near-black, not pure */
  --surface:      #211d18;   /* card, slightly elevated */
  --surface-2:    #2a251f;   /* hover/rail */
  --border:       #2c2820;
  --border-hover: #3a352d;

  --text:         #fbf6ec;   /* bone */
  --text-dim:     #b8ad97;   /* bumped 2026-04-29 to clear WCAG AA at small mono sizes */
  --text-faint:   #8a8170;   /* bumped 2026-04-29 to match docs/design-system.md spec */

  --primary:       #d27a55;  /* clay brightened for dark surround */
  --primary-hover: #de8866;
  --primary-soft:  #3a2419;  /* very dark clay tint, sits behind clay text */

  --warn:         #e0a04a;
  --warn-soft:    #3a2c14;
  --success:      #94ae77;
  --success-soft: #2a3a22;
  --danger:       #c06a5d;
  --danger-soft:  #3a1c18;

  --shadow-sm: 0 1px 2px 0 rgba(0,0,0,0.4);
  --shadow:    0 1px 3px 0 rgba(0,0,0,0.5), 0 1px 2px -1px rgba(0,0,0,0.3);
  --shadow-md: 0 4px 6px -1px rgba(0,0,0,0.5), 0 2px 4px -2px rgba(0,0,0,0.3);
}

/* ── 3. RESET + BASE ────────────────────────────────────────────────────── */

*, *::before, *::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html, body {
  min-height: 100vh;
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  /* 1.45 sits inside the appendix-recommended 1.2-1.4 band for body copy
     and stays appropriately tight for the instrument-panel mood. Prose
     blocks (.empty-state-body, .page-subtitle, etc.) override locally. */
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* Geist stylistic alternates: ss01 = single-storey 'a' (cleaner technical look),
     cv11 = slashed zero in mono fallback contexts. */
  font-feature-settings: "ss01", "cv11";
  transition: background-color 120ms ease, color 120ms ease;
}

/* Tabular numerics anywhere mono is used — locks numbers into columns,
   which is the whole point of mono in an instrument-panel context. */
.mono, code, kbd, pre,
[class*="num"], [class*="numeric"],
.badge { font-variant-numeric: tabular-nums; }

a {
  color: var(--primary);
  text-decoration: none;
  transition: color 120ms ease;
}
a:hover { color: var(--primary-hover); }

button { font-family: inherit; }

img { max-width: 100%; display: block; }

/* Global focus ring — applied to every interactive element via
   :focus-visible so mouse users aren't bothered by it. */
*:focus { outline: none; }
*:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* ── 5. TYPOGRAPHY UTILITIES ────────────────────────────────────────────── */

.mono { font-family: var(--font-mono); }

.text-xs   { font-size: var(--text-xs); }
.text-sm   { font-size: var(--text-sm); }
.text-base { font-size: var(--text-base); }
.text-lg   { font-size: var(--text-lg); }
.text-xl   { font-size: var(--text-xl); }
.text-2xl  { font-size: var(--text-2xl); }

.text-dim   { color: var(--text-dim); }
.text-faint { color: var(--text-faint); }

/* ── 6. LAYOUT PRIMITIVES ───────────────────────────────────────────────── */

.app-shell {
  display: grid;
  grid-template-columns: 240px 1fr;
  min-height: 100vh;
}

/* Sidebar — sticky to the top of the viewport so it stays in view as the
   main column scrolls. align-self:start keeps the sticky-context valid in
   the grid (without it, grid stretches the cell and there's no room to
   stick). The sidebar gets its own internal scroll if its content
   overflows 100vh. Mobile (≤767px) overrides this with position:fixed —
   see the @media block in section 7. */
.app-sidebar {
  background: var(--surface);
  border-right: 1px solid var(--border);
  padding: 1.5rem 1rem;
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  align-self: start;
  height: 100vh;
  overflow-y: auto;
}

.app-sidebar-brand {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 2rem;
  padding: 0 0.25rem;
  cursor: pointer;
  color: inherit;
}
.app-sidebar-brand img {
  height: 32px;
  width: auto;
}
.app-sidebar-brand .wordmark {
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.01em;
}

/* Nav */
.app-nav {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  flex: 1;
}
.app-nav-item {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.55rem 0.75rem;
  border-radius: var(--radius);
  color: var(--text-dim);
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease;
  background: none;
  border: 0;
  text-align: left;
  width: 100%;
}
.app-nav-item:hover {
  background: var(--surface-2);
  color: var(--text);
}
.app-nav-item.active {
  background: var(--primary-soft);
  color: var(--primary);
}
.app-nav-item .nav-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  stroke-width: 1.75;
}

.app-sidebar-footer {
  border-top: 1px solid var(--border);
  padding-top: 1rem;
  margin-top: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

/* Main column */
.app-main {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

/* Topbar */
.app-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 2rem;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
  position: sticky;
  top: 0;
  z-index: 10;
  gap: 1rem;
}

.app-topbar-left {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex: 1;
  min-width: 0;
}

.app-topbar-right {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

/* Breadcrumbs (replaces legacy #breadcrumb id contents) */
.breadcrumbs {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: var(--text-sm);
  flex-wrap: wrap;
}
.breadcrumbs a {
  color: var(--text-dim);
  text-decoration: none;
  transition: color 120ms ease;
}
.breadcrumbs a:hover { color: var(--text); }
.breadcrumbs .sep,
.breadcrumbs-sep {
  color: var(--text-faint);
}
.breadcrumbs-current,
.breadcrumbs > span:last-child {
  color: var(--text);
  font-weight: 500;
}

.user-badge {
  font-size: var(--text-xs);
  color: var(--text-dim);
  font-family: var(--font-mono);
}

/* Content area */
.app-content {
  flex: 1;
  padding: 2rem;
  width: 100%;
  /* No max-width cap — the natural responsive grids inside (gallery,
     instrument-grid, project-grid, etc.) all use auto-fill minmax(),
     so they fill the viewport sensibly without a fixed ceiling. The
     1400px cap was leaving 800-900px of empty rail on wide monitors. */
}

/* Sidebar toggle (hidden on desktop, shown on mobile) */
.sidebar-toggle {
  display: none;
  background: none;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  width: 36px;
  height: 36px;
  color: var(--text);
  cursor: pointer;
  font-size: 1.1rem;
  line-height: 1;
  align-items: center;
  justify-content: center;
}
.sidebar-toggle:hover { background: var(--surface-2); }

/* Page header (used inside .app-content by converted views) */
.page-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 2rem;
}
/* .page-title and .page-subtitle live with the Section-11+ overrides at the
   bottom of this file (search "Page header pattern"). Pre-Section-11 versions
   were removed 2026-04-30 to kill the dual-truth confusion flagged in the
   design audit (M3). */

/* ── 7. RESPONSIVE ──────────────────────────────────────────────────────── */

@media (max-width: 767px) {
  .app-shell {
    grid-template-columns: 1fr;
  }
  .app-sidebar {
    position: fixed;
    left: -240px;
    top: 0;
    bottom: 0;
    width: 240px;
    transition: left 200ms ease;
    z-index: 20;
  }
  .app-sidebar.open { left: 0; }
  .app-content { padding: 1rem; }
  .app-topbar { padding: 0.75rem 1rem; }
  .sidebar-toggle { display: inline-flex; }
  .app-topbar-right .btn { padding: 0.4rem 0.7rem; font-size: var(--text-xs); }
}

/* Scrim behind open mobile sidebar */
.sidebar-scrim {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.4);
  z-index: 15;
}
.sidebar-scrim.open { display: block; }

/* ── 8. COMPONENTS ─────────────────────────────────────────────────────── */

/* Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.55rem 1rem;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: 600;
  line-height: 1.2;
  border: 1px solid transparent;
  border-radius: var(--radius);
  cursor: pointer;
  transition: background-color 120ms ease, border-color 120ms ease,
              color 120ms ease, box-shadow 120ms ease;
  text-decoration: none;
  white-space: nowrap;
}
.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.btn-sm {
  padding: 0.4rem 0.75rem;
  font-size: var(--text-xs);
}
.btn-lg {
  padding: 0.75rem 1.25rem;
  font-size: var(--text-base);
}

.btn-primary {
  background: var(--primary);
  color: #ffffff;
  border-color: var(--primary);
}
.btn-primary:hover:not(:disabled) {
  background: var(--primary-hover);
  border-color: var(--primary-hover);
  color: #ffffff;
}

.btn-secondary {
  background: var(--surface);
  color: var(--text);
  border-color: var(--border);
}
.btn-secondary:hover:not(:disabled) {
  background: var(--surface-2);
  border-color: var(--border-hover);
}

.btn-danger {
  background: var(--danger);
  color: #ffffff;
  border-color: var(--danger);
}
.btn-danger:hover:not(:disabled) {
  filter: brightness(0.92);
}

/* Hi-vis warn variant — used on disruptive but non-destructive actions
   (e.g. station Reboot). Same visual weight as primary/danger so the
   three risk colors line up consistently in a button row. */
.btn-warn {
  background: var(--warn);
  color: #2c1d08;
  border-color: var(--warn);
  font-weight: 600;
}
.btn-warn:hover:not(:disabled) {
  filter: brightness(0.94);
}

.btn-ghost {
  background: transparent;
  color: var(--text-dim);
  border-color: transparent;
}
.btn-ghost:hover:not(:disabled) {
  background: var(--surface-2);
  color: var(--text);
}

/* Cards */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  transition: border-color 120ms ease, box-shadow 120ms ease,
              transform 120ms ease;
}
.card-interactive {
  cursor: pointer;
}
.card-interactive:hover {
  border-color: var(--border-hover);
  box-shadow: var(--shadow);   /* noticeable lift, not dramatic */
  transform: translateY(-1px);
}
.card-header {
  padding: 1rem 1.25rem;
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.card-header h2, .card-header h3 {
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--text);
}
.card-body {
  padding: 1.25rem;
}
.card-footer {
  padding: 0.875rem 1.25rem;
  border-top: 1px solid var(--border);
  background: var(--surface-2);
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
}

/* Form controls */
.form-field {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  margin-bottom: 1rem;
}
.form-field label {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--text);
}
.form-hint {
  font-size: var(--text-xs);
  color: var(--text-dim);
}

.input, .select, .textarea {
  width: 100%;
  padding: 0.55rem 0.75rem;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  transition: border-color 120ms ease, box-shadow 120ms ease;
}
.input:hover, .select:hover, .textarea:hover {
  border-color: var(--border-hover);
}
.input:focus, .select:focus, .textarea:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 3px var(--primary-soft);
}
.input::placeholder, .textarea::placeholder {
  color: var(--text-faint);
}
.textarea {
  min-height: 80px;
  resize: vertical;
}

/* Badges */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.2rem 0.55rem;
  font-size: var(--text-xs);
  font-weight: 600;
  line-height: 1.2;
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--text-dim);
}
.badge-primary { background: var(--primary-soft); color: var(--primary); }
.badge-success { background: var(--success-soft); color: var(--success); }
.badge-warn    { background: var(--warn-soft);    color: var(--warn);    }
.badge-danger  { background: var(--danger-soft);  color: var(--danger);  }

/* Status dot (small colored circle for online/offline) */
.status-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--text-faint);
  flex-shrink: 0;
}
.status-dot.online  { background: var(--success); }
.status-dot.offline { background: var(--danger); }
.status-dot.warning { background: var(--warn); }
.status-dot.online::after {
  content: '';
  display: block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--success);
  opacity: 0.4;
  animation: status-pulse 2s infinite;
}
@keyframes status-pulse {
  0%   { transform: scale(1);   opacity: 0.4; }
  100% { transform: scale(2.5); opacity: 0;   }
}

/* Tables */
.table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
}
.table thead th {
  text-align: left;
  padding: 0.6rem 0.75rem;
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--text-dim);
  border-bottom: 1px solid var(--border);
  background: var(--surface-2);
}
.table tbody td {
  padding: 0.7rem 0.75rem;
  border-bottom: 1px solid var(--border);
  color: var(--text);
}
.table tbody tr:last-child td { border-bottom: 0; }
.table tbody tr:hover { background: var(--surface-2); }

/* Modal — overlay + content. Backward compatible with existing
   .modal-bg + .modal structure so openModal() keeps working.
   z-index 1100 sits above Leaflet's control panes (default ~800-1000),
   so modals render on top of any open map. Audit 2026-04-30: members
   + other modals were rendering behind map controls. */
.modal-bg {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.5);
  z-index: 1100;
  align-items: center;
  justify-content: center;
  padding: 1rem;
}
.modal-bg.open { display: flex; }
.modal {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  width: 440px;
  max-width: 100%;
  max-height: calc(100vh - 2rem);
  overflow: auto;
}
.modal-header {
  padding: 1.25rem 1.5rem 0.75rem;
}
.modal-title {
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--text);
}
.modal-body {
  padding: 0.75rem 1.5rem 1rem;
}
.modal-footer {
  padding: 0.75rem 1.5rem 1.25rem;
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
}

/* Empty state */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 3rem 1.5rem;
  background: var(--surface);
  border: 1px dashed var(--border);
  border-radius: var(--radius-md);
  color: var(--text-dim);
  gap: 0.5rem;
}
.empty-state-icon {
  width: 48px;
  height: 48px;
  color: var(--text-faint);
  margin-bottom: 0.5rem;
}
.empty-state-title {
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--text);
}
.empty-state-body {
  font-size: var(--text-sm);
  color: var(--text-dim);
  max-width: 400px;
  margin-bottom: 1rem;
}

/* Toast (inherits legacy #toast behaviour — restyled) */
.toast {
  position: fixed;
  bottom: 2rem;
  right: 2rem;
  background: var(--text);
  color: var(--bg);
  font-size: var(--text-sm);
  font-weight: 500;
  padding: 0.7rem 1rem;
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 180ms ease, transform 180ms ease;
  z-index: 1500;  /* above modals (1100) and lightbox (1200) */
  pointer-events: none;
}
.toast.show {
  opacity: 1;
  transform: translateY(0);
}

/* What's-new banner — sticky-but-dismissible bottom-right notification.
   Distinct from .toast so it doesn't auto-vanish. */
.whatsnew-banner {
  position: fixed;
  bottom: 1.25rem;
  right: 1.25rem;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.5rem 0.4rem 0.5rem 1rem;
  box-shadow: var(--shadow-md);
  display: none;
  align-items: center;
  gap: 0.6rem;
  font-size: var(--text-sm);
  z-index: 90;
  max-width: calc(100vw - 2rem);
}
.whatsnew-banner.open { display: inline-flex; }
.whatsnew-banner .wn-spark { font-size: 1.05em; }
.whatsnew-banner .wn-msg { color: var(--text-dim); }
.whatsnew-banner .wn-cta {
  background: var(--primary);
  color: #fff;
  border: 0;
  padding: 0.4rem 0.9rem;
  border-radius: 999px;
  font-size: var(--text-xs);
  font-weight: 600;
  cursor: pointer;
  font-family: inherit;
}
.whatsnew-banner .wn-cta:hover { filter: brightness(1.08); }
.whatsnew-banner .wn-x {
  background: transparent;
  border: 0;
  color: var(--text-faint);
  font-size: 1.3rem;
  line-height: 1;
  padding: 0 0.4rem;
  cursor: pointer;
}
.whatsnew-banner .wn-x:hover { color: var(--text); }
@media (max-width: 600px) {
  .whatsnew-banner { right: 0.5rem; left: 0.5rem; bottom: 0.5rem; padding-left: 0.7rem; }
  .whatsnew-banner .wn-cta { padding: 0.4rem 0.7rem; }
}
.whatsnew-list { padding-left: 1.2rem; margin: 0.4rem 0; }
.whatsnew-list li { margin-bottom: 0.65rem; line-height: 1.45; }
.whatsnew-list li:last-child { margin-bottom: 0; }

/* ── 9. PROJECTS LIST (the one converted view) ─────────────────────────── */

.projects-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 1.25rem;
}

.project-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  cursor: pointer;
  transition: border-color 120ms ease, box-shadow 180ms ease,
              transform 180ms ease;
  display: flex;
  flex-direction: column;
}
.project-card:hover {
  border-color: var(--border-hover);
  box-shadow: var(--shadow);   /* noticeable lift, not dramatic */
  transform: translateY(-2px);
}

.project-card-cover {
  aspect-ratio: 16/9;
  background: var(--surface-2);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-faint);
  overflow: hidden;
}
.project-card-cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.project-card-cover svg {
  width: 36px;
  height: 36px;
}

.project-card-body {
  padding: 1.1rem 1.25rem 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  flex: 1;
}
.project-card-title {
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.01em;
}
.project-card-desc {
  font-size: var(--text-sm);
  color: var(--text-dim);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  line-height: 1.4;
  min-height: 2.45em;
}
.project-card-meta {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
  margin-top: auto;
  padding-top: 0.6rem;
  font-size: var(--text-xs);
  color: var(--text-dim);
}
.project-card-stat {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}
.project-card-stat svg {
  width: 14px;
  height: 14px;
  stroke-width: 2;
  color: var(--text-faint);
}
.project-card-stat-value {
  color: var(--text);
  font-weight: 600;
}

/* Create-project card (dashed ghost tile matching the grid) */
.project-create-card {
  background: var(--surface);
  border: 2px dashed var(--border);
  border-radius: var(--radius-md);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 240px;
  cursor: pointer;
  color: var(--text-dim);
  transition: border-color 120ms ease, background-color 120ms ease,
              color 120ms ease;
  gap: 0.4rem;
}
.project-create-card:hover {
  border-color: var(--primary);
  background: var(--primary-soft);
  color: var(--primary);
}
.project-create-card svg {
  width: 28px;
  height: 28px;
  stroke-width: 1.75;
}

/* ── 10. THEME TOGGLE (sidebar footer) ─────────────────────────────────── */

.theme-toggle {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.55rem 0.75rem;
  border-radius: var(--radius);
  color: var(--text-dim);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
  background: none;
  border: 0;
  text-align: left;
  width: 100%;
  transition: background-color 120ms ease, color 120ms ease;
}
.theme-toggle:hover {
  background: var(--surface-2);
  color: var(--text);
}
.theme-toggle .nav-icon { width: 18px; height: 18px; flex-shrink: 0; }
.theme-toggle .icon-moon { display: none; }
[data-theme="dark"] .theme-toggle .icon-sun  { display: none; }
[data-theme="dark"] .theme-toggle .icon-moon { display: block; }

/* ── 11. LOGIN PAGE ────────────────────────────────────────────────────── */

.login-screen {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  background: var(--bg);
}
.login-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  padding: 2.25rem 2rem;
  width: 400px;
  max-width: 100%;
}
.login-brand {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 1.5rem;
}
.login-brand img { height: 36px; width: auto; }
.login-brand .wordmark {
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.01em;
}
.login-title {
  font-size: var(--text-xl);
  font-weight: 700;
  color: var(--text);
  margin-bottom: 0.4rem;
  letter-spacing: -0.02em;
}
.login-sub {
  font-size: var(--text-sm);
  color: var(--text-dim);
  margin-bottom: 1.75rem;
}
.login-error {
  font-size: var(--text-sm);
  color: var(--danger);
  background: var(--danger-soft);
  border: 1px solid var(--danger-soft);
  padding: 0.55rem 0.75rem;
  border-radius: var(--radius-sm);
  margin-bottom: 1rem;
  display: none;
}
.login-error.show { display: block; }
.login-submit {
  width: 100%;
  margin-top: 0.5rem;
}

/* ═══════════════════════════════════════════════════════════════════════════
   PR 1 ITERATION — topbar actions, stats strip, activity panel
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 12. TOPBAR ACTIONS ─────────────────────────────────────────────────────
   Search + notifications bell + help button, on the right side of .app-topbar
   ─────────────────────────────────────────────────────────────────────────── */

.topbar-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.topbar-search {
  position: relative;
  display: flex;
  align-items: center;
  width: 280px;
  max-width: 40vw;
}
.topbar-search svg {
  position: absolute;
  left: 0.7rem;
  width: 16px;
  height: 16px;
  color: var(--text-faint);
  pointer-events: none;
  stroke-width: 2;
}
.topbar-search input {
  width: 100%;
  height: 36px;
  padding: 0 0.75rem 0 2.1rem;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid transparent;
  border-radius: var(--radius);
  transition: background-color 120ms ease, border-color 120ms ease,
              box-shadow 120ms ease;
}
.topbar-search input::placeholder { color: var(--text-faint); }
.topbar-search input:hover {
  background: var(--surface);
  border-color: var(--border);
}
.topbar-search input:focus {
  outline: none;
  background: var(--surface);
  border-color: var(--primary);
  box-shadow: 0 0 0 3px var(--primary-soft);
}

.topbar-icon-btn {
  position: relative;
  width: 36px;
  height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius);
  color: var(--text-dim);
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease,
              border-color 120ms ease;
}
.topbar-icon-btn:hover {
  background: var(--surface-2);
  color: var(--text);
}
.topbar-icon-btn svg {
  width: 18px;
  height: 18px;
  stroke-width: 1.75;
}

.topbar-badge {
  position: absolute;
  top: 2px;
  right: 2px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  font-size: 10px;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
  color: #ffffff;
  background: var(--danger);
  border-radius: 10px;
  border: 2px solid var(--surface);
  box-sizing: content-box;
}
.topbar-badge.hidden { display: none; }

/* ── 13. STATS STRIP (projects page) ───────────────────────────────────────
   Four-card at-a-glance KPI row above the project grid. Cards that have no
   data yet are simply omitted (no "—" placeholders).
   ─────────────────────────────────────────────────────────────────────────── */

.stats-strip {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1rem;
  margin-bottom: 2rem;
}
.stats-strip:empty { display: none; }

.stat-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: 1rem 1.1rem;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
.stat-card .stat-label {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.stat-card .stat-value {
  font-size: var(--text-xl);
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.02em;
  line-height: 1.15;
}
.stat-card .stat-sub {
  font-size: var(--text-xs);
  color: var(--text-faint);
}

@media (max-width: 1100px) {
  .stats-strip { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 520px) {
  .stats-strip { grid-template-columns: 1fr; }
}

/* ── 14. PROJECTS LAYOUT + ACTIVITY PANEL ──────────────────────────────────
   Two-column layout on desktop: the projects grid on the left, a recent
   activity feed on the right. Stacks on tablet and mobile.
   ─────────────────────────────────────────────────────────────────────────── */

.projects-layout {
  display: grid;
  grid-template-columns: 1fr 300px;
  gap: 1.5rem;
  align-items: start;
}
.projects-main { min-width: 0; }

.activity-panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  display: flex;
  flex-direction: column;
  /* Sticky so the feed stays visible when the projects column scrolls past
     on tall viewports. top offset matches the app-topbar height + a little. */
  position: sticky;
  top: 5rem;
  max-height: calc(100vh - 7rem);
  overflow: hidden;
}
.activity-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.9rem 1rem;
  border-bottom: 1px solid var(--border);
}
.activity-header h3 {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.01em;
}
.activity-header .btn-ghost.btn-sm {
  padding: 0.25rem 0.4rem;
  font-size: var(--text-sm);
  line-height: 1;
}
.activity-list {
  flex: 1;
  overflow-y: auto;
  padding: 0.25rem 0;
}
.activity-item {
  display: flex;
  gap: 0.7rem;
  padding: 0.65rem 1rem;
  border-bottom: 1px solid var(--border);
  font-size: var(--text-sm);
  line-height: 1.4;
}
.activity-item:last-child { border-bottom: 0; }

.activity-dot {
  flex-shrink: 0;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-top: 0.45rem;
  background: var(--text-faint);
}
.activity-dot.checkin    { background: var(--success); }
.activity-dot.command    { background: var(--primary); }
.activity-dot.enrollment { background: var(--warn); }

.activity-body {
  flex: 1;
  min-width: 0;
}
.activity-message {
  color: var(--text);
  word-wrap: break-word;
}
.activity-message a {
  color: var(--text);
  text-decoration: none;
  border-bottom: 1px dotted var(--border-hover);
}
.activity-message a:hover { color: var(--primary); border-bottom-color: var(--primary); }
.activity-time {
  font-size: var(--text-xs);
  color: var(--text-faint);
  margin-top: 0.15rem;
}

.activity-empty {
  padding: 2rem 1rem;
  text-align: center;
  font-size: var(--text-sm);
  color: var(--text-dim);
}

@media (max-width: 1100px) {
  .projects-layout {
    grid-template-columns: 1fr;
  }
  .activity-panel {
    position: static;
    max-height: none;
  }
  .activity-list {
    max-height: 400px;
  }
}

/* ── 15. TOPBAR — HIDE SEARCH + ACTIONS ON NARROW VIEWPORTS ────────────── */
@media (max-width: 767px) {
  .topbar-search { display: none; }
  .topbar-actions { gap: 0.25rem; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   PR 2a — project detail page + all modals
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 16. PROJECT DETAIL HEADER ──────────────────────────────────────────── */

.page-breadcrumb-back {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: var(--text-sm);
  color: var(--text-dim);
  text-decoration: none;
  margin-bottom: 0.6rem;
  transition: color 120ms ease;
}
.page-breadcrumb-back:hover { color: var(--primary); }
.page-breadcrumb-back svg {
  width: 14px;
  height: 14px;
  stroke-width: 2;
}

.page-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-shrink: 0;
}

/* Ghost button that reads "danger" on hover — used for "Delete project" in the
   page-actions row. Keeps visual weight low until the user commits. */
.btn-ghost.danger-text {
  color: var(--danger);
}
.btn-ghost.danger-text:hover:not(:disabled) {
  background: var(--danger-soft);
  color: var(--danger);
}

/* ── 17. STATS STRIP (project detail) ───────────────────────────────────── */

.stats-strip {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1rem;
  margin-bottom: 2rem;
}
@media (max-width: 900px) {
  .stats-strip { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 500px) {
  .stats-strip { grid-template-columns: 1fr; }
}

/* The global .stat-card (from PR 1) is already defined — we just make sure
   the project-detail variant uses the same rhythm. The card itself carries
   border/shadow/hover from .card; the inner structure is below. */
.stat-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: 1rem 1.1rem;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}
.stat-card-label {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--text-dim);
  text-transform: none;
  letter-spacing: 0;
}
.stat-card-value {
  font-size: var(--text-2xl);
  font-weight: 700;
  color: var(--text);
  letter-spacing: -0.02em;
  line-height: 1.1;
}
.stat-card-sub {
  font-size: var(--text-xs);
  color: var(--text-faint);
  margin-top: 0.1rem;
}

/* ── 18. STATIONS GRID ──────────────────────────────────────────────────── */

.stations-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 1rem;
}

.station-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  padding: 1.1rem 1.2rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  cursor: pointer;
  transition: border-color 120ms ease, box-shadow 120ms ease,
              transform 120ms ease;
  min-height: 160px;
}
.station-card:hover {
  border-color: var(--border-hover);
  box-shadow: var(--shadow);
  transform: translateY(-1px);
}

.station-card-header {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  min-width: 0;
}
.station-card-name {
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.01em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  flex: 1;
}

.station-card-meta {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  margin-top: auto;
}
.station-card-meta-item {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  font-size: var(--text-xs);
}
.station-card-meta-item .meta-label {
  color: var(--text-dim);
  flex-shrink: 0;
}
.station-card-meta-item .meta-value {
  color: var(--text);
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: calc(var(--text-xs) + 0.5px);
}

/* Create-station tile (dashed ghost — matches .project-create-card rhythm) */
.create-station-card {
  background: var(--surface);
  border: 2px dashed var(--border);
  border-radius: var(--radius-md);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 160px;
  cursor: pointer;
  color: var(--text-dim);
  transition: border-color 120ms ease, background-color 120ms ease,
              color 120ms ease;
  gap: 0.4rem;
}
.create-station-card:hover {
  border-color: var(--primary);
  background: var(--primary-soft);
  color: var(--primary);
}
.create-station-card svg {
  width: 24px;
  height: 24px;
  stroke-width: 1.75;
}
.create-station-card-label {
  font-size: var(--text-sm);
  font-weight: 500;
}

/* ── 19. ENROLLMENT MODAL FLOW ──────────────────────────────────────────── */

.enroll-intro {
  font-size: var(--text-sm);
  color: var(--text-dim);
  margin-bottom: 0.9rem;
  line-height: 1.45;
}

.enroll-code {
  display: block;
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--text-2xl);
  font-weight: 600;
  letter-spacing: 0.12em;
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.9rem 1rem;
  margin: 0.5rem 0 1rem;
  user-select: all;
  cursor: text;
}

.enroll-qr {
  display: flex;
  justify-content: center;
  margin: 0.5rem 0 1rem;
}
.enroll-qr img,
.enroll-qr svg {
  width: 200px;
  height: 200px;
  border-radius: var(--radius-sm);
  background: #ffffff;
  padding: 0.5rem;
  border: 1px solid var(--border);
}

.enroll-status {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.55rem;
  font-size: var(--text-sm);
  color: var(--text-dim);
  margin-top: 0.75rem;
}
.enroll-spinner {
  width: 14px;
  height: 14px;
  border: 2px solid var(--border);
  border-top-color: var(--primary);
  border-radius: 50%;
  animation: enroll-spin 0.8s linear infinite;
  flex-shrink: 0;
}
@keyframes enroll-spin {
  to { transform: rotate(360deg); }
}

.enroll-expiry {
  text-align: center;
  font-size: var(--text-xs);
  color: var(--text-faint);
  margin-top: 0.5rem;
}
.enroll-expiry.urgent {
  color: var(--warn);
  font-weight: 600;
}

.enroll-done-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  margin: 0 auto 1rem;
  border-radius: 50%;
  background: var(--success-soft);
  color: var(--success);
}
.enroll-done-icon svg {
  width: 32px;
  height: 32px;
  stroke-width: 2.5;
}
.enroll-done-title {
  text-align: center;
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--text);
  margin-bottom: 0.3rem;
}
.enroll-done-sub {
  text-align: center;
  font-size: var(--text-sm);
  color: var(--text-dim);
  line-height: 1.45;
}

/* ── 20. MEMBERS MODAL ──────────────────────────────────────────────────── */

.member-list {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  margin-bottom: 1rem;
}

.member-row {
  display: grid;
  grid-template-columns: 1fr 140px auto;
  align-items: center;
  gap: 0.6rem;
  padding: 0.55rem 0.75rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.member-row-name {
  font-size: var(--text-sm);
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.member-row-name .role-hint {
  display: inline-block;
  margin-left: 0.4rem;
  font-size: var(--text-xs);
  color: var(--text-faint);
  font-weight: 500;
}
.member-role-select {
  padding: 0.35rem 0.5rem;
  font-size: var(--text-xs);
}

.btn-icon-danger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  background: transparent;
  color: var(--text-faint);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: 1.1rem;
  line-height: 1;
  transition: background-color 120ms ease, color 120ms ease,
              border-color 120ms ease;
}
.btn-icon-danger:hover:not(:disabled) {
  background: var(--danger-soft);
  color: var(--danger);
  border-color: var(--danger-soft);
}

.member-add {
  /* Now hosts two add-rows separated by an "or" divider — the first
   * is "Add existing user by username", the second is "Send email
   * invitation to a non-user". Each row preserves the original
   * 1fr/140px/auto column rhythm. (2026-05-04.) */
  padding-top: 0.85rem;
  border-top: 1px solid var(--border);
  margin-top: 0.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
.member-add-row {
  display: grid;
  grid-template-columns: 1fr 140px auto;
  gap: 0.5rem;
  align-items: center;
}
.member-add-divider {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  color: var(--text-faint);
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.member-add-divider::before,
.member-add-divider::after {
  content: "";
  flex: 1;
  border-top: 1px dashed var(--border);
}

/* Pending-invitations section: same row visual as member-row but
 * tinted to read as "pending" rather than active. (2026-05-04.) */
.member-pending-section {
  margin-top: 1rem;
}
.member-section-label {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: 0.4rem;
  padding-left: 0.1rem;
}
.member-row.member-row-pending {
  background: var(--bg);
  border-style: dashed;
}

/* Phone width — invitation form rows stack so the email field
 * doesn't crush against the role select. */
@media (max-width: 480px) {
  .member-add-row {
    grid-template-columns: 1fr;
  }
}

/* ── 21. TYPE-TO-CONFIRM DESTRUCTIVE ACTIONS ────────────────────────────── */

.confirm-warning {
  font-size: var(--text-sm);
  color: var(--text);
  line-height: 1.5;
  margin-bottom: 0.9rem;
}
.confirm-warning strong {
  color: var(--danger);
  font-weight: 600;
}
.confirm-error {
  font-size: var(--text-xs);
  color: var(--danger);
  margin-top: 0.35rem;
  min-height: 1.1em;
}

/* ── 22. MEMBERS MODAL — RESPONSIVE ─────────────────────────────────────── */
@media (max-width: 500px) {
  .member-row,
  .member-add {
    grid-template-columns: 1fr;
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   PR 2b — station detail + admin + profile + lightbox
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 23. TAB NAV ────────────────────────────────────────────────────────────
   Used on the station detail page (Gallery / Status / Settings) and designed
   to be extendable when the Videos tab lands in Phase A. Replaces the legacy
   .st-tabs/.st-tab pair.
   ─────────────────────────────────────────────────────────────────────────── */

.tab-nav {
  display: flex;
  gap: 0.1rem;
  border-bottom: 1px solid var(--border);
  margin-bottom: 1.5rem;
  overflow-x: auto;
}
.tab-item {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.65rem 1rem;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--text-dim);
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  cursor: pointer;
  white-space: nowrap;
  transition: color 120ms ease, border-color 120ms ease;
  margin-bottom: -1px;
}
.tab-item:hover {
  color: var(--text);
}
.tab-item.active {
  color: var(--primary);
  border-bottom-color: var(--primary);
  font-weight: 600;
}

/* ── 24. DISK BAR (station stats-strip) ────────────────────────────────────
   Thin progress bar used inside .stat-card for disk usage. Color ramps from
   success → warn → danger at 70% / 85%. The data value sets width inline.
   ─────────────────────────────────────────────────────────────────────────── */

.disk-bar {
  width: 100%;
  height: 4px;
  background: var(--surface-2);
  border-radius: 2px;
  overflow: hidden;
  margin-top: 0.4rem;
}
.disk-bar-fill {
  height: 100%;
  background: var(--success);
  border-radius: 2px;
  transition: width 200ms ease, background-color 120ms ease;
}
.disk-bar-fill.warn   { background: var(--warn); }
.disk-bar-fill.danger { background: var(--danger); }

/* ── 25. GALLERY ─────────────────────────────────────────────────────────── */

.gallery-layout {
  display: grid;
  grid-template-columns: 220px 1fr;
  gap: 1.5rem;
  align-items: start;
}

.gallery-dates {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  position: sticky;
  top: 5rem;
  max-height: calc(100vh - 7rem);
  display: flex;
  flex-direction: column;
}
.gallery-dates-header {
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.sidebar-close {
  display: none;
  background: none;
  border: 0;
  color: var(--text-dim);
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
  padding: 0.2rem 0.4rem;
  border-radius: var(--radius-sm);
}
.sidebar-close:hover { background: var(--surface-2); color: var(--text); }

.date-list {
  flex: 1;
  overflow-y: auto;
  padding: 0.25rem 0;
}

/* Month grouping in the date sidebar (added 2026-05-04). The newest
 * month renders expanded; older months collapse so the sidebar
 * doesn't become a wall on long-running stations. Click the header
 * to toggle. The active date inside a collapsed group auto-expands. */
.date-month-group {
  border-bottom: 1px dashed var(--border);
}
.date-month-group:last-child { border-bottom: 0; }

.date-month-header {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.5rem 0.75rem;
  background: transparent;
  border: 0;
  cursor: pointer;
  text-align: left;
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  transition: background-color 120ms ease;
}
.date-month-header:hover { background: var(--surface-2); }

.date-month-chevron {
  width: 12px;
  height: 12px;
  flex: 0 0 12px;
  transition: transform 160ms ease;
  transform: rotate(-90deg);   /* collapsed default: chevron points right */
  color: var(--text-faint);
}
.date-month-group.is-expanded .date-month-chevron {
  transform: rotate(0);         /* expanded: chevron points down */
}

.date-month-label {
  flex: 1;
  color: var(--text);
  letter-spacing: 0.06em;
}
.date-month-meta {
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}

.date-month-items {
  /* Collapsed: hidden via display:none. Showing/hiding rather than
   * height-animating because the items are mono-uppercase rows of
   * unknown count — height animation flickers. */
  display: none;
}
.date-month-group.is-expanded .date-month-items {
  display: block;
}
.date-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.5rem 1rem;
  font-size: var(--text-sm);
  color: var(--text);
  cursor: pointer;
  border-left: 2px solid transparent;
  transition: background-color 120ms ease, border-color 120ms ease,
              color 120ms ease;
}
.date-item:hover {
  background: var(--surface-2);
}
.date-item.active {
  background: var(--primary-soft);
  color: var(--primary);
  border-left-color: var(--primary);
  font-weight: 600;
}
.date-item-label {
  font-family: var(--font-mono);
  font-size: calc(var(--text-sm) - 0.5px);
}
.date-item-count {
  font-size: var(--text-xs);
  padding: 0.1rem 0.45rem;
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--text-dim);
  font-weight: 600;
  flex-shrink: 0;
}
.date-item.active .date-item-count {
  background: var(--primary);
  color: #ffffff;
}

.gallery-main {
  min-width: 0;
}
.gallery-day-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-bottom: 1rem;
}
.gallery-day-header h2 {
  font-size: var(--text-xl);
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.01em;
  display: flex;
  align-items: center;
  gap: 0.6rem;
}
.gallery-day-header h2 .count {
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--text-dim);
}
.gallery-day-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.gallery-filters {
  display: flex;
  gap: 0.4rem;
  align-items: center;
  flex-wrap: wrap;
  margin: 0.5rem 0 0.85rem;
}
.filter-chip {
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text-dim);
  font-size: var(--text-xs);
  font-weight: 500;
  padding: 0.35rem 0.7rem;
  border-radius: 999px;
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease;
}
.filter-chip:hover:not(:disabled) {
  background: var(--surface-2);
  color: var(--text);
  border-color: var(--border-hover);
}
.filter-chip.active {
  background: var(--primary);
  color: #fff;
  border-color: var(--primary);
}
.filter-chip:disabled { opacity: 0.45; cursor: default; }
.filter-status {
  font-size: var(--text-xs);
  color: var(--text-faint);
  font-family: var(--font-mono);
  margin-left: 0.25rem;
}

/* AI badge — sparkle next to AI-driven actions. Tiny, never dominant. */
.ai-badge {
  display: inline-block;
  margin-left: 0.35rem;
  font-size: 0.85em;
  opacity: 0.7;
  vertical-align: baseline;
}
.btn:hover .ai-badge { opacity: 1; }


/* Smart-search presets + advanced disclosure */
.ss-preset-row {
  display: flex;
  gap: 0.4rem;
  align-items: center;
  flex-wrap: wrap;
  margin: 0.25rem 0 0.6rem;
}
.ss-preset {
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text-dim);
  padding: 0.3rem 0.7rem;
  border-radius: 999px;
  font-size: var(--text-xs);
  font-weight: 500;
  cursor: pointer;
}
.ss-preset:hover { background: var(--surface-2); color: var(--text); border-color: var(--border-hover); }
.ss-preset.active { background: var(--primary); color: #fff; border-color: var(--primary); }
.ss-range-summary {
  font-size: var(--text-xs);
  color: var(--text-faint);
  font-family: var(--font-mono);
  margin-left: 0.4rem;
}
/* Class picker — chip grid in the Smart Search modal. Renders at modal-open
   time from /api/yolo/status; only visible when YOLO is reachable AND has
   loaded models. Re-uses .filter-chip for the chip itself; this is just
   the wrapper grid + active-state colour matching the ss-preset pattern. */
.ss-classes-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  padding: 4px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg);
  max-height: 160px;
  overflow-y: auto;
}
.ss-class-chip {
  background: transparent;
  border: 1px solid transparent;
  color: var(--text-dim);
  padding: 0.25rem 0.55rem;
  border-radius: 3px;
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.04em;
}
.ss-class-chip:hover { color: var(--text); border-color: var(--border); }
.ss-class-chip.active {
  background: var(--primary);
  color: #fff;
  border-color: var(--primary);
}
.ss-advanced {
  display: none;
  margin-top: 0.6rem;
  padding-top: 0.6rem;
  border-top: 1px dashed var(--border);
}
.ss-advanced.open { display: block; }

/* Smart-search progress bar — shown while the YOLO stream walks frames.
   Mono caps for the count register, clay fill, single-edge track. */
.ss-progress {
  padding: 0.65rem 0;
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
}
.ss-progress-label {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}
.ss-progress-classes {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 0.3rem;
}
.ss-progress-chip {
  font-family: var(--font-mono);
  font-size: 9.5px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--primary);
  background: var(--primary-soft);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 2px 7px;
}
.ss-progress-count {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.06em;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.ss-progress-track {
  height: 6px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 3px;
  overflow: hidden;
}
.ss-progress-fill {
  height: 100%;
  background: var(--primary);
  width: 0%;
  transition: width 180ms ease;
}
.ss-progress-hint {
  font-family: var(--font-sans);
  font-size: 11.5px;
  color: var(--text-faint);
}
.ss-progress-cancel {
  /* Sits on the right of the progress label row. Mono+caps to match
     the mono-numerics convention of the count it pairs with. */
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-dim);
  padding: 2px 8px;
  margin-left: auto;
  border: 1px solid var(--border);
  background: var(--surface);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: border-color 120ms ease, color 120ms ease, background 120ms ease;
}
.ss-progress-cancel:hover {
  border-color: var(--danger);
  color: var(--danger);
}
.ss-progress-cancel:disabled {
  opacity: 0.55;
  cursor: not-allowed;
  color: var(--text-faint);
  border-color: var(--border);
}

/* Results header — stats line on the left, Clear button on the right.
   Single dashed top rule so it reads like a section break above the grid. */
.ss-results-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding-top: 0.6rem;
  margin: 0.5rem 0 0.6rem;
  border-top: 1px dashed var(--border);
  flex-wrap: wrap;
}

/* Smart-search results grid (inside the modal) */
.ss-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 0.6rem;
  margin-top: 0.4rem;
}
.ss-card {
  background: var(--surface-2, var(--surface));
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
  cursor: pointer;
  transition: border-color 120ms ease, transform 120ms ease;
}
.ss-card:hover { border-color: var(--primary); transform: translateY(-1px); }
.ss-card img { width: 100%; aspect-ratio: 4/3; object-fit: cover; display: block; }
.ss-card-meta { padding: 0.4rem 0.55rem; font-size: var(--text-xs); }
.ss-card-when { font-family: var(--font-mono); color: var(--text-dim); margin-bottom: 0.15rem; word-break: break-all; }
.ss-card-reason { color: var(--text); line-height: 1.35; }

/* ── Moments tab — sub-nav + saved actions + class filter ─────────────────
   Search and Saved sub-tabs inside the Moments tab. The sub-tab nav uses
   a smaller, lighter visual weight than the top-level station tab nav so
   the hierarchy reads correctly: station tabs (Gallery / Moments / …) →
   sub-tabs (Search / Saved). */
.moments-shell { display: flex; flex-direction: column; gap: 1rem; }
.sub-tab-nav {
  display: flex; gap: 0.05rem;
  border-bottom: 1px dashed var(--border);
  padding: 0;
}
.sub-tab-item {
  font-family: var(--font-sans); font-size: 0.78rem;
  font-weight: 500; letter-spacing: 0.04em;
  color: var(--text-dim); background: transparent;
  border: 0; border-bottom: 2px solid transparent;
  padding: 0.55rem 0.85rem;
  cursor: pointer;
  margin-bottom: -1px;
  transition: color 120ms ease, border-color 120ms ease;
}
.sub-tab-item:hover { color: var(--text); }
.sub-tab-item.active { color: var(--primary); border-bottom-color: var(--primary); }
.sub-tab-count {
  font-family: var(--font-mono); font-size: 9.5px;
  color: var(--text-faint); margin-left: 0.25rem; letter-spacing: 0.04em;
}

.ss-classes-header {
  display: flex; align-items: center; gap: 0.6rem;
  margin-bottom: 0.35rem;
}
.ss-class-filter-input {
  font-size: 0.85rem; padding: 0.3rem 0.55rem;
  width: 220px; flex: 0 0 auto;
}
@media (max-width: 600px) {
  .ss-classes-header { flex-direction: column; align-items: stretch; }
  .ss-class-filter-input { width: 100%; }
}

.moments-actions {
  display: flex; align-items: center; gap: 0.5rem;
  margin-top: 1rem; padding-top: 0.85rem;
  border-top: 1px solid var(--border);
}
.moments-saved-actions {
  display: flex; gap: 0.35rem; margin-top: 0.45rem;
}
.moments-saved-actions .btn { flex: 0 0 auto; }
.moments-saved-card .ss-card-meta { padding-bottom: 0.55rem; }
.moments-empty {
  padding: 2rem 1.25rem;
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  text-align: left;
}

/* ── Station-page: compact stats strip + detail-page chooser ──────────────
   The station-page stats strip is a flex row: 4 compact pills on the left,
   a dashed separator, then [Status] [Settings] action buttons. Detail
   pages (Status / Settings) replace the tab content entirely; the tab nav
   hides while a detail page is active. Sizes are calibrated against the
   Site Notes density rule — pills are ~1/3 shorter than the project-grid
   .stat-card, since on the station page the pills are secondary chrome. */
.stats-strip.stats-strip-compact {
  display: flex;
  align-items: stretch;
  gap: 0.75rem;
  margin-bottom: 1.25rem;
  grid-template-columns: none;            /* override the projects-grid rules */
}
.stats-strip-pills {
  display: grid;
  grid-template-columns: repeat(4, minmax(120px, 1fr));
  gap: 0.5rem;
  flex: 1 1 auto;
  min-width: 0;
}
/* Stat pill v2 (2026-05-05 redesign): label + value sit on the same
 * row — label LEFT in mono-uppercase faint, value RIGHT in tabular-
 * nums weight 500. Pills are ~36px tall instead of the old ~64px;
 * the third "sub" line (timestamp / "uploaded" hint) is hidden by
 * default and surfaces in a native tooltip via `title=...` on the
 * pill so the data isn't lost. Disk bar stretches under the pair
 * since it's purely visual. */
.stats-strip.stats-strip-compact .stat-card {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: baseline;
  gap: 0.55rem;
  padding: 0.55rem 0.75rem;
  min-height: 36px;
  box-shadow: none;
}
.stats-strip.stats-strip-compact .stat-card-label {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
  white-space: nowrap;
}
.stats-strip.stats-strip-compact .stat-card-value {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 1.05rem;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--text);
  text-align: right;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Sub line still rendered (the existing markup adds it as a sibling
 * of the value), but pulled into the same row visually as a small
 * faint suffix. Falls back to display:none on narrow widths so the
 * value doesn't fight for space. */
.stats-strip.stats-strip-compact .stat-card-sub {
  grid-column: 1 / -1;
  font-family: var(--font-mono);
  font-size: 9.5px;
  color: var(--text-faint);
  letter-spacing: 0.04em;
  text-align: right;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-top: -0.15rem;
}
@media (max-width: 880px) {
  .stats-strip.stats-strip-compact .stat-card-sub { display: none; }
}
.stats-strip.stats-strip-compact .disk-bar {
  grid-column: 1 / -1;
  height: 3px;
  margin-top: 0.2rem;
}
/* Vertical stack on the right of the stats strip. The two buttons share
   the strip's vertical rhythm — together they roughly equal one pill's
   height. Each button picks its own brand-palette tint:
     Status   → lichen (success-soft)   — informational read-only surface
     Settings → clay   (primary-soft)   — the place you change things
   They use the soft-tint background by default and fill solid when
   active, so the operator can see at a glance which detail they're in. */
/* Stats-strip actions v2: dropped the dashed left divider that made
 * Status/Settings read as a separate section. They're sibling
 * navigation to the pills now — same row, regular gap, smaller
 * button size so they don't compete with the pills for visual
 * weight. (2026-05-05.) */
.stats-strip-actions {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: stretch;
  gap: 0.3rem;
  padding-left: 0;
  border-left: 0;
  flex: 0 0 auto;
  min-width: 110px;
}
.stats-action-btn {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 0.32rem 0.7rem;
  white-space: nowrap;
  flex: 1 1 0;
  min-height: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Status — lichen tint */
.stats-action-btn#st-detail-status {
  background: var(--success-soft);
  border-color: var(--success);
  color: var(--success);
}
.stats-action-btn#st-detail-status:hover {
  background: var(--success);
  color: #fff;
  border-color: var(--success);
}
.stats-action-btn#st-detail-status.active {
  background: var(--success);
  color: #fff;
  border-color: var(--success);
}

/* Settings — clay tint */
.stats-action-btn#st-detail-settings {
  background: var(--primary-soft);
  border-color: var(--primary);
  color: var(--primary);
}
.stats-action-btn#st-detail-settings:hover {
  background: var(--primary);
  color: #fff;
  border-color: var(--primary);
}
.stats-action-btn#st-detail-settings.active {
  background: var(--primary);
  color: #fff;
  border-color: var(--primary);
}

@media (max-width: 900px) {
  .stats-strip.stats-strip-compact {
    flex-direction: column;
    gap: 0.6rem;
  }
  .stats-strip-pills { grid-template-columns: repeat(2, 1fr); }
  .stats-strip-actions {
    /* Below 900px the strip stacks vertically — Status/Settings revert to
       a horizontal pair so they don't become a tall narrow column. */
    flex-direction: row;
    border-left: 0;
    border-top: 1px dashed var(--border);
    padding-left: 0;
    padding-top: 0.6rem;
    justify-content: stretch;
    min-width: 0;
  }
  .stats-action-btn { flex: 1 1 0; }
}
@media (max-width: 520px) {
  .stats-strip-pills { grid-template-columns: 1fr 1fr; }
}

/* Detail-page shell — used by the Status and Settings surfaces when
   reached via the stats-strip buttons. Header has a back-link, a title,
   and a single right-aligned CTA (Run diagnostic / Rename station). */
.st-detail-page { display: flex; flex-direction: column; gap: 1rem; }
.st-detail-head {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  padding-bottom: 0.65rem;
  border-bottom: 1px solid var(--border);
}
/* Back-to-gallery button on the Status/Settings detail pages.
   Matches the .page-breadcrumb-back family so the two back-buttons read
   as one consistent control across the app. */
.btn.st-detail-back {
  display: inline-flex; align-items: center; gap: 0.4rem;
  height: 30px;
  padding: 0 0.75rem;
  box-sizing: border-box;
  font-family: var(--font-mono); font-size: 10.5px;
  font-weight: 500; letter-spacing: 0.06em; line-height: 1;
  text-transform: uppercase;
  color: var(--text-dim);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: color 120ms ease, border-color 120ms ease, background 120ms ease;
}
.btn.st-detail-back:hover {
  color: var(--primary);
  border-color: var(--primary);
  background: var(--primary-soft);
}
.btn.st-detail-back svg { stroke-width: 2; width: 12px; height: 12px; flex: 0 0 auto; }
.st-detail-title {
  font-size: 1.1rem; font-weight: 500; color: var(--text);
  letter-spacing: -0.01em; margin: 0;
}
.st-detail-actions { margin-left: auto; display: flex; gap: 0.5rem; }
@media (max-width: 600px) {
  .st-detail-head { flex-wrap: wrap; }
  .st-detail-actions { width: 100%; justify-content: flex-end; }
}
.mobile-dates-toggle {
  display: none;
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text);
  font-size: var(--text-xs);
  font-weight: 500;
  padding: 0.4rem 0.75rem;
  border-radius: var(--radius);
  cursor: pointer;
}
.mobile-dates-toggle:hover {
  background: var(--surface-2);
  border-color: var(--border-hover);
}

.image-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 0.85rem;
}

.image-card {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  cursor: pointer;
  transition: border-color 120ms ease, box-shadow 120ms ease,
              transform 120ms ease;
}
.image-card:hover {
  border-color: var(--border-hover);
  box-shadow: var(--shadow);
  transform: translateY(-1px);
}
.image-card.selected {
  border-color: var(--primary);
  box-shadow: 0 0 0 2px var(--primary);
}
.image-card img {
  width: 100%;
  aspect-ratio: 4/3;
  object-fit: cover;
  display: block;
  background: var(--surface-2);
}
.image-card-info {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.4rem;
  padding: 0.4rem 0.55rem;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-dim);
}
.image-card-size {
  color: var(--text-faint);
}
.image-card-delete {
  position: absolute;
  top: 0.4rem;
  right: 0.4rem;
  display: none;
  width: 26px;
  height: 26px;
  align-items: center;
  justify-content: center;
  background: rgba(0,0,0,0.55);
  color: #ffffff;
  border: 0;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: 0.85rem;
  line-height: 1;
  padding: 0;
  transition: background-color 120ms ease;
}
.image-card-delete:hover {
  background: var(--danger);
}
.image-card.has-actions:hover .image-card-delete {
  display: inline-flex;
}
.image-card-check {
  position: absolute;
  top: 0.4rem;
  left: 0.4rem;
  display: none;
  width: 22px;
  height: 22px;
  border: 2px solid #ffffff;
  border-radius: var(--radius-sm);
  background: rgba(0,0,0,0.35);
  box-shadow: 0 1px 3px rgba(0,0,0,0.3);
}
.image-card.selected .image-card-check {
  background: var(--primary);
  border-color: var(--primary);
}
.image-card.selected .image-card-check::after {
  content: '\2713';
  display: block;
  text-align: center;
  color: #ffffff;
  font-size: 0.75rem;
  font-weight: 700;
  line-height: 18px;
}
body.select-mode .image-card-check    { display: block; }
body.select-mode .image-card-delete   { display: none !important; }

/* ── Gallery compare-pick mode ──
   Activated by the toolbar Compare button. Hides delete (avoid mis-click),
   crosshair cursor, and a numbered "1" badge on the first picked card so
   the user sees which image is locked in. */
body.compare-pick-mode .image-card { cursor: crosshair; }
body.compare-pick-mode .image-card-delete { display: none !important; }
.image-card.compare-a {
  outline: 3px solid var(--primary);
  outline-offset: -3px;
}
.image-card.compare-a::before {
  content: '1';
  position: absolute;
  top: 0.4rem;
  left: 0.4rem;
  z-index: 1;
  background: var(--primary);
  color: #ffffff;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: var(--text-xs);
  box-shadow: 0 1px 3px rgba(0,0,0,0.3);
}
/* When active, the toolbar Compare button reads "Cancel compare" — give it a
   distinct fill so the user feels they're in a mode. */
#cmp-pick-btn.active {
  background: var(--primary-soft);
  color: var(--primary);
  border-color: var(--primary);
}

/* Gallery responsive: sidebar becomes off-canvas, toggle button appears */
@media (max-width: 900px) {
  .gallery-layout {
    grid-template-columns: 1fr;
  }
  .gallery-dates {
    position: fixed;
    top: 0;
    left: -280px;
    bottom: 0;
    width: 280px;
    max-height: 100vh;
    z-index: 30;
    transition: left 200ms ease;
    border-radius: 0;
  }
  .gallery-dates.open {
    left: 0;
    box-shadow: var(--shadow-md);
  }
  .sidebar-close { display: inline-flex; align-items: center; justify-content: center; }
  .mobile-dates-toggle { display: inline-flex; align-items: center; gap: 0.35rem; }
}

/* ── 26. LIGHTBOX ────────────────────────────────────────────────────────── */

.lightbox {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(10,10,10,0.96);
  /* z-index 1200 sits above modals (1100) and Leaflet controls (~1000),
     so opening an image takes over the screen even if a map or modal
     was on top. (Audit 2026-04-30 z-index ladder pass.) */
  z-index: 1200;
  align-items: center;
  justify-content: center;
  cursor: zoom-out;
}
.lightbox.open { display: flex; }

.lightbox-stage {
  position: relative;
  width: 92vw;
  height: 88vh;
  overflow: hidden;
  touch-action: none;
  cursor: zoom-in;
}
.lightbox-stage.zoomed { cursor: grab; }
.lightbox-stage.panning { cursor: grabbing; }
.lightbox-stage img {
  position: absolute;
  top: 0;
  left: 0;
  /* The global `img { max-width: 100%; }` reset would otherwise clamp the
     img's box to the stage width BEFORE our transform scales it down to
     fit. That double-shrink rendered captures at roughly 1/3 the stage
     size and made the annotation canvas (which does NOT inherit the
     img reset) misaligned. Force natural dims so transform-scale is the
     only thing that resizes the image. */
  max-width: none;
  max-height: none;
  width: auto;
  height: auto;
  transform-origin: 0 0;
  user-select: none;
  -webkit-user-drag: none;
  cursor: inherit;
  will-change: transform;
}
.lightbox-stage img.lb-anim { transition: transform 140ms ease; }
.lb-canvas {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 0 0;
  pointer-events: none;
}
.lb-canvas.editing {
  pointer-events: auto;
  cursor: crosshair;
}

/* ── Detections overlay (debug "what is the model seeing") ──────────────
   Sits between the image and the markup canvas in the lightbox stage.
   Its width/height are set to image naturalWidth/Height in JS, and the
   container mirrors the same CSS transform as the img — so boxes
   positioned by percentage inside stay aligned through pan + zoom. */
.lb-detections {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 0 0;
  pointer-events: none;
  z-index: 2;       /* above img, below markup canvas */
}
/* Vision AI bottom dock — sibling of .lb-detections so it does NOT
   inherit the image's CSS transform. Pinned to the bottom of the
   lightbox stage in normal coordinates: always full-size, always
   readable + clickable regardless of how the image is zoomed. */
.lb-det-dock {
  position: absolute;
  bottom: 16px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 6;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 12px;
  background: rgba(255,255,255,0.96);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 4px 14px rgba(0,0,0,0.22);
  pointer-events: auto;
  max-width: calc(100% - 32px);
  flex-wrap: wrap;
}
.lb-det-dock-btn {
  font-family: var(--font-sans);
  font-size: 12.5px;
  font-weight: 500;
  padding: 6px 12px;
  border: 1px solid var(--primary);
  background: var(--primary);
  color: #fff;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background 100ms ease, border-color 100ms ease;
}
.lb-det-dock-btn:hover { background: var(--primary-hover); border-color: var(--primary-hover); }
.lb-det-dock-btn.active {
  background: var(--warn);
  border-color: var(--warn);
  color: #fff;
}
.lb-det-dock-stats {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.lb-det-dock-stats .lb-det-stat-bad { color: var(--danger); }
.lb-det-dock-stats .lb-det-stat-rel { color: var(--warn); }
.lb-det-dock-stats .lb-det-stat-add { color: var(--success); }
.lb-det-dock-toast {
  /* Inline with the dock children. Hidden until populated. */
  font-family: var(--font-sans);
  font-size: 11.5px;
  color: var(--text);
  background: var(--surface-2);
  border-radius: var(--radius-sm);
  padding: 4px 8px;
  display: none;
  align-items: center;
  gap: 6px;
}
.lb-det-dock-toast.open { display: flex; }
.lb-det-dock-toast a {
  color: var(--primary);
  text-decoration: none;
  font-weight: 500;
}
.lb-det-dock-toast a:hover { text-decoration: underline; }
.lb-det-err { color: var(--danger); border-color: var(--danger); }
.lb-det-box {
  position: absolute;
  border: 2px solid var(--detbox, #888);
  background: transparent;
  pointer-events: none;
  box-shadow: 0 0 0 1px rgba(0,0,0,0.3);
}
.lb-det-label {
  position: absolute;
  top: 0; left: 0;
  transform: translateY(-100%);    /* sit above the box */
  font-family: var(--font-mono);
  font-size: 13px;                 /* bumped 2026-05-04 (client feedback) */
  font-weight: 500;
  letter-spacing: 0.02em;
  color: #fff;
  background: var(--detbox, #888);
  padding: 2px 7px;
  white-space: nowrap;
  pointer-events: none;
}
.lb-det-person        { --detbox: #2c7be5; }    /* blue   */
.lb-det-vehicle       { --detbox: #d4943f; }    /* hi-vis orange */
.lb-det-equipment     { --detbox: #d4b045; }    /* yellow */
.lb-det-ppe-compliance{ --detbox: #87a06b; }    /* lichen green */
.lb-det-ppe-violation { --detbox: #b25a4d; }    /* brick red */
.lb-det-other         { --detbox: #777; }

/* Box becomes interactive when feedback is enabled. Hover widens the
   stroke + shows a "click me" cursor; the inner area stays click-through
   so you can still drag the image underneath if you click outside the
   stroke (we set pointer-events on the BORDER region only via padding). */
.lb-det-box {
  cursor: pointer;
  pointer-events: auto;
  transition: box-shadow 100ms ease;
}
.lb-det-box:hover {
  box-shadow: 0 0 0 2px var(--detbox, #888), 0 0 0 4px rgba(0,0,0,0.4);
  z-index: 1;
}
/* Visual states for boxes that already have a verdict */
.lb-det-box.lb-det-fb-confirm { border-style: solid; opacity: 0.55; }
.lb-det-box.lb-det-fb-reject  { border-style: dashed; opacity: 0.4; }
.lb-det-box.lb-det-fb-relabel { border-style: dotted; opacity: 0.55; }
.lb-det-verdict-badge {
  position: absolute;
  top: -10px;
  right: -10px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--detbox, #888);
  color: #fff;
  font-size: 14px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 1px 3px rgba(0,0,0,0.4);
  pointer-events: none;
}
.lb-det-tally {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.04em;
  color: var(--text-dim);
  margin-left: 4px;
}

/* ── Verdict popup (shows on box click) ──────────────────────────────── */
.lb-det-popup {
  position: absolute;
  z-index: 10;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 6px 18px rgba(0,0,0,0.18);
  padding: 0.65rem 0.7rem 0.7rem;
  width: 280px;
  font-family: var(--font-sans);
  font-size: 12px;
  pointer-events: auto;
}
.lb-det-popup-head {
  font-weight: 500;
  font-size: 13px;
  margin-bottom: 0.4rem;
  padding-right: 1.2rem;    /* room for the close × */
}
.lb-det-popup-current {
  font-size: 11px;
  color: var(--text-dim);
  background: var(--surface-2);
  border-radius: var(--radius-sm);
  padding: 4px 6px;
  margin-bottom: 0.5rem;
}
.lb-det-popup-current a {
  color: var(--primary);
  text-decoration: none;
}
.lb-det-popup-current a:hover { text-decoration: underline; }
.lb-det-popup-actions {
  display: grid;
  gap: 0.3rem;
}
.lb-det-act {
  font-family: var(--font-sans);
  font-size: 11.5px;
  padding: 5px 8px;
  text-align: left;
  border: 1px solid var(--border);
  background: var(--surface);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: border-color 100ms ease, background 100ms ease;
}
.lb-det-act:hover { background: var(--surface-2); }
.lb-det-act-confirm:hover { border-color: var(--success); background: var(--success-soft); }
.lb-det-act-reject:hover  { border-color: var(--danger);  background: var(--danger-soft); }
.lb-det-act-relabel:hover { border-color: var(--warn);    background: var(--warn-soft); }
.lb-det-popup-close {
  position: absolute;
  top: 4px; right: 6px;
  background: transparent;
  border: none;
  font-size: 18px;
  line-height: 1;
  color: var(--text-faint);
  cursor: pointer;
  padding: 0 4px;
}
.lb-det-popup-close:hover { color: var(--text); }

/* Relabel sub-panel — opens under the action buttons */
.lb-det-popup-relabel {
  margin-top: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px dashed var(--border);
}
.lb-det-popup-relabel input[type="text"] {
  width: 100%;
  font-family: var(--font-sans);
  font-size: 12px;
  padding: 4px 6px;
  border: 1px solid var(--border);
  background: var(--surface);
  border-radius: var(--radius-sm);
  margin-bottom: 0.3rem;
}
.lb-det-relabel-list {
  max-height: 160px;
  overflow-y: auto;
  display: grid;
  gap: 1px;
}
.lb-det-relabel-opt {
  font-family: var(--font-mono);
  font-size: 11px;
  text-align: left;
  padding: 4px 6px;
  border: none;
  background: var(--surface);
  cursor: pointer;
  border-radius: var(--radius-sm);
}
.lb-det-relabel-opt:hover { background: var(--primary-soft); color: var(--primary); }
.lb-det-relabel-loading,
.lb-det-relabel-empty {
  font-size: 11px;
  color: var(--text-faint);
  padding: 4px 6px;
}

/* Active-state for the Boxes sidebar toggle */
.lb-side-btn.active {
  border-color: var(--primary);
  color: var(--primary);
  background: var(--primary-soft);
}

/* "+ add missing" button in the status bar */
.lb-det-draw-btn {
  margin-left: 8px;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 2px 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-dim);
  border-radius: var(--radius-sm);
  cursor: pointer;
  pointer-events: auto;
  transition: border-color 100ms ease, color 100ms ease, background 100ms ease;
}
.lb-det-draw-btn:hover {
  border-color: var(--primary);
  color: var(--primary);
}
.lb-det-draw-btn.active {
  border-color: var(--warn);
  color: #fff;
  background: var(--warn);
}

/* Draw mode: layer captures pointer events + crosshair cursor */
.lb-detections.lb-det-drawing {
  pointer-events: auto;
  cursor: crosshair;
}
.lb-detections.lb-det-drawing .lb-det-box {
  /* boxes are visible but not clickable while drawing — we want
     the drag-to-create gesture to land on the layer, not on a box */
  pointer-events: none;
}

/* In-progress draw preview rectangle */
.lb-det-draw-preview {
  position: absolute;
  border: 2px dashed var(--warn);
  background: rgba(212, 148, 63, 0.12);
  pointer-events: none;
  z-index: 5;
}

/* User-added "missing" boxes — distinct dashed style + USER tag so
   they're obviously different from model output boxes */
.lb-det-box.lb-det-missing {
  border-style: dashed;
  border-width: 2px;
}
.lb-det-user-tag {
  display: inline-block;
  margin-left: 4px;
  padding: 0 4px;
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 0.1em;
  background: rgba(255,255,255,0.85);
  color: var(--text);
  border-radius: 2px;
}

/* ── 26c. ANNOTATION EDITOR ─────────────────────────────────────────────── */

/* Persistent left sidebar (Compare / Mark up / Analyze) — replaces the old
   ⋯ overflow menu. Pinned to top-left of the lightbox so every lightbox
   action is one click away. Industrial styling matches the markup toolbar. */
.lb-sidebar {
  position: fixed;
  top: 1.5rem;
  left: 1.5rem;
  z-index: 102;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 6px;
  background: rgba(15, 17, 22, 0.94);
  border: 1px solid rgba(255,255,255,0.14);
  border-radius: 6px;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: 0 8px 28px rgba(0,0,0,0.45);
  font-family: var(--font-mono);
}
.lb-side-btn {
  width: 64px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 8px 4px;
  background: transparent;
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: 4px;
  color: rgba(255,255,255,0.78);
  cursor: pointer;
  font-family: inherit;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  transition: background-color 80ms, border-color 80ms, color 80ms;
  position: relative;
}
.lb-side-btn:hover {
  background: rgba(255,255,255,0.06);
  border-color: rgba(255,255,255,0.22);
  color: #fff;
}
.lb-side-btn:active { transform: translateY(1px); }
.lb-side-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  background: rgba(255,255,255,0.05);
  border-radius: 4px;
  border: 1px solid rgba(255,255,255,0.06);
}
.lb-side-icon svg { width: 16px; height: 16px; }
.lb-side-label { font-size: 10px; line-height: 1; }
.lb-side-ai .lb-side-icon {
  background: linear-gradient(135deg, var(--primary), var(--warn));
  border-color: rgba(255,255,255,0.18);
}
.lb-side-ai .ai-badge { opacity: 1; margin-left: 2px; font-size: 9px; }

@media (max-width: 500px) {
  .lb-sidebar { top: 0.75rem; left: 0.75rem; gap: 2px; padding: 4px; }
  .lb-side-btn { width: 52px; padding: 6px 3px; font-size: 9px; }
  .lb-side-icon { width: 22px; height: 22px; }
  .lb-side-icon svg { width: 14px; height: 14px; }
}

.lightbox.editing-annotation .lightbox-prev,
.lightbox.editing-annotation .lightbox-next,
.lightbox.editing-annotation .lb-sidebar,
.lightbox.editing-annotation .lightbox-close,
.lightbox.editing-annotation .lightbox-info { display: none; }

/* AI Analyze panel (Track B) — floats inside the lightbox over the
   bottom-right corner of the stage; doesn't fight pan/zoom. */
.lightbox-analyze-panel {
  display: none;
  position: fixed;
  right: 1.25rem;
  bottom: 4.25rem;
  z-index: 104;
  width: min(360px, calc(100vw - 2rem));
  color: #fff;
}
.ana-card {
  background: rgba(0,0,0,0.78);
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 12px;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  padding: 0.85rem;
  box-shadow: 0 8px 24px rgba(0,0,0,0.45);
}
.ana-loading, .ana-error {
  background: rgba(0,0,0,0.78);
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 12px;
  padding: 0.75rem 1rem;
  font-size: var(--text-sm);
  color: #fff;
}
.ana-error { border-color: var(--danger); color: #ffd6d6; }
.ana-header {
  display: flex; align-items: center; gap: 0.5rem;
  font-size: var(--text-sm); margin-bottom: 0.6rem;
}
.ana-cached {
  font-size: 10px;
  font-family: var(--font-mono);
  background: rgba(255,255,255,0.12);
  padding: 0.1rem 0.4rem;
  border-radius: 999px;
  color: rgba(255,255,255,0.7);
}
.ana-close {
  margin-left: auto;
  background: none; border: 0; color: rgba(255,255,255,0.7);
  cursor: pointer; font-size: 1.3rem; line-height: 1; padding: 0;
}
.ana-stats {
  display: flex; gap: 0.5rem; margin-bottom: 0.6rem;
}
.ana-stat {
  flex: 1;
  background: rgba(255,255,255,0.06);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: 8px;
  padding: 0.5rem 0.6rem;
  text-align: center;
  display: flex; flex-direction: column; gap: 0.15rem;
}
.ana-num { font-size: 1.4rem; font-weight: 700; line-height: 1; }
.ana-num.ana-good { color: var(--success); }
.ana-num.ana-warn { color: var(--warn); }
.ana-num.ana-bad  { color: var(--danger); }
.ana-num.ana-dim  { color: rgba(255,255,255,0.55); font-size: 0.95rem; }
.ana-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: rgba(255,255,255,0.55);
}
.ana-summary {
  font-size: var(--text-sm);
  line-height: 1.45;
  margin-bottom: 0.6rem;
}
.ana-concerns {
  background: rgba(178,90,77,0.18);   /* translucent --danger; sits on dark .ana-card */
  border-left: 3px solid var(--danger);
  border-radius: 4px;
  padding: 0.4rem 0.6rem;
  margin-bottom: 0.6rem;
  font-size: var(--text-xs);
}
.ana-concerns strong { display: block; margin-bottom: 0.25rem; }
.ana-concerns ul { margin: 0; padding-left: 1.1rem; }
.ana-actions { display: flex; justify-content: flex-end; }

@media (max-width: 600px) {
  .lightbox-analyze-panel { right: 0.5rem; left: 0.5rem; bottom: 3.5rem; width: auto; }
}

/* Industrial / control-panel toolbar — sharp corners, mono labels, sectioned. */
.ann-toolbar {
  position: fixed;
  top: 1.5rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 105;
  display: none;
  flex-direction: column;
  background: rgba(15, 17, 22, 0.94);
  border: 1px solid rgba(255,255,255,0.14);
  border-radius: 6px;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  max-width: calc(100vw - 2rem);
  font-family: var(--font-mono);
  box-shadow: 0 8px 28px rgba(0,0,0,0.45);
  overflow: hidden;
}
.ann-toolbar.open { display: inline-flex; }

.ann-status-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.4rem 0.75rem;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: rgba(255,255,255,0.55);
  border-bottom: 1px solid rgba(255,255,255,0.08);
  white-space: nowrap;
  gap: 0.6rem;
  min-width: 0;
}
.ann-status-bar > span:first-child {
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.ann-status-bar .ann-status-title {
  color: rgba(255,255,255,0.92);
  font-weight: 700;
  letter-spacing: 0.12em;
}
.ann-status-bar .ann-status-sep { color: rgba(255,255,255,0.3); }
.ann-status-bar .ann-status-dirty {
  display: none;
  color: var(--warn);
  font-weight: 700;
}
.ann-toolbar.dirty .ann-status-bar .ann-status-dirty { display: inline; }

.ann-tools-row {
  display: flex;
  align-items: stretch;
  flex-wrap: wrap;
}
.ann-tool-group {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.45rem 0.7rem;
  border-right: 1px solid rgba(255,255,255,0.08);
}
.ann-tool-group:last-child { border-right: 0; }
.ann-tool-group-label {
  font-size: 10px;
  color: rgba(255,255,255,0.4);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  margin-right: 0.15rem;
  font-weight: 600;
}

.ann-tool-btn {
  background: transparent;
  border: 1px solid rgba(255,255,255,0.1);
  color: rgba(255,255,255,0.78);
  padding: 0.35rem 0.65rem;
  border-radius: 4px;
  cursor: pointer;
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-family: inherit;
  white-space: nowrap;
  transition: background-color 80ms, border-color 80ms, color 80ms;
}
.ann-tool-btn:hover:not(:disabled) {
  background: rgba(255,255,255,0.06);
  border-color: rgba(255,255,255,0.22);
  color: #fff;
}
.ann-tool-btn.active {
  background: var(--primary);
  border-color: var(--primary);
  color: #fff;
}

.ann-swatches { display: inline-flex; gap: 4px; align-items: center; }
.ann-swatch {
  width: 18px;
  height: 18px;
  border-radius: 3px;
  cursor: pointer;
  border: 1.5px solid rgba(0,0,0,0.55);
  box-shadow: 0 0 0 1px rgba(255,255,255,0.06);
  padding: 0;
}
.ann-swatch.active {
  outline: 2px solid #fff;
  outline-offset: 1px;
}

.ann-sizes { display: inline-flex; gap: 0.25rem; align-items: center; }
.ann-size {
  min-width: 28px;
  height: 26px;
  border-radius: 4px;
  background: transparent;
  border: 1px solid rgba(255,255,255,0.12);
  color: rgba(255,255,255,0.7);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  font-weight: 700;
  font-family: inherit;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 0 0.35rem;
}
.ann-size:hover {
  background: rgba(255,255,255,0.08);
  border-color: rgba(255,255,255,0.24);
  color: #fff;
}
.ann-size.active {
  background: rgba(255,255,255,0.16);
  border-color: rgba(255,255,255,0.32);
  color: #fff;
}

.ann-actions-group {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.45rem 0.7rem;
  margin-left: auto;
  border-left: 1px solid rgba(255,255,255,0.14);
}

.ann-save-btn {
  background: var(--primary) !important;
  border-color: var(--primary) !important;
  color: #fff !important;
  padding: 0.4rem 1rem !important;
  letter-spacing: 0.1em;
}
.ann-save-btn:hover {
  background: var(--primary-hover) !important;
  border-color: var(--primary-hover) !important;
}
/* Dirty-state ring is clay (--primary, rgb(196,106,69)), not the original
   indigo (#6366f1) that survived the Section-11 palette swap. Audit M5
   (2026-04-30). Subdued glow — the saturated indigo halo was the loudest
   element on the page; this matches the rest of the system's dirty-state
   convention. */
.ann-toolbar.dirty .ann-save-btn {
  box-shadow: 0 0 0 2px rgba(196,106,69,0.45), 0 0 14px rgba(196,106,69,0.22);
}

.ann-cancel-btn { color: rgba(255,255,255,0.5) !important; }
.ann-cancel-btn:hover { color: #fff !important; }

.ann-share-btn { opacity: 0.6; }
.ann-share-btn:hover { opacity: 1; }

@media (max-width: 700px) {
  .ann-toolbar { top: 0.75rem; max-width: calc(100vw - 1rem); }
  .ann-tool-group { padding: 0.35rem 0.5rem; }
  .ann-tool-group-label { display: none; }
  .ann-tool-btn { padding: 0.3rem 0.55rem; font-size: 10px; }
  .ann-status-bar { padding: 0.35rem 0.6rem; font-size: 10px; }
}

/* ── 26d2. MARKUPS TAB (saved annotated images per station) ─────────────── */

.markups-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 0.85rem;
}
.markup-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  box-shadow: var(--shadow-sm);
  transition: border-color 120ms ease, box-shadow 120ms ease;
}
.markup-card:hover {
  border-color: var(--border-hover);
  box-shadow: var(--shadow);
}
.markup-thumb {
  position: relative;
  aspect-ratio: 4/3;
  background: var(--surface-2);
  cursor: pointer;
  overflow: hidden;
}
.markup-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.markup-marks {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  background: rgba(15,17,22,0.85);
  color: #fff;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 0.2rem 0.5rem;
  border-radius: 3px;
  border: 1px solid rgba(255,255,255,0.15);
}
.markup-meta {
  padding: 0.6rem 0.75rem 0.4rem;
  flex: 1 1 auto;
  min-width: 0;
}
.markup-when {
  font-size: var(--text-xs);
  color: var(--text-dim);
  margin-bottom: 0.2rem;
  word-break: break-all;
  line-height: 1.35;
}
.markup-by {
  line-height: 1.35;
}
.markup-actions {
  display: flex;
  gap: 0.4rem;
  padding: 0.4rem 0.75rem 0.75rem;
}
.markup-actions .btn { flex: 1; justify-content: center; }

/* ── 26d. PROGRESS-PHOTO SCHEDULE ROWS ──────────────────────────────────── */

.progress-photo-row {
  display: flex;
  gap: 1rem;
  align-items: center;
  padding: 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-2, var(--surface));
  margin-bottom: 0.6rem;
}
.progress-photo-row.disabled { opacity: 0.6; }
.progress-photo-row .pp-row-main { flex: 1 1 auto; min-width: 0; }
.progress-photo-row .pp-row-title { font-size: var(--text-sm); margin-bottom: 0.15rem; }
.progress-photo-row .pp-row-meta { line-height: 1.45; word-break: break-word; }
.progress-photo-row .pp-row-actions {
  display: flex;
  gap: 0.4rem;
  align-items: center;
  flex-wrap: wrap;
  flex-shrink: 0;
}
@media (max-width: 600px) {
  .progress-photo-row { flex-direction: column; align-items: stretch; }
  .progress-photo-row .pp-row-actions { justify-content: flex-start; }
}

.lightbox-close,
.lightbox-prev,
.lightbox-next {
  position: fixed;
  width: 48px;
  height: 48px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: rgba(0,0,0,0.55);
  color: #ffffff;
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 50%;
  cursor: pointer;
  font-size: 1.4rem;
  line-height: 1;
  padding: 0;
  z-index: 102;
  transition: background-color 120ms ease, transform 120ms ease,
              border-color 120ms ease;
  user-select: none;
}
.lightbox-close:hover,
.lightbox-prev:hover,
.lightbox-next:hover {
  background: var(--primary);
  border-color: var(--primary);
  transform: scale(1.05);
}
.lightbox-close { top: 1.5rem; right: 1.5rem; font-size: 1.2rem; }
.lightbox-prev  { left: 1.5rem;  top: 50%; transform: translateY(-50%); }
.lightbox-next  { right: 1.5rem; top: 50%; transform: translateY(-50%); }
.lightbox-prev:hover { transform: translateY(-50%) scale(1.05); }
.lightbox-next:hover { transform: translateY(-50%) scale(1.05); }

.lightbox-ann-status {
  display: none;
  position: fixed;
  bottom: 4rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 101;
  font-size: var(--text-xs);
  color: rgba(255,255,255,0.85);
  background: rgba(0,0,0,0.55);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  padding: 0.45rem 0.85rem;
  border-radius: 999px;
  border: 1px solid rgba(255,255,255,0.1);
  max-width: calc(100vw - 2rem);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.lightbox-ann-status .ann-share-state { color: #ffd; }
.lightbox-ann-status a {
  color: var(--primary);
  text-decoration: underline;
  margin-left: 0.25rem;
}
.lightbox-ann-status a:hover { filter: brightness(1.15); }

.lightbox-info {
  position: fixed;
  bottom: 1.5rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 101;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: #ffffff;
  background: rgba(0,0,0,0.5);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  padding: 0.55rem 1rem;
  border-radius: 999px;
  border: 1px solid rgba(255,255,255,0.08);
  max-width: calc(100vw - 2rem);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

@media (max-width: 500px) {
  .lightbox-close { top: 0.75rem; right: 0.75rem; }
  .lightbox-prev  { left: 0.5rem; }
  .lightbox-next  { right: 0.5rem; }
}

/* ── 26b. COMPARE OVERLAY ────────────────────────────────────────────────── */

.compare-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(10,10,10,0.97);
  z-index: 110;
  align-items: center;
  justify-content: center;
}
.compare-overlay.open { display: flex; }

.compare-stage {
  position: relative;
  width: 92vw;
  height: 84vh;
  overflow: hidden;
  user-select: none;
  touch-action: none;
}
.cmp-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  user-select: none;
  -webkit-user-drag: none;
}
.cmp-img-b { z-index: 1; }
.cmp-img-a {
  z-index: 2;
  /* clip from the right edge so left side shows image A */
  clip-path: inset(0 calc(100% - var(--cmp-pos, 50%)) 0 0);
}
.cmp-divider {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--cmp-pos, 50%);
  width: 2px;
  background: rgba(255,255,255,0.85);
  transform: translateX(-1px);
  z-index: 3;
  pointer-events: none;
}
.cmp-handle {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 44px;
  height: 44px;
  margin: -22px 0 0 -22px;
  background: rgba(0,0,0,0.65);
  border: 1px solid rgba(255,255,255,0.4);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #ffffff;
  cursor: ew-resize;
  pointer-events: auto;
  transition: transform 120ms ease, background-color 120ms ease;
}
.cmp-handle:hover { background: var(--primary); transform: scale(1.05); }
.cmp-handle svg { width: 22px; height: 22px; }

.cmp-close {
  position: fixed;
  top: 1.5rem;
  right: 1.5rem;
  width: 48px;
  height: 48px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: rgba(0,0,0,0.55);
  color: #ffffff;
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 50%;
  cursor: pointer;
  z-index: 112;
  transition: background-color 120ms ease, transform 120ms ease, border-color 120ms ease;
}
.cmp-close:hover { background: var(--primary); border-color: var(--primary); transform: scale(1.05); }
.cmp-close svg { width: 22px; height: 22px; }

.cmp-info {
  position: fixed;
  top: 1.5rem;
  z-index: 111;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: #ffffff;
  background: rgba(0,0,0,0.55);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  padding: 0.5rem 0.85rem;
  border-radius: 999px;
  border: 1px solid rgba(255,255,255,0.1);
  max-width: 38vw;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cmp-info-a { left: calc(1.5rem + 56px); }
.cmp-info-b { right: calc(1.5rem + 56px); }
/* No A/B prefix — timestamps speak for themselves; left/right is implicit. */
.cmp-info-a::before { content: ''; }
.cmp-info-b::before { content: ''; }

.cmp-actions {
  position: fixed;
  bottom: 1.5rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 111;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem;
  background: rgba(0,0,0,0.55);
  padding: 0.5rem;
  border-radius: 999px;
  border: 1px solid rgba(255,255,255,0.1);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  max-width: calc(100vw - 2rem);
}
.cmp-actions .btn { white-space: nowrap; }
.cmp-mode-toggle {
  display: inline-flex;
  background: rgba(255,255,255,0.08);
  border-radius: 999px;
  padding: 2px;
  margin-left: 0.25rem;
}
.cmp-mode-btn {
  background: transparent;
  border: 0;
  color: rgba(255,255,255,0.7);
  padding: 0.3rem 0.75rem;
  font-size: var(--text-xs);
  font-weight: 600;
  border-radius: 999px;
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease;
}
.cmp-mode-btn.active {
  background: var(--primary);
  color: #ffffff;
}
.cmp-mode-btn:not(.active):hover { color: #ffffff; }

/* Diff mode: show only the change-detection canvas. */
.cmp-img-diff { display: none; z-index: 2; background: transparent; }
.compare-overlay.mode-diff .cmp-img-a,
.compare-overlay.mode-diff .cmp-img-b,
.compare-overlay.mode-diff .cmp-divider { display: none; }
.compare-overlay.mode-diff .cmp-img-diff { display: block; }
.cmp-diff-status {
  position: absolute;
  top: 12px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 4;
  display: none;
  padding: 0.4rem 0.85rem;
  background: rgba(0,0,0,0.65);
  color: #fff;
  border-radius: 999px;
  font-size: var(--text-xs);
  font-family: var(--font-mono);
  border: 1px solid rgba(255,255,255,0.1);
  backdrop-filter: blur(8px);
}
.cmp-diff-status.show { display: block; }

@media (max-width: 700px) {
  .cmp-info { max-width: calc(50vw - 4rem); font-size: 10px; padding: 0.35rem 0.6rem; }
  .cmp-info-a { left: calc(0.75rem + 52px); top: 0.75rem; }
  .cmp-info-b { right: calc(0.75rem + 52px); top: 0.75rem; }
  .cmp-close { top: 0.75rem; right: 0.75rem; }
  .compare-stage { width: 100vw; height: 78vh; }
}

/* Compare picker (date strip + thumbnail grid) */
.cmp-picker-bg {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(10,10,10,0.85);
  z-index: 120;
  align-items: center;
  justify-content: center;
}
.cmp-picker-bg.open { display: flex; }
.cmp-picker {
  background: var(--surface);
  color: var(--text);
  border-radius: var(--radius);
  border: 1px solid var(--border);
  width: min(720px, 92vw);
  max-height: 80vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.cmp-picker-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.85rem 1rem;
  border-bottom: 1px solid var(--border);
  font-weight: 600;
}
.cmp-picker-close {
  background: none;
  border: 0;
  cursor: pointer;
  color: var(--text-dim);
  padding: 0.25rem;
  line-height: 0;
  border-radius: 6px;
}
.cmp-picker-close:hover { background: var(--surface-hover); color: var(--text); }
.cmp-picker-close svg { width: 18px; height: 18px; }
.cmp-picker-dates {
  display: flex;
  gap: 0.4rem;
  padding: 0.6rem 1rem;
  overflow-x: auto;
  border-bottom: 1px solid var(--border);
  flex: 0 0 auto;
}
.cmp-date-pill {
  flex: 0 0 auto;
  padding: 0.35rem 0.7rem;
  border-radius: 999px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  font-size: var(--text-xs);
  font-family: var(--font-mono);
  cursor: pointer;
  white-space: nowrap;
  color: var(--text-dim);
  transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease;
}
.cmp-date-pill:hover { background: var(--surface-hover); color: var(--text); }
.cmp-date-pill.active {
  background: var(--primary);
  border-color: var(--primary);
  color: #ffffff;
}
.cmp-picker-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
  gap: 0.4rem;
  padding: 0.75rem 1rem 1rem;
  overflow-y: auto;
}
.cmp-picker-grid .empty {
  grid-column: 1 / -1;
  padding: 1.5rem;
  text-align: center;
  color: var(--text-dim);
  font-size: var(--text-sm);
}
.cmp-thumb {
  position: relative;
  aspect-ratio: 4/3;
  border-radius: 8px;
  overflow: hidden;
  cursor: pointer;
  border: 2px solid transparent;
  background: var(--surface-2);
}
.cmp-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.cmp-thumb:hover { border-color: var(--primary); }
.cmp-thumb-time {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  background: linear-gradient(transparent, rgba(0,0,0,0.7));
  color: #ffffff;
  font-family: var(--font-mono);
  font-size: 10px;
  padding: 0.6rem 0.4rem 0.3rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ── 27. STATUS TAB (station detail) ────────────────────────────────────── */

.status-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1rem;
}
.status-metrics {
  display: flex;
  flex-direction: column;
}
.status-metric {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.5rem 0;
  font-size: var(--text-sm);
  border-bottom: 1px solid var(--border);
}
.status-metric:last-child { border-bottom: 0; }
.status-metric-label {
  color: var(--text-dim);
}
.status-metric-value {
  color: var(--text);
  font-weight: 500;
  font-family: var(--font-mono);
  font-size: calc(var(--text-sm) - 0.5px);
  text-align: right;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.status-metric-value.success { color: var(--success); }
.status-metric-value.danger  { color: var(--danger);  }
.status-metric-value.warn    { color: var(--warn);    }

/* Command button row inside status cards */
.cmd-btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0.5rem;
}

/* Command-history rows (inside a status card) */
.cmd-history-list {
  max-height: 240px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}
.cmd-history-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  border-bottom: 1px solid var(--border);
}
.cmd-history-row:last-child { border-bottom: 0; }
.cmd-history-row .cmd-type {
  color: var(--text);
  font-weight: 500;
}
.cmd-history-row .cmd-status {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--text-dim);
}
.cmd-history-row .cmd-status.completed { color: var(--success); }
.cmd-history-row .cmd-status.failed    { color: var(--danger);  }
.cmd-history-row .cmd-status.delivered { color: var(--warn);    }
.cmd-history-row .cmd-cancel {
  color: var(--danger);
  text-decoration: none;
  margin-left: 0.35rem;
  font-weight: 500;
}
.cmd-history-row .cmd-cancel:hover { text-decoration: underline; }

/* Station token display inside status */
.station-token {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-dim);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.55rem 0.7rem;
  word-break: break-all;
  user-select: all;
}

/* ── 28. DIAGNOSTIC REPORT MODAL ─────────────────────────────────────────── */

.diag-summary {
  display: flex;
  gap: 1.25rem;
  padding: 0.9rem 1rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  margin-bottom: 1rem;
}
.diag-summary-item {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}
.diag-summary-n {
  font-size: var(--text-xl);
  font-weight: 700;
  color: var(--text);
  line-height: 1.1;
  font-family: var(--font-mono);
}
.diag-summary-n.success { color: var(--success); }
.diag-summary-n.warn    { color: var(--warn);    }
.diag-summary-n.danger  { color: var(--danger);  }
.diag-summary-l {
  font-size: var(--text-xs);
  color: var(--text-dim);
  font-weight: 500;
}
.diag-meta {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-faint);
  margin-bottom: 1rem;
}
.diag-section { margin-bottom: 1rem; }
.diag-section h4 {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 0.5rem;
}
.diag-check {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-left-width: 3px;
  border-radius: var(--radius-sm);
  padding: 0.6rem 0.8rem;
  margin-bottom: 0.45rem;
}
.diag-check.ok      { border-left-color: var(--success); }
.diag-check.warning { border-left-color: var(--warn);    }
.diag-check.error   { border-left-color: var(--danger);  }
.diag-check.skipped { border-left-color: var(--text-faint); }
.diag-check-hdr {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem;
  margin-bottom: 0.25rem;
}
.diag-check-label {
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--text);
}
.diag-check-meta {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-faint);
}
.diag-check-detail {
  font-size: var(--text-sm);
  color: var(--text-dim);
  line-height: 1.45;
}
.diag-check details {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-dim);
  margin-top: 0.4rem;
}
.diag-check details summary {
  cursor: pointer;
  padding: 0.2rem 0;
  color: var(--text-dim);
}
.diag-check details pre {
  background: var(--bg);
  padding: 0.5rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  overflow: auto;
  max-height: 240px;
  margin-top: 0.3rem;
  white-space: pre-wrap;
  word-break: break-all;
}
.diag-empty {
  color: var(--text-dim);
  font-size: var(--text-sm);
  padding: 1rem;
  text-align: center;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

/* ── 29. SETTINGS TAB (station detail) ──────────────────────────────────── */

.settings-sections {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

/* Form grid — multiple short fields in a row, auto-wrapping. */
.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 0.9rem;
}

/* Toggle switch — relabeled primitive. The legacy .toggle.on selector
   migrates to :checked via an <input type="checkbox" class="toggle-input">
   paired with a .toggle-slider element. */
.toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  cursor: pointer;
  user-select: none;
}
.toggle input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
  pointer-events: none;
}
.toggle-slider {
  position: relative;
  display: inline-block;
  width: 36px;
  height: 20px;
  background: var(--border);
  border-radius: 999px;
  transition: background-color 180ms ease;
  flex-shrink: 0;
}
.toggle-slider::before {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  background: var(--surface);
  border-radius: 50%;
  box-shadow: var(--shadow-sm);
  transition: transform 180ms ease;
}
.toggle input:checked + .toggle-slider {
  background: var(--primary);
}
.toggle input:checked + .toggle-slider::before {
  transform: translateX(16px);
}
.toggle input:focus-visible + .toggle-slider {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}
.toggle-label {
  font-size: var(--text-sm);
  color: var(--text);
}

/* Danger-variant card — red border + red header color, used for
   "Danger zone" at the bottom of station settings. */
.card.card-danger {
  border-color: var(--danger);
}
.card.card-danger .card-header {
  border-bottom-color: var(--danger-soft);
}
.card.card-danger .card-header h2,
.card.card-danger .card-header h3 {
  color: var(--danger);
}

/* Schedules list (inside Schedules card) */
.schedule-list {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
.schedule-row {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.75rem 0.9rem;
}
.schedule-header {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.6rem;
}
.schedule-header .sched-name-input {
  flex: 1;
  background: transparent;
  border: 0;
  color: var(--text);
  font-size: var(--text-sm);
  font-weight: 600;
  padding: 0.2rem 0;
  outline: 0;
  min-width: 0;
}
.schedule-header .sched-name-input:focus {
  border-bottom: 1px solid var(--primary);
}
.schedule-fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: 0.7rem;
}

/* Per-schedule camera override (added 2026-05-04). Collapsed by
 * default; opens to a small grid of optional fields. Blank fields
 * inherit the global camera block — savePiConfig only sends filled
 * keys, so the Pi only sees an override when the operator typed
 * something. */
.schedule-camera-override {
  margin-top: 0.7rem;
  border-top: 1px dashed var(--border);
  padding-top: 0.55rem;
}
.schedule-camera-override > summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  padding: 0.25rem 0;
  user-select: none;
}
.schedule-camera-override > summary::-webkit-details-marker { display: none; }
.schedule-camera-override > summary::before {
  content: "▸";
  display: inline-block;
  width: 10px;
  color: var(--text-faint);
  transition: transform 160ms ease;
}
.schedule-camera-override[open] > summary::before {
  transform: rotate(90deg);
}
.schedule-camera-summary-label { flex: 1; }
.schedule-camera-summary-hint {
  color: var(--text-faint);
  font-weight: 400;
  letter-spacing: 0.1em;
}
.schedule-camera-fields {
  margin-top: 0.55rem;
  padding: 0.6rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

/* Auto-managed schedule (added 2026-05-05). Renders read-only with a
 * notice. The server expands this into multiple time-of-day windows
 * at update_config push time using lat/lng + sun position. */
.schedule-row-auto {
  border-left: 3px solid var(--success);
}
.schedule-row-auto .sched-name-input {
  font-weight: 600;
  color: var(--text);
}
.schedule-auto-notice {
  display: flex;
  gap: 0.6rem;
  align-items: flex-start;
  padding: 0.65rem 0.8rem;
  margin-top: 0.55rem;
  background: var(--success-soft);
  color: #3f5630;
  border-radius: var(--radius-sm);
  font-size: 12px;
  line-height: 1.45;
}
.schedule-auto-notice strong {
  color: #3f5630;
}
[data-theme="dark"] .schedule-auto-notice {
  background: rgba(135, 160, 107, 0.16);
  color: #cce0b4;
}
[data-theme="dark"] .schedule-auto-notice strong {
  color: #e5f0d4;
}

/* Settings shell (2026-05-05 redesign) — left rail nav + main column.
 * Operator complaint: 6 cards stacked all-visible was overwhelming.
 * Rail gives jump-anywhere navigation; IntersectionObserver flips
 * the active item as the operator scrolls. Below 880px the rail
 * collapses above the content (defined later in the section, search
 * "settings-shell mobile"). */
.settings-shell {
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: 1.5rem;
  align-items: start;
}
.settings-rail {
  position: sticky;
  top: 5rem;
  align-self: start;
  border-right: 1px dashed var(--border);
  padding: 0.5rem 0.5rem 0.5rem 0;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}
.settings-rail-label-group {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
  padding: 0.55rem 0.85rem 0.45rem;
}
.settings-rail-item {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.5rem 0.85rem;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-dim);
  border: 0;
  border-left: 3px solid transparent;
  background: transparent;
  text-decoration: none;
  cursor: pointer;
  text-align: left;
  width: 100%;
  font-weight: 500;
  transition: background-color 120ms ease, color 120ms ease,
              border-color 120ms ease;
}
.settings-rail-item:hover {
  background: var(--surface-2);
  color: var(--text);
}
.settings-rail-item.active {
  color: var(--primary);
  border-left-color: var(--primary);
  background: var(--primary-soft);
}
.settings-rail-item.settings-rail-danger { color: var(--danger); }
.settings-rail-item.settings-rail-danger.active {
  color: var(--danger);
  border-left-color: var(--danger);
  background: var(--danger-soft);
}
.settings-rail-dot {
  width: 6px; height: 6px; flex: none;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.55;
}
.settings-main {
  min-width: 0;
  /* Bottom padding so the sticky save bar doesn't cover the last
   * card's bottom edge when the operator scrolls to it. */
  padding-bottom: 5rem;
}

/* Settings section toggle (2026-05-05): only the section that
 * matches the active rail item is rendered. Default state via JS
 * showSettingsSection('sec-location') on first paint. Using
 * display:none rather than a class-gated visibility/opacity dance
 * because the contents are heavy (map / camera fields) and we want
 * them to truly drop out of the layout. */
.settings-section { display: none; }
.settings-section.is-active { display: block; }

/* Live video feed section layout (2026-05-05 redesign).
 * Operator feedback: the previous form-grid mixed checkbox-inline
 * fields with label-on-top fields, producing uneven heights and
 * misaligned baselines. New layout:
 *   - Hero toggle row at the top (the "is this on?" answer)
 *   - 3-up form grid with consistent label-on-top + helper text
 *   - Two sub-rows (proxy token + tailnet pin) with same shape:
 *     mono-uppercase label ┃ value pill ┃ action button
 */
.ls-hero-toggle {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: start;
  gap: 0.85rem;
  padding: 0.85rem 1rem;
  background: var(--surface-2);
  border-left: 3px solid var(--border-hover);
  border-radius: var(--radius);
  margin-bottom: 1.25rem;
  transition: border-left-color 160ms ease, background-color 160ms ease;
}
/* Border + tint follow the toggle state. CSS-only because the
 * checkbox is a sibling of the .ls-hero-toggle parent — we use
 * :has() (broad browser support post-2024) so the row turns lichen
 * on enabled, neutral on disabled. */
.ls-hero-toggle:has(input:checked) {
  border-left-color: var(--success);
  background: linear-gradient(90deg, var(--success-soft), var(--surface-2) 60%);
}
.ls-hero-toggle .toggle { padding-top: 0.15rem; }
.ls-hero-toggle-text strong {
  display: block;
  font-size: 0.95rem;
  font-weight: 500;
  color: var(--text);
  margin-bottom: 0.25rem;
}
.ls-hero-toggle-text p {
  margin: 0;
  line-height: 1.5;
}

/* Three-column config grid. Forces uniform field heights via
 * align-items:start on the parent and a min-height on each field
 * so the helper text doesn't pull rows out of alignment. */
.ls-config-grid {
  align-items: start;
  margin-bottom: 1.25rem;
}
.ls-config-grid .form-field {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
.ls-config-grid .form-field label:first-child {
  /* Force a consistent label height so the controls beneath line
   * up across all three columns even when the label is one line vs
   * two. */
  min-height: 1.25rem;
  display: flex;
  align-items: center;
  margin-bottom: 0;
}

/* Suffix-input pattern (e.g. "200 MB"). Lets the unit ride along
 * inside the input's visual frame so it doesn't fight for label
 * real estate. */
.ls-suffix-input {
  position: relative;
  display: flex;
  align-items: stretch;
}
.ls-suffix-input input {
  flex: 1;
  padding-right: 2.5rem;
}
.ls-suffix {
  position: absolute;
  right: 0.7rem;
  top: 50%;
  transform: translateY(-50%);
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
  pointer-events: none;
}

/* Inline toggle inside a form field — keeps the label-on-top
 * rhythm consistent with the select / number-input siblings. */
.ls-inline-toggle {
  align-self: flex-start;
  padding: 0.35rem 0;
}

/* Per-field helper text. Smaller + faint so it doesn't compete
 * with the actual control. */
.ls-field-hint {
  margin: 0;
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--text-faint);
}

/* Sub-section blocks for proxy token + tailnet pin. Same shape
 * for both: dashed top rule, label ┃ value ┃ button row, helper
 * text below. */
.ls-sub-section {
  margin-top: 1rem;
  padding-top: 0.85rem;
  border-top: 1px dashed var(--border);
}
.ls-sub-section + .ls-sub-section { margin-top: 0.85rem; }
.ls-sub-row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 0.4rem;
}
.ls-sub-label {
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
  white-space: nowrap;
}
.ls-sub-value {
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--text-dim);
  background: var(--surface-2);
  padding: 0.3rem 0.6rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.ls-sub-btn {
  flex: none;
  white-space: nowrap;
}

/* Phone — sub-row stacks: label on row 1, value full width on row
 * 2, button on row 3. Hero toggle keeps its 2-column shape since
 * the toggle is small. Form-grid already collapses via Section 20. */
@media (max-width: 600px) {
  .ls-sub-row {
    grid-template-columns: 1fr;
    gap: 0.4rem;
  }
  .ls-sub-row .ls-sub-btn {
    justify-self: start;
  }
}

/* Phone: collapse the rail above the content as a horizontal
 * scroller of pills so it doesn't eat vertical real estate. */
@media (max-width: 880px) {
  .settings-shell {
    grid-template-columns: 1fr;
    gap: 0.85rem;
  }
  .settings-rail {
    position: static;
    border-right: 0;
    border-bottom: 1px dashed var(--border);
    padding: 0;
    flex-direction: row;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x proximity;
  }
  .settings-rail-label-group { display: none; }
  .settings-rail-item {
    border-left: 0;
    border-bottom: 3px solid transparent;
    padding: 0.55rem 0.75rem;
    flex: 0 0 auto;
    scroll-snap-align: start;
    white-space: nowrap;
  }
  .settings-rail-item.active {
    border-left-color: transparent;
    border-bottom-color: var(--primary);
  }
}

/* Save bar — sticky bottom of the settings-main column. The single
 * place a save bar belongs is at the bottom of the form; mid-page
 * was a usability bug. (2026-05-05 redesign — was a static row
 * earlier despite the comment claiming sticky.) */
.settings-save-bar {
  position: sticky;
  bottom: 0;
  z-index: 5;
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  padding: 0.85rem 0;
  background: linear-gradient(0deg, var(--bg) 80%, transparent);
  margin-top: 0.5rem;
}

/* ── 30. PROFILE PAGE ─────────────────────────────────────────────────────── */

.profile-layout {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 1.25rem;
  max-width: 900px;
}
.profile-info {
  display: flex;
  flex-direction: column;
}
.profile-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--border);
}
.profile-row:last-child { border-bottom: 0; }
.profile-label {
  font-size: var(--text-sm);
  color: var(--text-dim);
}
.profile-value {
  font-size: var(--text-sm);
  color: var(--text);
  font-weight: 500;
  font-family: var(--font-mono);
  text-align: right;
}
.profile-value.plain { font-family: var(--font-sans); }

/* ═══════════════════════════════════════════════════════════════════════════
   §31 — Admin page helpers
   ─────────────────────────────────────────────────────────────────────────── */

/* Flush body (removes inner padding so a .table reaches the card's edges) */
.card-body-flush { padding: 0; }
.card-body-flush .table { border-radius: 0; }

/* Role badges (reuse the generic .badge shape with role-specific palettes) */
.badge-role-admin   { background: var(--primary-soft); color: var(--primary); }
.badge-role-manager { background: var(--warn-soft);    color: var(--warn);    }
.badge-role-viewer  { background: var(--surface-2);    color: var(--text-dim); }

/* Right-aligned action cell for a row's trailing button(s) */
.table-actions { text-align: right; white-space: nowrap; }

/* Narrow form grid — single column, max ~420px — for modals / settings panes
   where side-by-side fields would feel cramped. */
.form-grid-narrow {
  grid-template-columns: 1fr;
  max-width: 420px;
}

/* Danger-ghost button: red text and border, transparent fill; fills in on hover.
   Used for low-weight destructive actions in dense tables. */
.btn-danger-ghost {
  background: transparent;
  color: var(--danger);
  border: 1px solid var(--danger);
}
.btn-danger-ghost:hover {
  background: var(--danger);
  color: #fff;
}

/* ═══════════════════════════════════════════════════════════════════════════
   §32 — Videos tab (station detail): compile jobs, videos grid, player modal,
   compile wizard
   ─────────────────────────────────────────────────────────────────────────── */

/* Outer wrapper for the entire Videos tab. */
.videos-tab {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

/* Header row (title + "New video" button). */
.videos-tab-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}
.videos-tab-title {
  font-size: var(--text-xl);
  font-weight: 600;
  margin: 0;
}

/* ── Jobs section (in-progress / queued / recently finished) ─────────────── */

.jobs-section {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}
.jobs-section .section-header {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin: 0;
}
.jobs-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

/* A single job row. Left border colour-coded by status. */
.job-row {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.85rem 1rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-left-width: 3px;
  border-radius: var(--radius-sm);
}
.job-row-running { border-left-color: var(--primary); }
.job-row-queued  { border-left-color: var(--text-faint); }
.job-row-done    { border-left-color: var(--success); }
.job-row-failed  { border-left-color: var(--danger); }

.job-row-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}
.job-row-title {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.job-row-meta {
  font-size: var(--text-xs);
  color: var(--text-dim);
}
.job-row-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-shrink: 0;
}
.job-row-progress {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-shrink: 0;
}

/* Thin progress bar for running jobs. */
.progress-bar {
  width: 200px;
  height: 6px;
  background: var(--surface-2);
  border-radius: 999px;
  overflow: hidden;
}
.progress-bar-fill {
  height: 100%;
  background: var(--primary);
  border-radius: inherit;
  transition: width 300ms ease;
}

/* ── Videos grid ─────────────────────────────────────────────────────────── */

.videos-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 1rem;
}

.video-card {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: hidden;
  cursor: pointer;
  transition: border-color 150ms ease, transform 150ms ease, box-shadow 150ms ease;
}
.video-card:hover {
  border-color: var(--border-hover);
  transform: translateY(-1px);
  box-shadow: var(--shadow);
}

.video-card-thumb {
  position: relative;
  aspect-ratio: 16 / 9;
  background: var(--surface-2);
  overflow: hidden;
}
.video-card-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.video-card-thumb-placeholder {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-faint);
  font-size: var(--text-sm);
}
.video-card-duration {
  position: absolute;
  right: 0.5rem;
  bottom: 0.5rem;
  padding: 0.15rem 0.45rem;
  background: rgba(0,0,0,0.72);
  color: #fff;
  font-size: var(--text-xs);
  font-weight: 600;
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
}

.video-card-body {
  padding: 0.75rem 0.85rem;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.video-card-title {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.video-card-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  font-size: var(--text-xs);
  color: var(--text-dim);
}
.video-card-preset {
  padding: 0.08rem 0.4rem;
  background: var(--primary-soft);
  color: var(--primary);
  border-radius: var(--radius-sm);
  font-weight: 600;
  text-transform: capitalize;
}
.video-card-date { font-family: var(--font-mono); }

/* Public-visibility toggle in the top-right of the thumbnail. Hidden by
   default and fades in on card hover; stays pinned visible once the video
   is actually public so the state is always discoverable. */
.video-public-toggle {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.6);
  color: #fff;
  border: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity 150ms ease, background 150ms ease;
  padding: 0;
}
.video-card:hover .video-public-toggle { opacity: 1; }
.video-public-toggle:hover { background: rgba(0, 0, 0, 0.8); }
.video-public-toggle.is-public {
  opacity: 1;
  background: var(--primary);
}
.video-public-toggle.is-public:hover { background: var(--primary-hover); }

/* Inline "Public" badge in the card meta row — mirrors .video-card-preset
   but uses the success tint so it reads as a status, not a classification. */
.video-public-badge {
  display: inline-block;
  padding: 0.08rem 0.4rem;
  border-radius: var(--radius-sm);
  background: var(--primary-soft);
  color: var(--primary);
  font-size: var(--text-xs);
  font-weight: 600;
}

/* Brief highlight when scrollToVideo() targets a card. */
.video-card-flash {
  animation: videoCardFlash 1.6s ease-out;
}
@keyframes videoCardFlash {
  0%   { box-shadow: 0 0 0 0 var(--primary); }
  30%  { box-shadow: 0 0 0 4px var(--primary-soft); }
  100% { box-shadow: 0 0 0 0 transparent; }
}

/* ── Wide modal variant (video player + wizard use 800px instead of 440px) */

.modal-wide {
  width: min(800px, calc(100vw - 2rem));
  max-width: none;
}

/* Meta rows under the <video> element in the player modal. */
.video-modal-meta {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  margin-top: 0.75rem;
}
.video-meta-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  font-size: var(--text-sm);
}
.video-meta-label {
  color: var(--text-dim);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* ── Wizard steps (2-step compile flow) ──────────────────────────────────── */

.wizard-steps {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 1rem;
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-faint);
}
.wizard-step {
  padding: 0.25rem 0.6rem;
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--text-dim);
  font-weight: 600;
}
.wizard-step-active {
  background: var(--primary-soft);
  color: var(--primary);
}
.wizard-step-sep {
  flex: 1;
  height: 1px;
  background: var(--border);
}

/* Radio-option groups (mode select + preset select). */
.wizard-mode-options,
.wizard-preset-options {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
.wizard-mode-option,
.wizard-preset-option {
  display: flex;
  gap: 0.75rem;
  padding: 0.75rem 0.85rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: border-color 120ms ease, background 120ms ease;
}
.wizard-mode-option:hover,
.wizard-preset-option:hover {
  border-color: var(--border-hover);
}
.wizard-mode-option:has(input:checked),
.wizard-preset-option:has(input:checked) {
  border-color: var(--primary);
  background: var(--primary-soft);
}
.wizard-mode-option input[type="radio"],
.wizard-preset-option input[type="radio"] {
  margin-top: 0.2rem;
  flex-shrink: 0;
}
.wizard-mode-body,
.wizard-preset-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}
.wizard-mode-title,
.wizard-preset-title {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text);
}
.wizard-mode-desc,
.wizard-preset-desc {
  font-size: var(--text-xs);
  color: var(--text-dim);
}
.wizard-mode-field {
  margin-top: 0.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
.wizard-mode-field label {
  font-size: var(--text-xs);
  color: var(--text-dim);
}

/* Step-2 summary of the selections from step 1. */
.wizard-summary {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 0.75rem 0.85rem;
  background: var(--surface-2);
  border-radius: var(--radius-sm);
  margin-bottom: 1rem;
}
.wizard-summary-label {
  font-size: var(--text-xs);
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.wizard-summary-value {
  font-size: var(--text-sm);
  color: var(--text);
  font-weight: 500;
}

/* ═══════════════════════════════════════════════════════════════════════════
   11. SITE NOTES — STRUCTURAL POLISH (Apr 28 2026)
   ═══════════════════════════════════════════════════════════════════════════
   This block applies the typographic and density discipline from the v1
   redesign mockup on top of the existing component CSS. It targets the
   real class names already in the markup; rules placed here win over the
   earlier definitions via source-order specificity. Revert by deleting
   this section.

   Concepts in play:
     - Cards: one border OR one shadow, never both. Drop the dual-shadow.
     - Numerics, identifiers, timestamps, file sizes: ALL mono.
     - Section headers and tags: mono uppercase, letterspacing 0.08em.
     - Density: tighter padding on cards/tiles, smaller meta text. The
       result feels like an instrument panel instead of a SaaS dashboard.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Cards — single edge ─────────────────────────────────────────────── */
.card { box-shadow: none; }
.card-interactive:hover {
  box-shadow: var(--shadow-sm);
  border-color: var(--primary);
}
.card-header { padding: 0.85rem 1.1rem; }
.card-header h3,
.card-header > h2 {
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.card-body { padding: 1.1rem; }

/* ── Sidebar ─────────────────────────────────────────────────────────── */
.app-sidebar { padding: 1.1rem 0.7rem 1rem; }
.app-sidebar-brand { margin-bottom: 1.4rem; }
.app-sidebar-brand .wordmark {
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: -0.01em;
}
.app-nav { gap: 1px; }
.app-nav-item {
  padding: 0.45rem 0.6rem;
  font-size: 0.83rem;
  border-radius: var(--radius-sm);
}
.app-nav-item .nav-icon { width: 16px; height: 16px; }
.theme-toggle {
  padding: 0.45rem 0.6rem;
  font-size: 0.78rem;
  letter-spacing: 0.04em;
}

/* User badge in sidebar footer */
.user-badge {
  font-family: var(--font-mono) !important;
  font-size: 10.5px !important;
  letter-spacing: 0.04em;
  color: var(--text-dim);
}

/* ── Topbar ──────────────────────────────────────────────────────────── */
.app-topbar { padding: 0.7rem 1.5rem; }
.breadcrumbs {
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: 0.04em;
  color: var(--text-dim);
}
.breadcrumbs-sep { color: var(--text-faint); margin: 0 0.35rem; }
.breadcrumbs-current { color: var(--text); }
.topbar-search {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.4rem 0.7rem;
  font-size: 12.5px;
}
.topbar-icon-btn {
  width: 32px;
  height: 32px;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  transition: all 120ms ease;
}
.topbar-icon-btn:hover {
  background: var(--surface-2);
  border-color: var(--border);
  color: var(--text);
}

/* ── Page header pattern ─────────────────────────────────────────────── */
.page-header {
  padding-bottom: 0.85rem;
  border-bottom: 1px dashed var(--border);
  margin-bottom: 1.5rem;
}
.page-title {
  font-size: 1.5rem;
  font-weight: 500;
  letter-spacing: -0.02em;
}
/* Subtitle is a register change from the title, not a quieter same-size sans
   line. Mono caps + tracking puts it in the same family as .card-header h3
   ("drafting-block tag" convention) — see docs/design-system.md. The eye
   reads title and subtitle as different *kinds* of information, not as
   "same thing in two colors" (audit M2). */
.page-subtitle {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin-top: 0.4rem;
}
/* Breadcrumb back-button — was a low-contrast link, now a proper bordered
   button so the operator's eye picks it up at the top of every detail page.
   Same family as .st-detail-back (the back-button on Status / Settings).
   Site Notes mono uppercase + clay accent on hover, same as the other
   instrument-row controls. */
.page-breadcrumb-back {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  height: 30px;
  padding: 0 0.75rem;
  box-sizing: border-box;
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.06em;
  line-height: 1;
  text-transform: uppercase;
  color: var(--text-dim);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  margin-bottom: 0.85rem;
  cursor: pointer;
  text-decoration: none;
  transition: color 120ms ease, border-color 120ms ease, background 120ms ease;
}
.page-breadcrumb-back:hover {
  color: var(--primary);
  border-color: var(--primary);
  background: var(--primary-soft);
}
.page-breadcrumb-back svg { stroke-width: 2; width: 12px; height: 12px; flex: 0 0 auto; }

/* ── Buttons — slightly tighter ──────────────────────────────────────── */
.btn {
  border-radius: var(--radius-sm);
  font-weight: 500;
  font-size: 0.825rem;
  padding: 0.45rem 0.85rem;
  transition: all 120ms ease;
}
.btn-sm  { font-size: 0.75rem; padding: 0.3rem 0.6rem; }
.btn-lg  { font-size: 0.9rem;  padding: 0.6rem 1.1rem; }
.btn-secondary {
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
}
.btn-secondary:hover {
  background: var(--surface-2);
  border-color: var(--border-hover);
}

/* ── Badges — mono uppercase tabular ─────────────────────────────────── */
.badge {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 0.15rem 0.45rem;
  border-radius: 2px;
  font-variant-numeric: tabular-nums;
  border: 1px solid transparent;
}
.badge-primary { background: var(--primary-soft); color: var(--primary-hover); border-color: var(--primary); }
.badge-success { background: var(--success-soft); color: #56723f;             border-color: var(--success); }
.badge-warn    { background: var(--warn-soft);    color: #8a5e1e;             border-color: var(--warn); }
.badge-danger  { background: var(--danger-soft);  color: #8a3a30;             border-color: var(--danger); }

/* ── Tabs ────────────────────────────────────────────────────────────── */
.tab-item { font-size: 0.85rem; padding: 0.55rem 1rem; letter-spacing: 0; }
.tab-item.active { font-weight: 500; border-bottom-width: 2px; }

/* ── Activity feed — mono timestamps, structured row ─────────────────── */
.activity-list { gap: 0; }
.activity-item {
  display: grid;
  grid-template-columns: 80px 8px 1fr;
  gap: 0.7rem;
  padding: 0.6rem 1rem;
  border-bottom: 1px solid var(--border);
  align-items: baseline;
}
.activity-item:last-child { border-bottom: 0; }
.activity-time {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.04em;
  color: var(--text-faint);
  text-transform: lowercase;
}
.activity-dot { align-self: center; width: 6px; height: 6px; }
.activity-message { font-size: 0.83rem; line-height: 1.45; }

/* ── Status / Instrument-panel tiles ─────────────────────────────────── */
.status-grid,
.status-metrics,
.stat-grid { gap: 0.7rem; }
.status-metric,
.stat-card {
  padding: 0.85rem 1rem;
  border-radius: var(--radius);
  position: relative;
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: none;
}
/* Traffic-light borders — only when explicitly classed */
.status-metric.is-ok::before,
.status-metric.is-warn::before,
.status-metric.is-bad::before,
.stat-card.is-ok::before,
.stat-card.is-warn::before,
.stat-card.is-bad::before {
  content: '';
  position: absolute;
  top: 0; left: 0; bottom: 0;
  width: 3px;
}
.status-metric.is-ok::before,   .stat-card.is-ok::before   { background: var(--success); }
.status-metric.is-warn::before, .stat-card.is-warn::before { background: var(--warn); }
.status-metric.is-bad::before,  .stat-card.is-bad::before  { background: var(--danger); }

.status-metric-label,
.stat-card-label,
.stat-label {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.status-metric-value,
.stat-card-value,
.stat-value {
  font-family: var(--font-mono);
  font-size: 1.5rem;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
  color: var(--text);
  line-height: 1.1;
  margin-top: 0.2rem;
}
.stat-card-sub, .stat-sub {
  font-size: 11.5px;
  color: var(--text-dim);
  margin-top: 0.25rem;
}

/* ── Project cards ───────────────────────────────────────────────────── */
.project-card {
  border-radius: var(--radius);
  box-shadow: none;
  transition: border-color 120ms ease;
}
.project-card:hover { border-color: var(--primary); }
.project-card-cover { aspect-ratio: 16 / 9; }
.project-card-body { padding: 0.85rem 0.95rem 0.95rem; }
.project-card-title {
  font-size: 0.95rem;
  font-weight: 500;
  letter-spacing: -0.01em;
}
.project-card-desc { font-size: 0.78rem; color: var(--text-dim); }
.project-card-meta {
  margin-top: 0.65rem;
  padding-top: 0.55rem;
  border-top: 1px dashed var(--border);
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.2rem;
}
.project-card-stat-value {
  font-family: var(--font-mono);
  font-size: 0.95rem;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.project-card-stat { display: flex; flex-direction: column; }

/* ── Station card (redesigned 2026-05-05) ─────────────────────────────────
 * Latest-capture thumbnail at the top + 4px left-edge color strip that
 * aggregates station health into one signal so the operator can spot a
 * problem station at a glance across the project grid. Single source of
 * truth for the colour: health_state on the API response → is-ok /
 * is-warn / is-bad / is-cold class on the .station-card.
 *
 * Health rules (computed in api_list_stations):
 *   ok    everything healthy → lichen (DK's "smoothly running" green)
 *   warn  disk ≥ 70 OR cpu ≥ 65 OR config drift → hi-vis amber
 *   bad   offline / disk ≥ 85 / failed command / many upload failures
 *         → brick red
 *   cold  never checked in (no signal yet) → faint gray
 */
.station-card {
  /* Override the legacy .card padding/shadow — image is flush. */
  display: grid;
  grid-template-rows: auto 1fr;
  padding: 0;
  overflow: hidden;
  border-left-width: 4px;
  box-shadow: none;
  transition: border-color 120ms ease;
}
.station-card.is-ok    { border-left-color: var(--success); }   /* lichen */
.station-card.is-warn  { border-left-color: var(--warn); }      /* hi-vis */
.station-card.is-bad   { border-left-color: var(--danger); }    /* brick */
.station-card.is-cold  { border-left-color: var(--text-faint); } /* gray */
.station-card:hover { border-color: var(--primary); }
/* Re-apply the health colour on the LEFT edge after hover changes the
 * border colour for the rest of the card — left edge stays the health
 * signal, the other 3 edges become the clay primary on hover. */
.station-card.is-ok:hover    { border-left-color: var(--success); }
.station-card.is-warn:hover  { border-left-color: var(--warn); }
.station-card.is-bad:hover   { border-left-color: var(--danger); }
.station-card.is-cold:hover  { border-left-color: var(--text-faint); }

/* Faint health tint over the body so the colour reads even on dark
 * mode where the 4px strip can disappear into the surrounding card. */
.station-card.is-ok   .station-card-body { background: linear-gradient(0deg, rgba(135,160,107,0.06), transparent 40%); }
.station-card.is-warn .station-card-body { background: linear-gradient(0deg, rgba(212,148,63,0.08), transparent 40%); }
.station-card.is-bad  .station-card-body { background: linear-gradient(0deg, rgba(178,90,77,0.10), transparent 40%); }

.station-card-thumb {
  aspect-ratio: 16 / 9;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  position: relative;
  overflow: hidden;
}
.station-card-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.station-card-thumb-empty {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.station-card-body {
  padding: 0.75rem 0.95rem 0.85rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
.station-card-header {
  display: flex; align-items: center; gap: 0.5rem;
  min-width: 0;
}
.station-card-name {
  font-size: 0.95rem;
  font-weight: 500;
  letter-spacing: -0.01em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.station-card-loc {
  font-size: 0.8rem;
  color: var(--text-dim);
  line-height: 1.3;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.station-card-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 0.85rem;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.04em;
  color: var(--text-dim);
  margin-top: 0.15rem;
}
.station-card-meta .meta-item {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.station-card-meta .meta-label {
  color: var(--text-faint);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 9.5px;
  margin-right: 0.15rem;
}
.station-card-meta .meta-value {
  color: var(--text);
  font-weight: 500;
}
.station-token {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--text-faint);
  letter-spacing: 0.04em;
}

/* ── Gallery ─────────────────────────────────────────────────────────── */
.gallery-day-header {
  padding-bottom: 0.65rem;
  border-bottom: 1px dashed var(--border);
  margin-bottom: 0.85rem;
}
.gallery-day-header > h2,
.gallery-day-header > h3 {
  font-size: 1rem;
  font-weight: 500;
  letter-spacing: -0.01em;
}
.gallery-day-actions {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.04em;
}
.gallery-filters {
  display: inline-flex;
  gap: 2px;
  padding: 2px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.filter-chip {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.04em;
  padding: 0.25rem 0.55rem;
  border-radius: 3px;
  color: var(--text-dim);
  background: transparent;
  border: 0;
  cursor: pointer;
}
.filter-chip:hover { color: var(--text); }
.filter-chip.active {
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  margin: -1px;
}

/* Date list rail */
.date-list { gap: 0; }
.date-item {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-start;
  gap: 2px;
  padding: 0.5rem 0.85rem;
  font-size: 0.8rem;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
}
.date-item-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}
.date-item:last-child { border-bottom: 0; }
.date-item.active {
  background: var(--primary-soft);
  color: var(--primary-hover);
  font-weight: 500;
  border-color: var(--primary);
}
.date-item-count {
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-variant-numeric: tabular-nums;
  /* Count is load-bearing data, not decoration — promote from --text-faint
     to --text-dim so it reads cleanly on the chip's --surface-2 pill bg. */
  color: var(--text-dim);
}
/* Active row: chip bg is --primary (clay) per section 1 line 1825-1827.
   Section 11 had set this to --primary-hover (darker clay), which collapsed
   to ~1.6:1 contrast — invisible. Restore white for clear contrast on clay. */
.date-item.active .date-item-count { color: #ffffff; }
/* Per-day data line: mono, faint, MB + HH:MM → HH:MM. Drafting-block
   convention — gives the date rail more density than just count + name. */
.date-item-meta {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.04em;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
  line-height: 1.3;
}
.date-item.active .date-item-meta { color: var(--primary-hover); opacity: 0.7; }

/* Image (capture) card */
.image-card {
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  transition: all 120ms ease;
}
.image-card:hover { border-color: var(--primary); }
.image-card-info {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
}
.image-card-size {
  font-family: var(--font-mono);
  font-size: 9.5px;
  font-variant-numeric: tabular-nums;
  color: var(--text-faint);
}

/* ── Markup card ─────────────────────────────────────────────────────── */
.markup-card { box-shadow: none; transition: border-color 120ms ease; }
.markup-card:hover { border-color: var(--primary); }
.markup-marks {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}
.markup-when { font-size: 0.85rem; font-weight: 500; }
.markup-meta {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.04em;
  color: var(--text-dim);
}
.markup-by { font-family: var(--font-mono); font-size: 10px; color: var(--text-faint); }

/* ── Video card ──────────────────────────────────────────────────────── */
.video-card { box-shadow: none; }
.video-card:hover { border-color: var(--primary); }
.video-card-title {
  font-size: 0.9rem;
  font-weight: 500;
  letter-spacing: -0.01em;
}
.video-card-meta {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.04em;
  color: var(--text-dim);
}
.video-card-preset,
.video-card-date,
.video-card-duration {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 10.5px;
}

/* ── Command history (queue/log) ─────────────────────────────────────── */
.cmd-history-list {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
.cmd-history-row {
  display: grid;
  /* id (mono fixed) | type (flex) | status (auto) | time (mono fixed) */
  grid-template-columns: 56px 1fr auto 76px;
  gap: 0.85rem;
  padding: 0.65rem 0.95rem;
  border-bottom: 1px solid var(--border);
  font-size: 0.8rem;
  align-items: center;
}
.cmd-history-row:last-child { border-bottom: 0; }
.cmd-history-row:hover { background: var(--surface-2); }
.cmd-history-row > *:first-child {
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--text-faint);
  letter-spacing: 0.04em;
}
.cmd-type { font-weight: 500; font-family: var(--font-sans); }
.cmd-status {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* ── Login screen polish ─────────────────────────────────────────────── */
/* Section 17 — Login split-pane (2026-04-28).
   Left pane: drafting-grid backdrop (engineering paper). Right pane: the
   existing card. Falls back to single-column on narrow viewports — the art
   pane hides entirely so a phone user just sees the form. */
.login-screen {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  justify-content: stretch;
  padding: 0;
  min-height: 100vh;
  background: var(--bg);
}
.login-pane-art {
  flex: 1 1 60%;
  position: relative;
  background-color: var(--surface-2);
  /* Drafting grid: 100px major (stronger), 20px minor (faint).
     color-mix lets us reuse --border so dark/light themes both look right.
     The radial overlay adds the same clay glow the landing hero uses, so
     login + landing read as one design. */
  background-image:
    radial-gradient(900px 500px at 35% 25%,
      color-mix(in srgb, var(--primary-soft) 80%, transparent),
      transparent 65%),
    linear-gradient(to right,
      color-mix(in srgb, var(--border) 80%, transparent) 1px,
      transparent 1px),
    linear-gradient(to bottom,
      color-mix(in srgb, var(--border) 80%, transparent) 1px,
      transparent 1px),
    linear-gradient(to right,
      color-mix(in srgb, var(--border) 32%, transparent) 1px,
      transparent 1px),
    linear-gradient(to bottom,
      color-mix(in srgb, var(--border) 32%, transparent) 1px,
      transparent 1px);
  background-size:
    auto auto,
    100px 100px, 100px 100px,
    20px 20px, 20px 20px;
  background-position:
    0 0,
    -1px -1px, -1px -1px,
    -1px -1px, -1px -1px;
  border-right: 1px solid var(--border);
  overflow: hidden;
}
.login-art-block {
  position: absolute;
  left: clamp(2rem, 5vw, 4rem);
  bottom: clamp(2rem, 5vh, 3.5rem);
  max-width: 32ch;
  padding: 1.4rem 1.5rem 1.5rem;
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}
.login-art-eyebrow {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--primary);
  font-weight: 500;
}
.login-art-title {
  margin-top: 0.35rem;
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--text);
}
.login-art-list {
  margin: 0.85rem 0 0;
  padding: 0;
  list-style: none;
  font-size: var(--text-sm);
  color: var(--text-dim);
  line-height: 1.55;
}
.login-art-list li {
  padding-left: 1.1rem;
  position: relative;
}
.login-art-list li::before {
  content: '—';
  position: absolute;
  left: 0;
  color: var(--text-faint);
}
.login-art-stamp {
  margin-top: 0.85rem;
  padding-top: 0.55rem;
  border-top: 1px dashed var(--border);
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.login-pane-form {
  flex: 0 1 480px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}
/* Theme toggle in the login brand row — small ghost button, sits in
   the natural reading-end of the brand line. localStorage 'dk-theme'
   is shared with SPA + landing so flips persist across nav. */
.login-theme-toggle {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text-dim);
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
  transition: border-color 120ms ease, color 120ms ease;
}
.login-theme-toggle:hover {
  border-color: var(--primary);
  color: var(--text);
}
@media (max-width: 880px) {
  .login-pane-art { display: none; }
  .login-pane-form { flex: 1 1 100%; }
}

.login-card { box-shadow: var(--shadow); border-radius: var(--radius-md); }
.login-title {
  font-size: 1.4rem;
  font-weight: 500;
  letter-spacing: -0.02em;
}
.login-sub { font-size: 0.8rem; color: var(--text-dim); }

/* ── Wizard summary mono ────────────────────────────────────────────── */
.wizard-summary-label {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.1em;
}

/* ── Diagnostic panel ────────────────────────────────────────────────── */
.diag-meta {
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
}
.diag-section h4 {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
  font-weight: 500;
}

/* ── Forms — tighter ─────────────────────────────────────────────────── */
.form-field label,
.form-grid label,
.form-grid-narrow label {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.form-hint { font-size: 11px; color: var(--text-faint); }

/* ── Modal — tightening ─────────────────────────────────────────────── */
.modal-header { padding: 0.95rem 1.1rem; border-bottom: 1px solid var(--border); }
.modal-title {
  font-size: 1rem;
  font-weight: 500;
  letter-spacing: -0.01em;
}
.modal-body   { padding: 1.1rem; }
.modal-footer { padding: 0.85rem 1.1rem; background: var(--surface-2); }

/* ── Empty state — quieter ───────────────────────────────────────────── */
.empty-state-title {
  font-size: 0.95rem;
  font-weight: 500;
  letter-spacing: -0.01em;
}
.empty-state-body { font-size: 0.83rem; color: var(--text-dim); }

/* ── Job rows (compile worker queue) ─────────────────────────────────── */
.job-row { border-radius: var(--radius-sm); }
.job-row-meta {
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
}
.job-row-title { font-size: 0.85rem; font-weight: 500; }

/* ── Smart Search result cards mono ─────────────────────────────────── */
.ss-card-when,
.ss-card-meta {
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
}

/* ── Progress photo rows mono ───────────────────────────────────────── */
.pp-row-meta {
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-variant-numeric: tabular-nums;
}

/* ── Compare overlay date pills ─────────────────────────────────────── */
.cmp-date-pill {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
}

/* ── Enrollment code ─────────────────────────────────────────────────── */
.enroll-code {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.06em;
}
.enroll-status,
.enroll-expiry {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* ═══════════════════════════════════════════════════════════════════════════
   End of structural polish
   ═══════════════════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════════════
   12. SITE NOTES — POLISH ROUND 2 (Apr 28 2026)
   Addresses critique findings: kill the SaaS-style hover lifts on cards,
   add the missing chrome elements (sidebar group label, Cmd+K bar look,
   station live pulse pill), bump badge text contrast.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Hover-lift kills ────────────────────────────────────────────────── */
/* Original .card-interactive:hover (line 502) and .project-card:hover
   (line 810) re-add box-shadow + transform: translateY(-1px). That
   contradicts the Site Notes "single edge, no lift" rule. Border-color
   shift on hover is enough — and matches the mockup's mood. */
.card-interactive:hover,
.project-card:hover,
.station-card:hover,
.markup-card:hover,
.video-card:hover,
.image-card:hover {
  transform: none;
  box-shadow: none;
}

/* ── Card-header h2 too — original (line 514) sets font-size: var(--text-lg)
   font-weight: 600 for both h2 and h3. The polish layer (section 11)
   only retargeted h3, leaving h2 with the SaaS treatment. ─────────────── */
.card-header h2 {
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
}

/* ── Badge success contrast bump ─────────────────────────────────────── */
/* Original polish set .badge-success text to #56723f on lichen-soft
   #d8e2c8 — passes AA at ~4.9:1 but borderline for 10px small text.
   Bump to #3f5630 for ~6.5:1 safety margin. */
.badge-success { color: #3f5630; }

/* ── Sidebar nav group label ─────────────────────────────────────────── */
/* Mono uppercase, tracked, dim. Used as a section divider above nav
   items in .app-nav (e.g. "Workspace"). Acts as a drafting-block
   convention rather than a heading. */
.shell-nav-label {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
  padding: 0.55rem 0.6rem 0.3rem;
  font-weight: 500;
}
.shell-nav-label:not(:first-child) {
  margin-top: 0.4rem;
  border-top: 1px dashed var(--border);
  padding-top: 0.65rem;
}

/* ── Topbar search → Cmd+K bar look ──────────────────────────────────── */
/* The existing .topbar-search markup is <div><svg/><input/></div>.
   We add a <span class="cmd-kbd">⌘ K</span> on the right. CSS-only
   visual; no JS handler attached for v1 (typing in the box still works
   as the live search-as-you-type the input already does). */
.topbar-search {
  position: relative;
}
.topbar-search input {
  font-family: var(--font-sans);
  font-size: 12.5px;
}
.topbar-search input::placeholder {
  color: var(--text-faint);
}
.cmd-kbd {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.04em;
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text-dim);
  padding: 0.05rem 0.35rem;
  border-radius: 3px;
  margin-left: auto;
  flex-shrink: 0;
  pointer-events: none;
  user-select: none;
}

/* Search results dropdown — anchored to .topbar-search via position:absolute.
   Single-edge bone surface; floating control so a small shadow is allowed.
   Hidden by default; opens when results are present (.open). */
.topbar-search-results {
  display: none;
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  background: var(--surface);
  border: 1px solid var(--border-hover);
  border-radius: var(--radius);
  box-shadow: 0 6px 22px rgba(42,38,32,0.12);
  max-height: 60vh;
  overflow-y: auto;
  z-index: 50;
  padding: 0.3rem 0;
}
[data-theme="dark"] .topbar-search-results {
  box-shadow: 0 6px 22px rgba(0,0,0,0.5);
}
.topbar-search-results.open { display: block; }
.tsr-section {
  font-family: var(--font-mono);
  font-size: 9.5px;
  font-weight: 500;
  letter-spacing: 0.1em;
  color: var(--text-faint);
  padding: 0.5rem 0.85rem 0.25rem;
  text-transform: uppercase;
}
.tsr-item {
  display: grid;
  grid-template-columns: 56px 1fr;
  grid-template-areas: "kind label" "kind sub";
  column-gap: 0.5rem;
  align-items: baseline;
  padding: 0.4rem 0.85rem;
  cursor: pointer;
  transition: background-color 100ms ease;
}
.tsr-item:hover,
.tsr-item.is-highlight {
  background: var(--surface-2);
}
.tsr-kind {
  grid-area: kind;
  font-family: var(--font-mono);
  font-size: 9.5px;
  font-weight: 500;
  letter-spacing: 0.1em;
  color: var(--text-faint);
  align-self: center;
}
.tsr-label {
  grid-area: label;
  font-family: var(--font-sans);
  font-size: 0.84rem;
  color: var(--text);
  font-weight: 500;
}
.tsr-sub {
  grid-area: sub;
  font-family: var(--font-sans);
  font-size: 11.5px;
  color: var(--text-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.tsr-empty {
  padding: 0.85rem;
  font-family: var(--font-sans);
  font-size: 0.82rem;
  color: var(--text-dim);
  text-align: center;
}

/* ── Live pulse pill (next to station name) ──────────────────────────── */
.live-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-weight: 500;
  padding: 0.2rem 0.5rem 0.2rem 0.45rem;
  background: var(--success-soft);
  color: #3f5630;
  border: 1px solid var(--success);
  border-radius: var(--radius-sm);
  vertical-align: middle;
  margin-left: 0.65rem;
  position: relative;
  top: -2px;
}
.live-pill-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--success);
  box-shadow: 0 0 0 2px rgba(135, 160, 107, 0.25);
  animation: live-pulse 2s ease-in-out infinite;
}
@keyframes live-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: 0.55; transform: scale(0.85); }
}
@media (prefers-reduced-motion: reduce) {
  .live-pill-dot { animation: none; }
}

/* ── Sidebar system-status panel (admin only) ─────────────────────────
   Mono register at the top of .app-sidebar-footer. Hidden by default;
   loadSidebarHealth() unhides for admin users after the /api/admin/
   health probe lands. Reads as an instrument-panel header. */
.sidebar-system-status {
  padding: 0.55rem 0.5rem 0.65rem;
  margin-bottom: 0.55rem;
  border-bottom: 1px dashed var(--border);
  display: flex;
  flex-direction: column;
  gap: 3px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.04em;
}
.sss-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 6px;
}
.sss-label  { color: var(--text-faint); }
.sss-value  {
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.sss-value.is-ok   { color: var(--success); }
.sss-value.is-warn { color: var(--warn); }
.sss-value.is-bad  { color: var(--danger); }

/* ═══════════════════════════════════════════════════════════════════════════
   13. SITE NOTES — STATUS TAB INSTRUMENT PANEL (Apr 28 2026)
   Step 6 of the migration. The Status tab on a station detail page used
   to be three category cards (Connection / System / Activity) of plain
   .status-metric rows. This converts it to a flat instrument grid where
   every metric is a tile with mono-numeric value, mono-uppercase label,
   traffic-light borders on threshold breach, and an optional sub-line.
   The command + diagnostic histories drop their card chrome and become
   tabular logs.
   ═══════════════════════════════════════════════════════════════════════════ */

/* The outer .status-grid wrapper is now a column of sections. The actual
   instrument tiles live inside .instrument-grid. */
.status-grid {
  display: flex;
  flex-direction: column;
  gap: 1.2rem;
}

.instrument-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 0.65rem;
}

/* Override the original .status-metric rule (line 3025+) which was a
   horizontal label/value strip inside a card. New tile is vertical:
   tiny mono uppercase label on top, big mono numeric value below,
   optional dim mono-ish sub-line. Border-left state strip via .is-* */
.status-metric {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-start;
  gap: 0.15rem;
  padding: 0.85rem 0.95rem 0.95rem 1.05rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  position: relative;
  overflow: hidden;
  font-size: inherit;
}
.status-metric:last-child { border-bottom: 1px solid var(--border); }

.status-metric-label {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
  font-weight: 500;
}
.status-metric-value {
  font-family: var(--font-mono);
  font-size: 1.5rem;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
  color: var(--text);
  line-height: 1.1;
  margin-top: 0.2rem;
}
.status-metric-sub {
  display: block;
  font-size: 11px;
  color: var(--text-dim);
  margin-top: 0.3rem;
  font-family: var(--font-mono);
  letter-spacing: 0.02em;
}

/* Status-section: a labeled block (Commands / Command history / etc.)
   without the card chrome. Reads as a drafting-block heading on top
   of a flat region. */
.status-section {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
.status-section-head {
  display: flex;
  flex-direction: column;        /* stacked: title row above meta line */
  align-items: stretch;
  gap: 0.2rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px dashed var(--border);
}
.status-section-head > span:first-child {
  /* title — mono uppercase tracked, drafting-block convention */
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text);
  font-weight: 500;
}
.status-section-meta {
  /* description — a normal sentence under the heading. NOT uppercase,
     NOT tracked; otherwise long sentences read as one stretched ribbon
     across the screen. */
  font-family: var(--font-sans);
  font-size: 12px;
  color: var(--text-dim);
  letter-spacing: 0;
  text-transform: none;
  font-weight: 400;
  line-height: 1.5;
}
.status-section-meta code {
  font-family: var(--font-mono);
  font-size: 11px;
  background: var(--bg);
  border: 1px solid var(--border);
  padding: 1px 5px;
  border-radius: 2px;
  color: var(--text);
}

/* Two-column layout for command + diagnostic history */
.status-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}
@media (max-width: 980px) { .status-split { grid-template-columns: 1fr; } }

/* Action-row inside the Commands section — mirrors .cmd-btn-row but
   without the surrounding card-body padding the original had. */
.cmd-action-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* Empty placeholder for cmd-history-list when nothing yet */
.cmd-history-empty {
  padding: 0.85rem 1rem;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-faint);
  letter-spacing: 0.04em;
}

/* The cmd-history-row was 2-cell in the old markup; the new markup
   emits 4 cells (id / type / status / time). Section-12 already
   defined the grid, but the .cmd-status semantic state needs colors
   for completed / pending / failed. */
.cmd-status.is-ok   { color: var(--success); }
.cmd-status.is-warn { color: var(--warn); }
.cmd-status.is-bad  { color: var(--danger); }
.cmd-time {
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--text-faint);
  letter-spacing: 0.04em;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Station-token block — promoted from a card to a flat section. */
.station-token {
  font-family: var(--font-mono);
  font-size: 12.5px;
  color: var(--text);
  letter-spacing: 0.04em;
  word-break: break-all;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.7rem 0.95rem;
  user-select: all;
  -webkit-user-select: all;
  font-variant-numeric: tabular-nums;
}

/* ═══════════════════════════════════════════════════════════════════════════
   15. STATUS TAB — CAMERA-FIRST LAYOUT (Apr 28 2026)
   Most-recent capture as a small-but-prominent hero, vertical rail of
   status chips alongside, commands + history below. Hero is sized to
   match the surrounding chrome rather than dominating the viewport —
   the photo is one element among many, not the whole page.
   ═══════════════════════════════════════════════════════════════════════════ */
.nvr-layout {
  display: grid;
  /* Hero sized to be visually balanced with the chip rail rather than
     overwhelming it. ~360px hero + flexible rail that grids its chips
     into 2-3 columns depending on available width. */
  grid-template-columns: minmax(0, 360px) 1fr;
  gap: 1rem;
  align-items: start;
}
@media (max-width: 760px) {
  .nvr-layout { grid-template-columns: 1fr; }
}
.nvr-hero {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.nvr-img-link { display: block; line-height: 0; }
.nvr-img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}
.nvr-img-meta {
  padding: 0.5rem 0.7rem 0.55rem;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem;
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--text-dim);
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
  border-top: 1px solid var(--border);
  flex-wrap: wrap;
}
.nvr-img-time { color: var(--text); font-weight: 500; }
.nvr-img-file { color: var(--text-faint); }
.nvr-img-empty {
  padding: 4rem 1rem;
  text-align: center;
  color: var(--text-faint);
  font-family: var(--font-mono);
  font-size: 12px;
  font-style: italic;
}
.nvr-rail {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 0.5rem;
  align-self: stretch;
}
.nvr-chip {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.55rem 0.7rem 0.6rem 0.8rem;
  position: relative;
  overflow: hidden;
}
.nvr-chip.is-ok::before, .nvr-chip.is-warn::before, .nvr-chip.is-bad::before {
  content: ''; position: absolute; top: 0; left: 0; bottom: 0; width: 3px;
}
.nvr-chip.is-ok::before   { background: var(--success); }
.nvr-chip.is-warn::before { background: var(--warn); }
.nvr-chip.is-bad::before  { background: var(--danger); }
.nvr-chip-label {
  display: block;
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.nvr-chip-val {
  display: block;
  font-family: var(--font-mono);
  font-size: 14px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  margin-top: 0.15rem;
}


/* ═══════════════════════════════════════════════════════════════════════════
   16. POST-NVR POLISH (Apr 28 2026)
   Bumps button presence + spreads the semantic palette across surfaces
   that were previously grayscale-only. DK's note: "we barely use all the
   colors" — addressed by promoting state into cmd/diag history rows,
   bigger high-contrast command buttons, and rail-chip value coloring
   when state is non-ok.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Bigger commands buttons — risk colors (.btn-primary / .btn-warn /
   .btn-danger) now carry the visual weight, and bigger size makes them
   read as primary actions on the page rather than chrome. */
.cmd-action-row { gap: 0.6rem; }
.cmd-action-row .btn {
  font-size: 0.875rem;
  padding: 0.55rem 1.1rem;
  font-weight: 600;
  letter-spacing: 0.01em;
}

/* Rail chip value coloring when state is breached — currently the strip
   on the left signals state but the value text stays default ink. Color
   the value too so it pops against the surrounding chips. */
.nvr-chip.is-warn .nvr-chip-val { color: #8a5e1e; }
.nvr-chip.is-bad  .nvr-chip-val { color: #8a3a30; }
.nvr-chip.is-ok   .nvr-chip-val { color: #3f5630; }

/* Cmd / diag history rows — left border state strip via :has() so the
   row reads as "this command finished OK / this one failed" at a glance.
   Modern browsers (Chrome 105+, Safari 15.4+, Firefox 121+) support
   :has(); older browsers fall back to plain rows (graceful). */
.cmd-history-row { border-left: 3px solid transparent; }
.cmd-history-row:has(.cmd-status.is-ok)   { border-left-color: var(--success); }
.cmd-history-row:has(.cmd-status.is-warn) { border-left-color: var(--warn); }
.cmd-history-row:has(.cmd-status.is-bad)  { border-left-color: var(--danger); }

/* Date-item active state — bump the left strip from primary-soft fill
   to a clear clay border so the palette is visible at the rail level. */
.date-item.active {
  border-left: 3px solid var(--primary);
  padding-left: calc(0.85rem - 3px);
}


/* ═══════════════════════════════════════════════════════════════════════════
   17. STATUS TAB — NVR FILMSTRIP (Apr 28 2026)
   Horizontal strip of the day's recent captures below the hero. Each
   cell is a clay-bordered thumbnail linking to the full image. The
   most recent capture (rightmost) gets highlighted so the eye
   completes the connection back to the hero card above it.
   ═══════════════════════════════════════════════════════════════════════════ */
.nvr-filmstrip-wrap {
  margin-top: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}
.nvr-filmstrip-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.6rem;
  padding-bottom: 0.4rem;
  border-bottom: 1px dashed var(--border);
}
.nvr-filmstrip-label {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text);
  font-weight: 500;
}
.nvr-filmstrip-more {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.06em;
  color: var(--primary);
  cursor: pointer;
  border-bottom: 1px dotted var(--primary);
  text-transform: uppercase;
  text-decoration: none;
}
.nvr-filmstrip-more:hover {
  color: var(--primary-hover);
  border-bottom-style: solid;
}

.nvr-filmstrip {
  display: flex;
  gap: 0.4rem;
  overflow-x: auto;
  padding-bottom: 0.3rem;
  /* Subtle scrollbar styling — no scrollbar in default state on
     macOS/Windows; appears on hover/scroll. */
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}
.nvr-filmstrip::-webkit-scrollbar { height: 6px; }
.nvr-filmstrip::-webkit-scrollbar-track { background: transparent; }
.nvr-filmstrip::-webkit-scrollbar-thumb {
  background: var(--border);
  border-radius: 3px;
}

.nvr-filmstrip-cell {
  flex-shrink: 0;
  width: 96px;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--surface);
  text-decoration: none;
  position: relative;
  transition: border-color 120ms ease;
}
.nvr-filmstrip-cell:hover { border-color: var(--primary); }
.nvr-filmstrip-cell img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  background: var(--bg);
}
.nvr-filmstrip-time {
  display: block;
  font-family: var(--font-mono);
  font-size: 9px;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
  color: var(--text-dim);
  padding: 0.2rem 0.4rem 0.25rem;
  text-align: center;
  border-top: 1px solid var(--border);
  background: var(--surface);
}

/* The latest capture gets a clay border + filled label so the eye
   connects it to the hero. Same color language as the live-pill. */
.nvr-filmstrip-cell.is-latest {
  border-color: var(--primary);
  box-shadow: 0 0 0 1px var(--primary);
}
.nvr-filmstrip-cell.is-latest .nvr-filmstrip-time {
  background: var(--primary-soft);
  color: var(--primary-hover);
  font-weight: 600;
}

/* ═══════════════════════════════════════════════════════════════════════════
   18. PI CONFIG-DRIFT WARNING PILL (Apr 28 2026)
   Hi-vis warn variant of .live-pill, shown next to a station's name when
   the Pi's reported camera config has diverged from the server's
   expected pi_config (versions match → it's drift, not a pending push).
   Clicking 're-sync' fires resyncStationConfig() which calls the same
   POST /api/stations/<sid>/pi-config endpoint as Save station settings,
   bumping config_version and queuing an update_config command.
   ═══════════════════════════════════════════════════════════════════════════ */
.drift-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-weight: 500;
  padding: 0.2rem 0.55rem;
  background: var(--warn-soft);
  color: #6b4818;
  border: 1px solid var(--warn);
  border-radius: var(--radius-sm);
  vertical-align: middle;
  margin-left: 0.5rem;
  position: relative;
  top: -2px;
}
.drift-pill-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--warn);
  box-shadow: 0 0 0 2px rgba(212, 148, 63, 0.25);
  animation: live-pulse 2s ease-in-out infinite;
}
.drift-pill-cta {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--primary);
  text-decoration: none;
  border-bottom: 1px dotted var(--primary);
  padding-bottom: 1px;
  margin-left: 0.3rem;
  cursor: pointer;
}
.drift-pill-cta:hover {
  color: var(--primary-hover);
  border-bottom-style: solid;
}
@media (prefers-reduced-motion: reduce) {
  .drift-pill-dot { animation: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   19. ADMIN BRANDING SECTION (Apr 28 2026)
   2-column layout: current logo preview on the left, file picker + upload
   button on the right. Lives in the Branding card on /admin/users.
   ═══════════════════════════════════════════════════════════════════════════ */
.branding-row {
  display: grid;
  grid-template-columns: minmax(0, 320px) 1fr;
  gap: 1.5rem;
  align-items: start;
}
@media (max-width: 720px) {
  .branding-row { grid-template-columns: 1fr; }
}
.branding-current {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
.form-field-label {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  font-weight: 500;
}
.branding-preview-frame {
  border: 1px dashed var(--border);
  border-radius: var(--radius-sm);
  padding: 1rem;
  background: var(--bg);
  min-height: 90px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.branding-preview-frame img {
  max-height: 96px;
  max-width: 100%;
  height: auto;
  width: auto;
  display: block;
}
.branding-upload {
  display: flex;
  flex-direction: column;
}

/* ═══════════════════════════════════════════════════════════════════════════
   20. LOGO INVERT IN LIGHT MODE (Apr 28 2026)
   When the admin toggles 'Invert logo in light mode' in /admin/users, the
   server writes class="invert-logo-light" onto <body> at render time and
   this rule applies filter: invert(1) to every logo image — but only when
   the active theme is light (data-theme is unset or 'light'). Dark mode is
   intentionally untouched: a logo designed for a dark background already
   reads correctly there.
   ═══════════════════════════════════════════════════════════════════════════ */
body.invert-logo-light img[src^="/branding/logo"] {
  filter: invert(1);
}
[data-theme="dark"] body.invert-logo-light img[src^="/branding/logo"],
html[data-theme="dark"] body.invert-logo-light img[src^="/branding/logo"] {
  filter: none;
}

/* Branding-card preview frames — second frame shows the logo on an ink
   bg so admins can see how it reads in both modes side-by-side without
   actually toggling the theme. */
.branding-preview-frame-dark {
  margin-top: 0.5rem;
  background: var(--text);
}
.branding-preview-frame-dark img {
  /* Mirror the live invert behavior in the dark preview when the toggle
     is OFF: dark-mode page shows the logo as-uploaded. So the dark
     preview is a flat 'as-uploaded' view regardless of the toggle. */
  filter: none !important;
}
/* Top preview tracks the live light-mode rendering — invert applies if
   the toggle is on, since that's what visitors will see. */
body.invert-logo-light .branding-preview-frame:not(.branding-preview-frame-dark) img {
  filter: invert(1);
}

.branding-toggle-label {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  font-size: 0.875rem;
  color: var(--text);
  cursor: pointer;
  user-select: none;
}
.branding-toggle-label input[type="checkbox"] {
  width: 16px;
  height: 16px;
  accent-color: var(--primary);
  cursor: pointer;
}
.branding-toggle-label code {
  font-family: var(--font-mono);
  font-size: 11px;
  background: var(--bg);
  border: 1px solid var(--border);
  padding: 1px 4px;
  border-radius: 2px;
}

/* ═══════════════════════════════════════════════════════════════════════════
   Section 18 — Focus indicators (2026-04-29).

   Pre-this-section, only inputs and the toggle slider had visible focus
   rings. Buttons, tabs, nav items, image cards, video cards, markup cards,
   command-history rows, date items — every other interactive element fell
   through to the platform's default focus indication, which is invisible
   against the aged-paper bg on most browsers.

   :focus-visible (not :focus) means mouse clicks don't trigger the ring;
   only keyboard navigation does. Outline + offset is the simplest, most
   robust approach: works under any background, doesn't fight the single-
   edge card convention (no shadow lift), and disappears cleanly on blur.
   WCAG 2.4.7 (Focus Visible) — Level AA.
   ═══════════════════════════════════════════════════════════════════════════ */

.btn:focus-visible,
.tab-item:focus-visible,
.app-nav-item:focus-visible,
.theme-toggle:focus-visible,
.sidebar-toggle:focus-visible,
.topbar-icon-btn:focus-visible,
.image-card:focus-visible,
.markup-card:focus-visible,
.video-card:focus-visible,
.date-item:focus-visible,
.cmd-history-row:focus-visible,
.activity-item:focus-visible,
.project-card:focus-visible,
.station-card:focus-visible,
.filter-chip:focus-visible,
.ss-preset:focus-visible,
.showcase-card:focus-visible,
.ann-tool-btn:focus-visible,
.ann-swatch:focus-visible,
.ann-size:focus-visible,
.video-public-toggle:focus-visible,
.image-card-delete:focus-visible,
.cmd-cancel:focus-visible,
a:focus-visible,
[onclick]:focus-visible,
[role="button"]:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
  /* Inner 1px backdrop-coloured halo keeps the ring readable against
     elements whose own border is also clay-on-hover. */
  box-shadow: 0 0 0 1px var(--bg);
}
/* Inside .modal: same ring, slightly stronger so the dimmed backdrop
   doesn't wash it out. */
.modal-bg .btn:focus-visible,
.modal-bg .input:focus-visible,
.modal-bg .select:focus-visible,
.modal-bg .textarea:focus-visible,
.modal-bg [role="button"]:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 3px;
}

/* ═══════════════════════════════════════════════════════════════════════════
   MAP VIEW — projects map, project-detail stations map, station-pin picker
   (2026-04-30, "Site Map" feature)

   Three surfaces, one visual language:
     • Projects page   — Grid/Map toggle above .projects-layout. Map shows
                         project centroids; hover → tooltip listing stations.
     • Project page    — Grid/Map toggle above .stations-grid. Map shows the
                         pinned stations of one project, zoomed to bbox.
     • Station Settings — Location card with click-to-pin Leaflet picker.

   Tile chrome is tinted via a subtle CSS filter so CartoDB Voyager warms
   slightly toward the aged-paper palette without losing legibility.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Center-top segmented toggle. Mono uppercase, single edge, clay active. */
.view-toggle-row {
  display: flex;
  justify-content: center;
  margin: .5rem 0 1rem;
}
.view-toggle {
  display: inline-flex;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 3px;
  gap: 2px;
  box-shadow: none;
}
.view-toggle-btn {
  appearance: none;
  -webkit-appearance: none;
  border: 0;
  background: transparent;
  color: var(--text-dim);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: .45rem 1.1rem;
  border-radius: calc(var(--radius) - 3px);
  cursor: pointer;
  transition: background-color .12s ease, color .12s ease;
}
.view-toggle-btn:hover { color: var(--text); }
/* Active state is a *selection* indicator, not a CTA. Reuse the same soft-clay
   treatment as .app-nav-item.active so clay-saturated CTAs (Run, Save) keep
   their semantic weight in the same field of view (audit m1). */
.view-toggle-btn.is-active {
  background: var(--primary-soft);
  color: var(--primary);
}
.view-toggle-btn:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

/* Map containers — same single-edge treatment as cards. */
.projects-map-wrap,
.project-map-wrap {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 240px;
  gap: 1rem;
}
.projects-map,
.project-map {
  position: relative;
  height: 540px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
.loc-map {
  position: relative;
  height: 360px;
  margin: .85rem 0;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

/* Subtle tile tint — warm Voyager toward aged paper without breaking
   legibility. Dark theme uses CartoDB DarkMatter tiles (designed for dark
   surrounds), so no filter inversion is needed there. Satellite mode
   (Esri photographic tiles) opts out of the warm tint via the
   data-map-layer="satellite" attribute set on <html> by JS — imagery
   stays true-colour. (Audit M6 + satellite-overlay 2026-04-30.) */
.leaflet-tile {
  filter: saturate(0.88) brightness(0.99);
}
[data-theme="dark"] .leaflet-tile,
[data-map-layer="satellite"] .leaflet-tile {
  filter: none;
}

/* Floating Map / Satellite control — top-right corner of every map.
   Styled like .view-toggle but sits over the tile area, so a small
   shadow is allowed (it's a *floating* control, not a card). */
.map-layer-toggle {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 3px;
  display: inline-flex;
  gap: 2px;
  box-shadow: 0 1px 4px rgba(42,38,32,0.18);
  margin: 8px;
}
[data-theme="dark"] .map-layer-toggle {
  box-shadow: 0 1px 4px rgba(0,0,0,0.5);
}
.map-layer-btn {
  appearance: none;
  -webkit-appearance: none;
  border: 0;
  background: transparent;
  color: var(--text-dim);
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: .35rem .75rem;
  border-radius: calc(var(--radius) - 3px);
  cursor: pointer;
  transition: background-color .12s ease, color .12s ease;
  line-height: 1;
}
.map-layer-btn:hover { color: var(--text); }
.map-layer-btn.is-active {
  background: var(--primary-soft);
  color: var(--primary);
}
.map-layer-btn:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

/* Leaflet attribution + zoom controls — tone to Site Notes. */
.leaflet-control-attribution {
  background: rgba(246, 239, 220, 0.85) !important;
  font-family: var(--font-mono);
  font-size: 9.5px !important;
  color: var(--text-faint) !important;
  letter-spacing: 0.04em;
}
[data-theme="dark"] .leaflet-control-attribution {
  background: rgba(33, 29, 24, 0.88) !important;
  color: var(--text-faint) !important;
}
.leaflet-control-attribution a { color: var(--text-dim) !important; }
.leaflet-control-zoom a {
  background: var(--surface) !important;
  color: var(--text) !important;
  border-color: var(--border) !important;
}
.leaflet-control-zoom a:hover {
  background: var(--surface-2) !important;
}

/* Pulse pin — DivIcon. Three layered spans:
     .map-pin-pulse — animated halo (scale + fade)
     .map-pin-dot   — fixed shape (state-dependent)
     .map-pin-glyph — center glyph for redundant cue
   ok  = filled disc + ✓ glyph (lichen)
   bad = outlined ring + ! glyph (brick)
   Color + shape + glyph are three independent channels — passes the
   Ch 8 redundant-cue rule for color-blind operators. */
.map-pin {
  position: relative;
  width: 28px;
  height: 28px;
}
.map-pin-dot {
  position: absolute;
  inset: 5px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 2px solid var(--surface);
  box-sizing: border-box;
}
.map-pin-glyph {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 700;
  color: var(--surface);
  pointer-events: none;
  line-height: 1;
}
.map-pin-pulse {
  position: absolute;
  inset: 0;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  opacity: 0.55;
  animation: mapPinPulse 2.2s ease-out infinite;
}
/* OK: filled disc, lichen, ✓ glyph. */
.map-pin.is-ok .map-pin-dot   { background: var(--success); }
.map-pin.is-ok .map-pin-pulse { background: var(--success); }
/* BAD: outlined ring (transparent fill), brick stroke, ! glyph. The shape
   change — solid vs ring — is the redundant cue independent of hue. */
.map-pin.is-bad .map-pin-dot {
  background: var(--surface);
  border-color: var(--danger);
  border-width: 4px;
}
.map-pin.is-bad .map-pin-pulse { background: var(--danger); }
.map-pin.is-bad .map-pin-glyph { color: var(--danger); }
@keyframes mapPinPulse {
  0%   { transform: scale(0.7); opacity: 0.55; }
  70%  { transform: scale(2.1); opacity: 0; }
  100% { transform: scale(2.1); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .map-pin-pulse { animation: none; opacity: 0.35; transform: scale(1.3); }
}

/* Tooltip shell — Leaflet wraps our HTML in .leaflet-tooltip; override the
   chrome so it matches Site Notes (single-edge bone surface, mono labels).
   Single edge: 1px border, no shadow. Border bumped to --border-hover so the
   tooltip stays legible against any tile colour. */
.leaflet-tooltip.map-tooltip-shell {
  background: var(--surface);
  border: 1px solid var(--border-hover);
  border-radius: var(--radius);
  color: var(--text);
  padding: 0;
  box-shadow: none;
  font-family: var(--font-sans);
}
.leaflet-tooltip.map-tooltip-shell::before { display: none; }  /* kill the default arrow */
.map-tooltip {
  padding: .65rem .8rem;
  min-width: 180px;
  max-width: 260px;
}
.map-tooltip-title {
  font-family: var(--font-sans);
  font-size: .85rem;
  font-weight: 500;
  color: var(--text);
  margin-bottom: .15rem;
}
.map-tooltip-sub {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--text-faint);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.map-tooltip-list {
  list-style: none;
  margin: .55rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: .25rem;
}
/* Status row: dot + name + mono caps status word. Three channels — colour,
   shape (solid vs ring) and text — so colour-blind operators don't lose
   meaning. Mirrors the .cmd-history-row pattern (Ch 8 redundant-cue rule,
   audit M4). */
.map-tooltip-row {
  display: grid;
  grid-template-columns: 10px 1fr auto;
  align-items: center;
  gap: .5rem;
  font-family: var(--font-sans);
  font-size: .78rem;
  color: var(--text);
}
.map-tooltip-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--text-faint);
  border: 1px solid transparent;
  box-sizing: border-box;
}
.map-tooltip-row.is-ok  .map-tooltip-dot { background: var(--success); }
/* BAD = ring shape, not filled — redundant with hue. */
.map-tooltip-row.is-bad .map-tooltip-dot {
  background: transparent;
  border: 2px solid var(--danger);
}
.map-tooltip-tag {
  font-family: var(--font-mono);
  font-size: 9.5px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.map-tooltip-row.is-ok  .map-tooltip-tag { color: var(--success); }
.map-tooltip-row.is-bad .map-tooltip-tag { color: var(--danger); }

/* Map empty states — when no project / station has a pin yet. */
.map-empty {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 2rem;
  color: var(--text-dim);
}
.map-empty-title {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin-bottom: .4rem;
}
.map-empty-body {
  font-size: .85rem;
  max-width: 360px;
  line-height: 1.5;
}

/* Side panel listing unpinned projects/stations. */
.projects-map-aside,
#project-map-aside {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: .85rem;
  display: flex;
  flex-direction: column;
  gap: .55rem;
  align-self: start;
  min-height: 0;
  max-height: 540px;
  overflow-y: auto;
}
.map-aside-label {
  font-family: var(--font-mono);
  font-size: 9.5px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.map-aside-list {
  display: flex;
  flex-direction: column;
  gap: .3rem;
}
.map-aside-item {
  appearance: none;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: .5rem .65rem;
  text-align: left;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: .15rem;
  transition: border-color .12s ease;
}
.map-aside-item:hover { border-color: var(--primary); }
.map-aside-name {
  font-family: var(--font-sans);
  font-size: .82rem;
  color: var(--text);
}
.map-aside-meta {
  font-family: var(--font-mono);
  font-size: 9.5px;
  color: var(--text-faint);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.map-aside-hint {
  font-size: 11px;
  line-height: 1.5;
  color: var(--text-faint);
  border-top: 1px dashed var(--border);
  padding-top: .55rem;
}

/* Station Settings → Location card: editable coordinate inputs + actions.
 * Replaced the read-only label/value grid with proper text inputs so
 * operators can type coordinates directly (in addition to clicking on the
 * map). The inputs are wired to onLocCoordInput() which parses + validates
 * on change and drops the marker. The map's click and drag handlers also
 * write back into the inputs (focus-aware, so they don't clobber what
 * you're typing). */
.loc-coord-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: .8rem;
  margin-bottom: .55rem;
}
.loc-coord-input {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: .9rem;
  letter-spacing: -.005em;
}
.loc-readout-hint {
  font-size: 11px;
  color: var(--text-faint);
  margin: 0 0 .55rem;
  font-style: italic;
}
.loc-actions {
  display: flex;
  gap: .5rem;
  align-items: center;
  flex-wrap: wrap;
}
@media (max-width: 540px) {
  .loc-coord-grid { grid-template-columns: 1fr; }
}

/* Responsive — collapse the side panel below the map on narrow viewports. */
@media (max-width: 880px) {
  .projects-map-wrap,
  .project-map-wrap {
    grid-template-columns: 1fr;
  }
  .projects-map-aside,
  #project-map-aside {
    max-height: none;
  }
  .projects-map,
  .project-map {
    height: 420px;
  }
}

/* ─────────────────────────────────────────────────────────────────────────
 * 19 — Live video feed (2026-05-04)
 *
 * The live-pill in the title row becomes a clickable button when the
 * station has live_stream.enabled. Two states:
 *   .live-pill-btn (idle)     — lichen pulse + clay play triangle
 *   .live-pill-btn.streaming  — clay pulse + clay stop square
 *
 * The overlay anchors below the stats strip by default (compact pane,
 * keeps the rest of the station page visible) and can toggle to
 * full-screen via the fullscreen button. Mono-uppercase chrome to match
 * the Site Notes drafting-instrument feel.
 * ───────────────────────────────────────────────────────────────────── */

button.live-pill-btn {
  /* Inherits .live-pill base styling. Override to make it interactive. */
  cursor: pointer;
  font: inherit;            /* normalize away <button>'s default font */
  letter-spacing: 0.08em;   /* slightly more spaced when it's a CTA */
  padding: 0.2rem 0.55rem 0.2rem 0.5rem;
  transition: background-color 0.12s ease, border-color 0.12s ease,
              color 0.12s ease;
}
button.live-pill-btn:hover {
  background: var(--success);
  color: #fff;
}
button.live-pill-btn:hover .live-pill-dot {
  background: #fff;
  box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.3);
}

.live-pill-icon {
  width: 8px;
  height: 8px;
  display: inline-block;
  vertical-align: -1px;
  color: var(--primary);     /* clay play triangle on lichen ground */
}
button.live-pill-btn:hover .live-pill-icon {
  color: #fff;
}

/* Active-stream state — operator is currently viewing. Click stops. */
button.live-pill-btn.streaming {
  background: var(--primary-soft);
  color: #6e3a25;
  border-color: var(--primary);
}
button.live-pill-btn.streaming .live-pill-dot {
  background: var(--primary);
  box-shadow: 0 0 0 2px rgba(196, 106, 69, 0.30);
}
button.live-pill-btn.streaming .live-pill-icon {
  color: var(--primary);
}
button.live-pill-btn.streaming:hover {
  background: var(--primary);
  color: #fff;
}

/* ── Stream overlay ──────────────────────────────────────────────────── */

.live-overlay {
  /* Compact mode: fixed-position floating panel in the lower-right.
   * Body-attached so it survives station-header re-renders. Doesn't
   * cover the page so operator can keep working below it. */
  position: fixed;
  right: 1.2rem;
  bottom: 1.2rem;
  width: min(720px, calc(100vw - 2.4rem));
  max-height: min(560px, calc(100vh - 2.4rem));
  background: var(--surface);
  border: 1px solid var(--border-hover);
  border-radius: var(--radius);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18),
              0 0 0 1px rgba(0, 0, 0, 0.04);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  z-index: 950;          /* below fullscreen (1000), above page chrome */
}
[data-theme="dark"] .live-overlay {
  box-shadow: 0 14px 40px rgba(0, 0, 0, 0.55);
}

/* Full-screen mode — pinned to viewport, dim backdrop for focus.
 * Override every constraint the base compact rule sets (right/bottom
 * anchor, width cap, max-height cap) — `inset: 0` alone wouldn't
 * release the width/height caps. */
.live-overlay.fullscreen {
  position: fixed;
  inset: 0;
  right: 0;
  bottom: 0;
  width: 100vw;
  max-width: none;
  max-height: none;
  height: 100vh;
  z-index: 1000;
  margin: 0;
  border-radius: 0;
  border: none;
  background: rgba(0, 0, 0, 0.92);
  box-shadow: none;
}
[data-theme="dark"] .live-overlay.fullscreen {
  background: rgba(0, 0, 0, 0.96);
}

.live-overlay-head {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.55rem 0.8rem;
  border-bottom: 1px solid var(--border);
  background: var(--surface-2);
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  flex-wrap: wrap;
}
.live-overlay.fullscreen .live-overlay-head {
  background: rgba(34, 31, 26, 0.82);
  color: #d8cfb6;
  border-bottom-color: rgba(255, 255, 255, 0.08);
}

.live-overlay-title {
  font-weight: 500;
  color: var(--text);
  letter-spacing: 0.1em;
}
.live-overlay.fullscreen .live-overlay-title { color: #ede5ce; }

.live-overlay-spacer { flex: 1; }

.live-overlay-stat {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 10.5px;
  color: var(--text-dim);
  letter-spacing: 0.04em;
}
.live-overlay-stat .lo-label {
  color: var(--text-faint);
  text-transform: uppercase;
  margin-right: 0.3em;
}
.live-overlay-stat .lo-value { color: var(--text); }
.live-overlay.fullscreen .live-overlay-stat,
.live-overlay.fullscreen .live-overlay-stat .lo-label {
  color: #b8ad97;
}
.live-overlay.fullscreen .live-overlay-stat .lo-value { color: #ede5ce; }

.live-overlay-btn {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.3rem 0.6rem;
  background: transparent;
  color: var(--text-dim);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background-color 0.12s, color 0.12s, border-color 0.12s;
}
.live-overlay-btn:hover {
  background: var(--primary);
  color: #fff;
  border-color: var(--primary);
}
.live-overlay-btn.is-close:hover {
  background: var(--danger);
  border-color: var(--danger);
}

.live-overlay-warn {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.35rem 0.8rem;
  background: var(--warn-soft);
  color: #6c4615;
  border-bottom: 1px solid var(--warn);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-weight: 500;
}
.live-overlay-warn::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--warn);
  flex: none;
  animation: live-pulse 2s ease-in-out infinite;
}

.live-overlay-stage {
  position: relative;
  background: #000;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 240px;
  flex: 1;
}
.live-overlay-img {
  display: block;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
}
.live-overlay.fullscreen .live-overlay-stage {
  flex: 1 1 auto;
}
.live-overlay.fullscreen .live-overlay-img {
  max-height: calc(100vh - 100px);  /* leave room for head + footer */
}

.live-overlay-empty,
.live-overlay-error {
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #b8ad97;
  text-align: center;
  padding: 2rem 1rem;
}
.live-overlay-error { color: #d8a896; }

.live-overlay-foot {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.5rem 0.8rem;
  border-top: 1px solid var(--border);
  background: var(--surface-2);
  flex-wrap: wrap;
}
.live-overlay.fullscreen .live-overlay-foot {
  background: rgba(34, 31, 26, 0.82);
  border-top-color: rgba(255, 255, 255, 0.08);
}

/* Profile picker — mono-uppercase pills, low/med/high. Disabled
 * options (above max_profile, or "high" without mains_powered) render
 * dimmed and non-clickable. */
.live-profile-row {
  display: flex;
  gap: 0.35rem;
  align-items: center;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.live-profile-row .lp-label {
  color: var(--text-faint);
  margin-right: 0.3rem;
}
.live-profile-pill {
  font: inherit;
  padding: 0.25rem 0.55rem;
  background: transparent;
  color: var(--text-dim);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background-color 0.12s, color 0.12s, border-color 0.12s;
}
.live-profile-pill:hover:not([disabled]) {
  border-color: var(--primary);
  color: var(--text);
}
.live-profile-pill.active {
  background: var(--primary);
  color: #fff;
  border-color: var(--primary);
}
.live-profile-pill[disabled] {
  opacity: 0.4;
  cursor: not-allowed;
}
.live-overlay.fullscreen .live-profile-row .lp-label,
.live-overlay.fullscreen .live-profile-pill {
  color: #b8ad97;
  border-color: rgba(255, 255, 255, 0.18);
}
.live-overlay.fullscreen .live-profile-pill.active {
  color: #fff;
}

/* Compact viewport: head + foot stack neatly on narrow screens. */
@media (max-width: 600px) {
  .live-overlay-head,
  .live-overlay-foot {
    gap: 0.4rem;
    padding: 0.45rem 0.6rem;
  }
  .live-overlay-stat { font-size: 9.5px; }
}

/* ─────────────────────────────────────────────────────────────────────────
 * 20 — Mobile (≤480 / ≤380) — added 2026-05-04
 *
 * Mobile-responsive overlay. The original CSS targeted 500/520/600/767 and
 * up; nothing collapsed below 480 except a handful of single-purpose rules.
 * This section is the "Site Notes on a phone" layer: tap-target floor,
 * form collapses, sticky save bar, lightbox bottom rail, live-overlay
 * auto-fullscreen, gallery toolbar wrap, admin tables-as-cards.
 *
 * RULE FOR EDITING: nothing in this section may affect the desktop
 * (≥768) rendering. Anything cross-cutting (utility classes, etc.) gets
 * wrapped in `(pointer: coarse)` so devtools-narrow-on-desktop keeps
 * the slim desktop sizes.
 *
 * STRUCTURE:
 *   20.1  Touch-device baseline (pointer: coarse + ≤767)
 *   20.2  Utility classes (.table-scroll, .sticky-save-bar)
 *   20.3  Full-viewport surface dvh swaps
 *   20.4  ≤480 — phone layout collapses (forms, status, gallery, lightbox, ...)
 *   20.5  ≤380 — small-phone tightening
 *   20.6  Reduced-motion + landscape-orientation tweaks
 * ───────────────────────────────────────────────────────────────────── */

/* 20.1 — Touch-device baseline ──────────────────────────────────────────
 * Bumps every interactive element to a 44pt tap floor on real touch
 * devices. The (pointer: coarse) qualifier means desktop browsers
 * narrow-resized via devtools or split-screen retain their slim
 * desktop sizes — this only fires on phones/tablets.
 */
@media (max-width: 767px) and (pointer: coarse) {
  .btn,
  .btn-sm,
  .live-pill-btn,
  .stats-action-btn,
  .filter-chip,
  .ss-preset,
  .cmp-date-pill,
  .lb-side-btn,
  .ann-tool-btn,
  .tab-item,
  .sub-tab-item,
  .live-profile-pill,
  .live-overlay-btn,
  .cmd-cancel,
  .mobile-dates-toggle,
  .live-pill,
  .input,
  .select,
  textarea {
    min-height: 44px;
  }
  .input,
  .select,
  textarea {
    /* iOS auto-zooms text fields below 16px. Keep body input at 16
     * to defeat the zoom; visual size unchanged via line-height. */
    font-size: max(16px, 0.95rem);
  }
  /* Kill the legacy 300ms tap delay on iOS Safari. */
  button,
  a,
  [role="button"],
  .btn,
  input[type="checkbox"],
  input[type="radio"] {
    touch-action: manipulation;
  }
  /* Pad the tappable inner so the increased min-height doesn't
   * change the visual padding asymmetrically. */
  .btn-sm,
  .stats-action-btn,
  .live-overlay-btn {
    padding-block: max(0.55rem, 0.4rem);
  }
}

/* 20.2 — Utility classes ───────────────────────────────────────────────
 * .table-scroll wraps a wide table in a horizontal scroller so it
 * doesn't blow the viewport. .sticky-save-bar pins a save action to
 * the bottom of a card with safe-area inset padding. */
.table-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  /* Keep border-radius edges clean even with overflow. */
  border-radius: var(--radius);
}
.table-scroll > table {
  min-width: 100%;
}

.sticky-save-bar {
  position: sticky;
  bottom: 0;
  background: var(--surface);
  border-top: 1px solid var(--border);
  padding: 0.85rem 1rem
           calc(0.85rem + env(safe-area-inset-bottom, 0px))
           1rem;
  z-index: 5;
  display: flex;
  gap: 0.6rem;
  align-items: center;
  flex-wrap: wrap;
}
.sticky-save-bar > .btn {
  flex: 1 1 auto;
  min-width: 140px;
}

/* 20.3 — Full-viewport surface dvh swaps ───────────────────────────────
 * Mobile Safari shrinks `vh` units when the URL bar collapses, then
 * redraws on tap — visible layout twitch. `dvh` is dynamic-viewport-
 * height and tracks the actual visible rect. Only applies to surfaces
 * that genuinely take over the screen. */
@media (max-width: 767px) {
  .live-overlay.fullscreen {
    height: 100dvh;
    max-height: 100dvh;
  }
  .lightbox-stage,
  .lightbox {
    min-height: 100dvh;
  }
  .login-bg {
    min-height: 100dvh;
  }
}

/* 20.4 — ≤480 phone layout collapses ───────────────────────────────────
 * Per-view rules. Each block calls out the view it targets so this
 * section stays maintainable as the design system evolves. */
@media (max-width: 480px) {

  /* — Shell padding tightens — */
  .app-content { padding: 0.85rem 0.75rem; }
  .app-topbar  { padding: 0.6rem 0.75rem; }
  .page-header { margin-bottom: 1.25rem; }
  .page-title  { font-size: 1.25rem; }

  /* — Login: tighter card on small phones — */
  .login-card,
  .login-pane-form,
  .login-pane-art {
    padding: 1.25rem 1rem;
  }

  /* — Stats strip: stack info + actions vertically so neither
   *   gets squeezed. Keeps mono numerics readable. — */
  .stats-strip-compact {
    grid-template-columns: 1fr;
    gap: 0.85rem;
  }
  .stats-strip-actions {
    border-left: none !important;
    border-top: 1px dashed var(--border);
    padding-left: 0 !important;
    padding-top: 0.85rem;
    flex-direction: row;
  }
  .stats-strip-actions .stats-action-btn {
    flex: 1 1 0;
  }

  /* — Form grid: single-column on phone — */
  .form-grid,
  .form-grid-narrow {
    grid-template-columns: 1fr !important;
  }
  .form-field { margin-bottom: 0.85rem; }

  /* — Schedule fields: 2x2 (start+end on row 1, interval spans
   *   full width on row 2). The .schedule-fields auto-fit grid
   *   would already drop to 2-col on phone, but pinning makes the
   *   layout deterministic and gives interval enough room. — */
  .schedule-fields {
    grid-template-columns: 1fr 1fr !important;
    gap: 0.5rem 0.6rem !important;
  }
  .schedule-fields > .form-field:nth-child(3) {
    grid-column: 1 / -1;
  }

  /* — Settings save bar pins to the bottom of the viewport on
   *   phone so the Save button is always reachable. — */
  .settings-save-bar {
    position: sticky;
    bottom: 0;
    background: var(--surface);
    border-top: 1px solid var(--border);
    margin: 0.85rem -0.75rem -0.85rem;
    padding: 0.85rem 0.75rem
             calc(0.85rem + env(safe-area-inset-bottom, 0px));
    z-index: 6;
  }
  .settings-save-bar .btn {
    width: 100%;
  }

  /* — Status: instrument tiles single column — */
  .instrument-grid,
  .status-metrics {
    grid-template-columns: 1fr !important;
  }

  /* — NVR rail: single column under hero (already collapses
   *   at 760, just gentler gap on phone) — */
  .nvr-rail {
    grid-template-columns: 1fr 1fr !important;
    gap: 0.5rem;
  }

  /* — Commands history: collapse 4-col grid to 2-line card.
   *   Line 1: id + type. Line 2: status + time. — */
  .cmd-history-row {
    grid-template-columns: 56px 1fr !important;
    grid-template-areas:
      "id     type"
      "status time" !important;
    row-gap: 0.25rem;
    padding-block: 0.6rem;
  }
  .cmd-history-row > :nth-child(1) { grid-area: id; }
  .cmd-history-row > :nth-child(2) { grid-area: type; }
  .cmd-history-row > :nth-child(3) { grid-area: status; }
  .cmd-history-row > :nth-child(4) { grid-area: time; justify-self: end; }

  /* — Commands action row: stack vertically, full-width buttons — */
  .cmd-action-row {
    flex-direction: column !important;
    gap: 0.5rem !important;
  }
  .cmd-action-row .btn {
    width: 100%;
    justify-content: center;
  }

  /* — Gallery day toolbar: wrap instead of overflow horizontally — */
  .gallery-day-actions {
    flex-wrap: wrap;
    gap: 0.4rem;
  }
  .gallery-day-actions .btn {
    flex: 0 1 auto;
  }

  /* — Image grid: 2 cols at 360 (was 1 col due to 180 minmax) — */
  .image-grid {
    grid-template-columns: repeat(auto-fill, minmax(128px, 1fr));
    gap: 0.45rem;
  }

  /* — Lightbox side rail → bottom horizontal pill bar — */
  .lb-sidebar {
    position: fixed !important;
    top: auto !important;
    left: 0 !important;
    right: 0 !important;
    bottom: calc(env(safe-area-inset-bottom, 0px) + 0.5rem) !important;
    flex-direction: row !important;
    justify-content: center;
    padding: 0.4rem 0.6rem !important;
    background: rgba(34, 31, 26, 0.82);
    border-radius: var(--radius);
    margin: 0 0.5rem;
    gap: 0.4rem !important;
    z-index: 30;
  }
  .lb-side-btn {
    flex: 1 1 0;
    min-width: 56px;
    width: auto !important;
  }

  /* — Live overlay: auto-fullscreen on phone (compact mode awkward).
   *   Profile pills wrap if they overflow; head + foot rows stack. — */
  .live-overlay {
    right: 0;
    bottom: 0;
    width: 100vw;
    max-width: none;
    height: 100dvh;
    max-height: 100dvh;
    border-radius: 0;
    border: none;
    box-shadow: none;
  }
  .live-overlay-head,
  .live-overlay-foot {
    flex-wrap: wrap;
    row-gap: 0.5rem;
  }
  .live-profile-row { flex-wrap: wrap; }

  /* — Compare: stack images top-bottom instead of side-by-side — */
  .cmp-stage {
    flex-direction: column !important;
    grid-template-columns: 1fr !important;
  }
  .cmp-half {
    width: 100% !important;
  }
  .cmp-info {
    max-width: 100% !important;
    position: static !important;
    transform: none !important;
    margin-top: 0.4rem;
  }

  /* — Annotation toolbar: two-row layout — */
  .ann-toolbar {
    flex-wrap: wrap;
    row-gap: 0.4rem;
    padding: 0.5rem 0.6rem;
  }
  .ann-tool-group { flex: 0 0 auto; }

  /* — Admin Users: card-per-row — */
  .users-table-wrap .users-table thead {
    /* Hide table head on phone; each cell becomes its own row
     * with a data-label prefix. */
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
  }
  .users-table-wrap .users-table,
  .users-table-wrap .users-table tbody,
  .users-table-wrap .users-table tr,
  .users-table-wrap .users-table td {
    display: block;
    width: 100%;
  }
  .users-table-wrap .users-table tr {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    margin-bottom: 0.6rem;
    padding: 0.6rem 0.75rem;
    background: var(--surface);
  }
  .users-table-wrap .users-table td {
    padding: 0.2rem 0;
    text-align: left;
    border: none;
  }
  .users-table-wrap .users-table td::before {
    content: attr(data-label);
    display: inline-block;
    min-width: 88px;
    color: var(--text-faint);
    font-family: var(--font-mono);
    font-size: 10.5px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    margin-right: 0.5rem;
  }

  /* — Modals: full-viewport on phone with sticky footer — */
  .modal-bg .modal,
  .modal-bg-wide .modal {
    width: 100vw !important;
    max-width: 100vw !important;
    max-height: 100dvh !important;
    border-radius: 0;
    margin: 0;
  }
  .modal-bg .modal-footer,
  .modal-bg-wide .modal-footer {
    position: sticky;
    bottom: 0;
    padding-bottom: calc(0.85rem + env(safe-area-inset-bottom, 0px));
    background: var(--surface);
  }

  /* — Configurator: dimensions to 2 cols on phone — */
  .cfg-dims {
    grid-template-columns: repeat(2, 1fr) !important;
  }
  .cfg-options {
    grid-template-columns: 1fr !important;
  }

  /* — Marketing landing: clamp hero text + collapse feature grid — */
  .landing-hero-title { font-size: clamp(2rem, 8vw, 2.6rem); }
  .landing-features-grid,
  .landing-howitworks-steps {
    grid-template-columns: 1fr !important;
  }

  /* — Live pill button: a touch more breathing room — */
  .live-pill,
  .live-pill-btn {
    padding: 0.35rem 0.65rem 0.35rem 0.55rem;
  }
}

/* 20.5 — ≤380 small-phone tightening ───────────────────────────────────
 * iPhone SE / Mini territory. Just shaves padding and drops gallery
 * dim-grid + cfg-dims to single column. */
@media (max-width: 380px) {
  .app-content { padding: 0.7rem 0.6rem; }
  .login-card,
  .login-pane-form,
  .login-pane-art {
    padding: 1rem 0.85rem;
  }
  .image-grid {
    grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
    gap: 0.35rem;
  }
  .cfg-dims {
    grid-template-columns: 1fr !important;
  }
  .nvr-rail {
    grid-template-columns: 1fr !important;
  }
  /* Mono numerics down a notch so 6-digit timestamps don't wrap. */
  .status-metric-value { font-size: 1.25rem; }
}

/* 20.6 — Reduced-motion + landscape ────────────────────────────────────
 * Honors prefers-reduced-motion (already covered globally for
 * live-pill-dot), and trims top padding in phone landscape so the
 * shrinking viewport doesn't eat the page header. */
@media (max-width: 900px) and (orientation: landscape) and (max-height: 500px) {
  .app-topbar     { padding: 0.4rem 0.75rem; }
  .page-header    { margin-bottom: 0.85rem; }
  .stats-strip-compact { padding-block: 0.5rem; }
}

/* ═════════════════════════════════════════════════════════════════════════
 * Section 19 — Station front-page instrument register (2026-05-05)
 * ═════════════════════════════════════════════════════════════════════════
 *
 * The Status detail page was dissolved on 2026-05-05. Its 7-chip rail
 * was promoted to the station front page as a 4×2 grid of pills
 * directly above the gallery. The right-column action stack collapsed
 * to a single full-height "Settings" button (Status was the other half;
 * it's gone). Commands and command/diagnostic history moved into
 * Settings → Maintenance.
 *
 * Three additions over the old strip:
 *   1. Live-pulse dot on the Status pill   (.stat-pulse-dot)
 *   2. Click-Disk-pill popover             (.disk-popover)
 *   3. Threshold sub-lines on banded chips (warn 70 / crit 90, etc.)
 *
 * The .stats-strip-pills already grids 4-wide with auto-flow; rendering
 * 8 children naturally fills 2 rows. No grid override needed beyond the
 * v2 modifier opting into the slightly taller per-pill min-height.
 */

/* Allow traffic-light borders + threshold sub-lines on the front-page
 * pills. The compact strip already strips the standard .stat-card box-
 * shadow; here we add the 3px left strip in the four state classes. */
.stats-strip.stats-strip-compact .stat-card.is-ok   { border-left: 3px solid var(--success); }
.stats-strip.stats-strip-compact .stat-card.is-warn { border-left: 3px solid var(--warn); }
.stats-strip.stats-strip-compact .stat-card.is-bad  { border-left: 3px solid var(--danger); }
/* And tint the value text to match — color redundancy for a11y. */
.stats-strip.stats-strip-compact .stat-card.is-ok   .stat-card-value { color: var(--success); }
.stats-strip.stats-strip-compact .stat-card.is-warn .stat-card-value { color: var(--warn); }
.stats-strip.stats-strip-compact .stat-card.is-bad  .stat-card-value { color: var(--danger); }

/* v2 grid — slightly taller min-height to accommodate the threshold
 * sub-lines without making row 2 (no subs) look short. The existing
 * v1 .stat-card style is reused inside; v2 just opts up the row height. */
.stats-strip-pills-v2 {
  grid-template-columns: repeat(4, minmax(120px, 1fr));
  /* Two rows is implicit via 8 children + 4 columns. */
}
.stats-strip-pills-v2 .stat-card {
  min-height: 48px;
  padding: 0.5rem 0.75rem 0.55rem;
  align-items: center;
}
.stats-strip-pills-v2 .stat-card-sub {
  text-align: right;
  font-size: 9px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin-top: -0.05rem;
}
@media (max-width: 880px) {
  /* Threshold sub-lines hidden on narrow widths to keep 2-col layouts
   * legible — the color border still communicates the state. */
  .stats-strip-pills-v2 .stat-card-sub { display: none; }
}

/* Live-pulse dot inside the Status pill value. Keyed to staleness:
 *   pulse-ok  → online, gentle 2s lichen pulse
 *   pulse-warn → never checked in, static hi-vis
 *   pulse-bad  → went offline, static brick
 * Pulse animation is identical to .live-pill-dot (Section 12) so the
 * page reads coherently. Honors prefers-reduced-motion. */
.stat-pulse-dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  margin-right: 0.4rem;
  vertical-align: middle;
  background: var(--text-faint);
  flex: 0 0 auto;
}
.stat-pulse-dot.pulse-ok {
  background: var(--success);
  animation: stat-pulse 2s ease-in-out infinite;
}
.stat-pulse-dot.pulse-warn { background: var(--warn); }
.stat-pulse-dot.pulse-bad  { background: var(--danger); }
@keyframes stat-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(135, 160, 107, 0.55); }
  50%      { box-shadow: 0 0 0 5px rgba(135, 160, 107, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .stat-pulse-dot.pulse-ok { animation: none; }
}

/* Inline mini disk-bar — replaces the larger ~3px below-value bar with
 * a thinner one nested inside the value cell. The pill's main numeric
 * stays the focus; the bar is a quiet accent. */
.stat-card-bar {
  display: inline-block;
  width: 36px;
  height: 4px;
  border-radius: 2px;
  background: var(--surface-2);
  margin-left: 0.45rem;
  vertical-align: middle;
  overflow: hidden;
}
.stat-card-bar-fill {
  display: block;
  height: 100%;
  background: var(--success);
  transition: width 200ms ease;
}
.stat-card-bar-fill.warn { background: var(--warn); }
.stat-card-bar-fill.bad  { background: var(--danger); }

/* Disk popover — fixed-position card anchored under the Disk pill on
 * click. Auto-closes on the next document click. */
.disk-popover {
  position: absolute;
  z-index: 60;
  min-width: 220px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--primary);
  border-radius: var(--radius);
  padding: 0.65rem 0.85rem 0.7rem;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
}
.disk-popover-head {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: 0.5rem;
}
.disk-popover-row {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.18rem 0;
  font-family: var(--font-mono);
  font-size: 12px;
}
.disk-popover-row .dp-label { color: var(--text-dim); }
.disk-popover-row .dp-val   {
  color: var(--text);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
}
.disk-popover-foot {
  margin-top: 0.45rem;
  padding-top: 0.4rem;
  border-top: 1px dashed var(--border);
  font-size: 9.5px;
  color: var(--text-faint);
  font-family: var(--font-mono);
  letter-spacing: 0.04em;
  font-style: italic;
}

/* Solo right-column — when the strip has only one button (Settings),
 * the column expands to align the button to the strip's full height. */
.stats-strip-actions-solo .stats-action-btn {
  flex: 1 1 auto;
  font-size: 11px;
  letter-spacing: 0.08em;
  padding: 0.7rem 0.9rem;
  height: 100%;
}
.stats-strip-actions-solo {
  min-width: 130px;
}

/* Recent-commands collapsible (under the pills, above the tab nav) ───
 * Default-collapsed <details> with a thin drafting-block summary row.
 * On open, the body shows the last 5 commands using the existing
 * .cmd-history-row layout so it's visually consistent with the
 * Maintenance section's history. */
.st-recent-cmds {
  margin: -0.6rem 0 0.85rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;
}
.st-recent-cmds[open] { border-color: var(--border-hover); }
.st-recent-cmds-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.55rem 0.85rem;
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.st-recent-cmds-head::-webkit-details-marker { display: none; }
.st-recent-cmds-head::before {
  content: "▸";
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-faint);
  margin-right: 0.5rem;
  transition: transform 150ms ease;
}
.st-recent-cmds[open] .st-recent-cmds-head::before { transform: rotate(90deg); }
.st-recent-cmds-label {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-dim);
  flex: 1;
}
.st-recent-cmds-hint {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.st-recent-cmds[open] .st-recent-cmds-hint { display: none; }
.st-recent-cmds-body {
  padding: 0.25rem 0.65rem 0.6rem;
  border-top: 1px dashed var(--border);
}

/* ── Title-row inline Settings button (2026-05-06) ──────────────────────────
 * Sits inline with the station name + LIVE pill in .page-title. Matches the
 * .live-pill visual scale (mono uppercase 10.5px, ~32px tall, vertical-
 * centered to the title baseline via top:-2px) so the eye reads the row as
 * a single coherent cluster rather than a heading + a stranded button.
 *
 * Clay tint per the design system convention "clay = the place you change
 * things" (Section "When to use which" in docs/design-system.md). Soft-tint
 * at rest, fills solid on hover, locks solid when the Settings detail page
 * is active.
 *
 * The .page-title was previously a plain inline-element flow; we now turn
 * it into an inline-flex row with consistent gap so name / LIVE / drift /
 * settings all align on the same baseline at any width without margin
 * tweaking each child.
 */
.page-title {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 1.1rem;   /* widened 2026-05-06: name / LIVE / Settings now read as
                    three distinct items with breathing room, not a packed pill cluster */
}
/* Reset the live-pill's hand-tuned margin-left now that gap handles spacing */
.page-title .live-pill,
.page-title .live-pill-btn,
.page-title .drift-pill {
  margin-left: 0;
}
/* LIVE + Settings: pixel-locked to the same height (30px) and font (12px)
 * so they're visually identical regardless of inherited line-height from
 * the parent .page-title. Padding is horizontal-only — vertical centering
 * comes from the fixed height + flex align. */
.page-title .live-pill,
.page-title .live-pill-btn,
.title-settings-btn {
  height: 30px;
  padding: 0 0.75rem;
  font-size: 12px;
  line-height: 1;
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  top: -1px;
  position: relative;
}
.page-title .live-pill-dot {
  width: 7px;
  height: 7px;
}
.page-title .live-pill-icon {
  width: 11px;
  height: 11px;
}

.title-settings-btn {
  font-family: var(--font-mono);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-weight: 500;
  background: var(--primary-soft);
  color: var(--primary);
  border: 1px solid var(--primary);
  border-radius: var(--radius-sm);
  cursor: pointer;
  vertical-align: middle;
  transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease;
}
.title-settings-btn svg {
  width: 13px;
  height: 13px;
  flex: 0 0 auto;
}
.title-settings-btn:hover {
  background: var(--primary);
  color: #fff;
  border-color: var(--primary);
}
.title-settings-btn.active {
  background: var(--primary);
  color: #fff;
  border-color: var(--primary);
}
.title-settings-btn:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

/* Mobile: keep the cluster compact. The title can wrap, but the button
 * should never split below the title on narrow screens — it stays on the
 * right of the name (or wraps as a unit). */
@media (max-width: 540px) {
  .page-title { gap: 0.75rem; }
  .page-title .live-pill,
  .page-title .live-pill-btn,
  .title-settings-btn {
    height: 26px;
    font-size: 11px;
    padding: 0 0.6rem;
  }
  .title-settings-btn svg { width: 11px; height: 11px; }
}
