:root {
  --bg: #0f1115;
  --panel: #171a21;
  --panel-border: #262b36;
  --text: #e6e9ef;
  --text-dim: #8b93a3;
  --accent: #4f8cff;
  --green: #35d07f;
  --amber: #f5b942;
  --gray: #5a6172;
  --red: #ef5350;
  --radius: 10px;
}

@media (prefers-color-scheme: light) {
  :root {
    --bg: #f4f5f7;
    --panel: #ffffff;
    --panel-border: #dde1e8;
    --text: #1a1d24;
    --text-dim: #666e7d;
  }
}

/* Manual theme toggle (common.js) overrides the OS preference above in
   either direction once the user picks a side explicitly. */
:root[data-theme="dark"] {
  --bg: #0f1115;
  --panel: #171a21;
  --panel-border: #262b36;
  --text: #e6e9ef;
  --text-dim: #8b93a3;
}
:root[data-theme="light"] {
  --bg: #f4f5f7;
  --panel: #ffffff;
  --panel-border: #dde1e8;
  --text: #1a1d24;
  --text-dim: #666e7d;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  background: var(--bg);
  color: var(--text);
  min-height: 100vh;
}

/* Plain content links (repo URLs, device/hardware cross-links, etc.) had no
   color rule anywhere, so they fell back to the browser's default link blue
   (~#0000EE) - low contrast and hard to read against the dark theme's
   near-black background. --accent (#4f8cff) is already tuned for both
   themes elsewhere on this page (badges, borders), so reuse it here too.
   More specific rules (header a, a.btn-nav, .linklike, ...) already set
   their own color and win over this on specificity. */
a { color: var(--accent); }
a:visited { color: var(--accent); }

header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 24px;
  border-bottom: 1px solid var(--panel-border);
}

/* Shared top navigation (injected by renderNav in common.js on every page) -
   one consistent bar so you always know where you are and where you can go. */
.topnav {
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 12px 24px;
  border-bottom: 1px solid var(--panel-border);
  flex-wrap: wrap;
}
.topnav .brand {
  font-weight: 700;
  font-size: 15px;
  color: var(--text);
  text-decoration: none;
  white-space: nowrap;
}
.topnav .navlinks { display: flex; gap: 4px; flex: 1; flex-wrap: wrap; }
.topnav .navlink {
  padding: 6px 12px;
  border-radius: 8px;
  font-size: 14px;
  color: var(--text-dim);
  text-decoration: none;
  border: 1px solid transparent;
}
.topnav .navlink:hover { color: var(--text); background: var(--panel); }
.topnav .navlink.active {
  color: var(--text);
  background: var(--panel);
  border-color: var(--panel-border);
  font-weight: 600;
}
.topnav .navstatus { display: flex; align-items: center; gap: 14px; }

.page-title { font-size: 20px; font-weight: 700; margin: 0 0 16px; }

/* Path input + Browse button, and the folder-picker modal it opens. */
.path-row { display: flex; gap: 8px; align-items: center; }
.path-row .form-input { flex: 1; }
.path-row .cmd-btn { white-space: nowrap; }
.dirpick-overlay {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(0, 0, 0, 0.5);
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.dirpick {
  width: min(560px, 100%); max-height: 80vh; display: flex; flex-direction: column;
  background: var(--panel); border: 1px solid var(--panel-border); border-radius: 12px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
}
.dirpick-head { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px; border-bottom: 1px solid var(--panel-border); }
.dirpick-path { padding: 8px 16px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; color: var(--text-dim); word-break: break-all; border-bottom: 1px solid var(--panel-border); }
.dirpick-list { overflow-y: auto; padding: 6px; flex: 1; }
.dirpick-item {
  display: block; width: 100%; text-align: left; background: none; border: none;
  color: var(--text); padding: 8px 10px; border-radius: 8px; cursor: pointer; font-size: 14px;
}
.dirpick-item:hover { background: var(--bg); }
.dirpick-foot { display: flex; gap: 10px; align-items: center; padding: 12px 16px; border-top: 1px solid var(--panel-border); }
.dirpick-newrow { display: flex; gap: 8px; padding: 10px 16px; border-top: 1px solid var(--panel-border); }
/* Long-text popup editor (openTextEditorModal) - reuses the dirpick shell. */
.dirpick.texted { width: min(860px, 94vw); height: min(640px, 88vh); display: flex; flex-direction: column; }
.texted-area { flex: 1; margin: 12px 16px 0; resize: none; font-size: 14px; line-height: 1.5; }
.texted-tabs { display: flex; gap: 6px; margin-left: auto; margin-right: 10px; }
/* The head's cmd-btns render filled - the active tab must invert, not tint
   (accent-on-accent text was invisible, caught in the first screenshot). */
.texted-tabs .cmd-btn { background: transparent; color: var(--text-dim); border: 1px solid var(--panel-border); }
.texted-tabs .cmd-btn.active { background: var(--accent); color: #fff; border-color: var(--accent); }
.texted-render { flex: 1; overflow-y: auto; margin: 12px 16px 0; padding: 4px 8px; }
.texted-render[hidden] { display: none; }
/* Rendered-markdown typography - kept minimal, inherits the app's colors. */
.md-body { font-size: 14px; line-height: 1.55; }
.md-body h1 { font-size: 20px; margin: 14px 0 8px; }
.md-body h2 { font-size: 17px; margin: 14px 0 6px; }
.md-body h3 { font-size: 15px; margin: 12px 0 4px; }
.md-body p { margin: 8px 0; }
.md-body ul, .md-body ol { margin: 8px 0; padding-left: 22px; }
.md-body li { margin: 3px 0; }
.md-body code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; background: rgba(139,147,163,0.14); padding: 1px 5px; border-radius: 4px; }
.md-body pre { background: var(--bg); border: 1px solid var(--panel-border); border-radius: 8px; padding: 10px 12px; overflow-x: auto; }
.md-body pre code { background: none; padding: 0; }
.md-body table { border-collapse: collapse; margin: 10px 0; }
.md-body th, .md-body td { border: 1px solid var(--panel-border); padding: 5px 10px; font-size: 13px; }
.md-body th { background: rgba(139,147,163,0.10); }
.md-body blockquote { border-left: 3px solid var(--panel-border); margin: 8px 0; padding: 2px 12px; color: var(--text-dim); }
/* Natural-size diagrams (useMaxWidth:false) - scroll when wider than the
   modal, zoom-in cursor because clicking opens the lightbox. */
.md-body .mermaid { display: block; text-align: center; margin: 12px 0; overflow-x: auto; cursor: zoom-in; }
.diagram-lightbox {
  position: fixed; inset: 0; z-index: 400;
  background: rgba(10, 12, 16, 0.94);
  display: flex; align-items: center; justify-content: center;
  cursor: zoom-out;
}
.diagram-lightbox svg { display: block; }
/* Inline description preview + open button in the project form. */
.desc-preview-row { display: flex; gap: 8px; align-items: center; }
.desc-preview { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 13px; color: var(--text-dim); border: 1px solid var(--panel-border); border-radius: 8px; padding: 7px 10px; background: var(--bg); }
.desc-preview.empty { font-style: italic; }
.dirpick-newrow[hidden] { display: none; } /* display:flex would otherwise beat the hidden attribute (same class of bug as .card[hidden]) */
.dirpick-newrow .form-input { flex: 1; }

/* Product-photo thumbnails (hardware inventory + device detail). object-fit
   keeps varied vendor images from stretching; a light pad + border frames
   transparent PNGs on either theme. */
.hw-thumb,
.hw-preview {
  object-fit: contain;
  border-radius: 8px;
  background: #fff;
  border: 1px solid var(--panel-border);
  padding: 3px;
  cursor: zoom-in;
  transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
}
.hw-thumb { width: 64px; height: 64px; }
.hw-preview { width: 76px; height: 76px; }

/* Hover to enlarge so you can actually inspect the item. In-place scale is
   only safe for a LONE image (device detail card): a scaled image sits on
   top of its neighbors and steals their mouse events, which is exactly what
   broke the multi-photo gallery (Peter, 2026-07-27). Gallery previews
   (.hw-preview) instead use hardware.html's floating #imgPreview box -
   pointer-events:none, viewport-clamped - plus click-to-fullscreen. */
.hw-thumb:hover {
  transform: scale(4);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
  border-color: var(--accent);
  position: relative;
  z-index: 60;
  transform-origin: top right;
}
.hw-preview:hover { border-color: var(--accent); }

header h1 {
  font-size: 18px;
  margin: 0;
  font-weight: 600;
}

header a {
  color: var(--text);
  text-decoration: none;
}

.conn-indicator {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: var(--text-dim);
}

.dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--gray);
  flex-shrink: 0;
}
.dot.live { background: var(--green); }
.dot.stale { background: var(--amber); }
.dot.never { background: var(--gray); }
.dot.offline { background: var(--red); }

main {
  max-width: 720px;
  margin: 0 auto;
  padding: 24px;
}
/* .wide is for pages that are wider than reading-width but aren't a raw
   data table - dashboard cards (index.html), a project's detail/edit form
   (project.html). 980px reads fine there; going further just stretches
   single-line text inputs across the whole window for no benefit. */
main.wide { max-width: 980px; }
/* .table-wide is for the actual list/table pages (devices, projects,
   hardware, purchases, log) - these should grow with the browser instead of
   being stuck at a fixed width regardless of window size. A soft ceiling
   keeps rows from stretching absurdly thin-and-huge on an ultrawide/4K
   display; drop it entirely if that turns out not to matter in practice. */
main.table-wide { max-width: 1800px; }

/* A page that fills the window. For the Overview, which Peter runs on an
   office monitor to see the state of everything at a glance — a 980px column
   in the middle of a 32-inch screen wastes the entire point of having it
   there. Padding rather than a max-width, so it scales with whatever it is
   shown on. */
main.full { max-width: none; padding-left: clamp(16px, 2vw, 40px); padding-right: clamp(16px, 2vw, 40px); }

/* Wall mode: the nav and chrome go away, type scales up, and nothing
   scrolls. Toggled per browser and remembered, because the office display
   wants it permanently while a laptop does not. */
body.wall #topnav { display: none; }
body.wall main.full { padding-top: 14px; }
body.wall .page-title { font-size: 22px; }
body.wall { overflow: hidden; }              /* glanceable means no scrolling */
body.wall .kpi .n { font-size: clamp(30px, 4.2vw, 68px); }
body.wall .kpi .k { font-size: clamp(11px, 0.9vw, 16px); }
body.wall .ov-panel h2 { font-size: clamp(14px, 1.1vw, 20px); }
body.wall .site-row, body.wall .attn-row { font-size: clamp(12px, 0.95vw, 18px); }

/* Fill the HEIGHT too. Fitting the window is only half of it — a dashboard
   that occupies the top third of a 32-inch monitor and leaves the rest black
   wastes exactly the space it was put there to use. The panels grow, and the
   row cap below is computed from what actually fits. */
body.wall main.full { height: 100vh; display: flex; flex-direction: column; box-sizing: border-box; }
body.wall .ov-cols { flex: 1 1 auto; min-height: 0; }
body.wall .ov-panel { display: flex; flex-direction: column; min-height: 0; }
body.wall .ov-panel .attn { flex: 1 1 auto; min-height: 0; }
body.wall .tiles { flex: 0 0 auto; }

/* Touch: anything tappable gets a real target. 44px is the size a fingertip
   actually hits reliably; a 22px icon button on a wall display is a game. */
@media (pointer: coarse), (min-width: 1600px) {
  .cmd-btn, .btn-save, .icon-btn, .tile, .segmented button, .view-toggle button {
    min-height: 44px; display: inline-flex; align-items: center; justify-content: center;
  }
  .tile { min-height: 92px; }
}

.card {
  display: block;
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  padding: 16px 18px;
  margin-bottom: 12px;
  text-decoration: none;
  color: var(--text);
  transition: border-color 0.15s ease;
}
.card:hover { border-color: var(--accent); }

.group-section { margin-bottom: 8px; }

.group-header {
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-dim);
  margin: 20px 0 8px;
}
.group-section:first-child .group-header { margin-top: 0; }

.card-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.card-title {
  font-size: 15px;
  font-weight: 600;
}

.card-meta {
  font-size: 12px;
  color: var(--text-dim);
  margin-top: 4px;
}

.status-label {
  font-size: 12px;
  color: var(--text-dim);
  display: flex;
  align-items: center;
  gap: 6px;
}

.empty-state {
  text-align: center;
  color: var(--text-dim);
  padding: 48px 24px;
  border: 1px dashed var(--panel-border);
  border-radius: var(--radius);
}

.back-link {
  display: inline-block;
  margin-bottom: 16px;
  color: var(--text-dim);
  text-decoration: none;
  font-size: 13px;
}
.back-link:hover { color: var(--text); }

.panel-header {
  margin-bottom: 20px;
}

.panel-header h2 {
  margin: 0 0 4px 0;
  font-size: 20px;
}

.panel-header .source-path {
  font-size: 12px;
  color: var(--text-dim);
  font-family: ui-monospace, SFMono-Regular, monospace;
}

.field-list {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  overflow: hidden;
  margin-bottom: 20px;
}

.field-row {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding: 10px 16px;
  border-bottom: 1px solid var(--panel-border);
  font-size: 14px;
}
.field-row:last-child { border-bottom: none; }

.field-key {
  color: var(--text-dim);
  font-family: ui-monospace, SFMono-Regular, monospace;
  font-size: 13px;
}

.field-value {
  font-weight: 500;
  text-align: right;
  word-break: break-word;
}

.pill {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
}
.pill.on { background: rgba(53, 208, 127, 0.15); color: var(--green); }
.pill.off { background: rgba(90, 97, 114, 0.2); color: var(--text-dim); }
/* Fields like fault/error/alarm are inverted - their true state is bad and
   should read as alarming, not the same reassuring green as e.g. "Device
   Powered: ON" (see renderFieldValue's NEGATIVE_BOOL_FIELDS). */
.pill.bad { background: rgba(239, 83, 80, 0.15); color: var(--red); }
.pill.warn { background: rgba(245, 185, 66, 0.15); color: var(--amber); }

.stage-pill {
  display: inline-block;
  padding: 1px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  background: rgba(79, 140, 255, 0.15);
  color: var(--accent);
  margin-left: 4px;
}

/* Track: which world the project belongs to (Home proving ground vs CRINT
   outside world) - same pill shape as stage, its own colors. */
.track-badge {
  display: inline-block;
  padding: 1px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  margin-left: 4px;
  white-space: nowrap;
}
.track-badge.track-home { background: rgba(53, 208, 127, 0.15); color: var(--green); }
.track-badge.track-home-to-crint { background: rgba(245, 185, 66, 0.15); color: var(--amber); }
.track-badge.track-crint { background: rgba(79, 140, 255, 0.15); color: var(--accent); }
.track-badge.track-shared { background: rgba(90, 97, 114, 0.2); color: var(--text-dim); }

/* Kind tabs on the Projects page (All / Hardware / Software / Mixed) */
.kind-tabs { display: flex; gap: 6px; margin-bottom: 14px; flex-wrap: wrap; }
.kind-tab {
  padding: 6px 14px;
  border-radius: 999px;
  border: 1px solid var(--panel-border);
  background: var(--panel);
  color: var(--text-dim);
  font-size: 13px;
  cursor: pointer;
}
.kind-tab:hover { color: var(--text); }
.kind-tab.active { color: var(--text); border-color: var(--accent); background: rgba(79, 140, 255, 0.12); font-weight: 600; }

.commands {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 20px;
}

button.cmd-btn,
a.cmd-btn {
  background: var(--accent);
  color: #fff;
  border: none;
  border-radius: var(--radius);
  padding: 10px 18px;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
  display: inline-block;
}
button.cmd-btn:disabled { opacity: 0.5; cursor: default; }
button.cmd-btn:not(:disabled):hover,
a.cmd-btn:hover { filter: brightness(1.1); }

/* Parameterized command: its number inputs + send button travel as one
   visual unit inside the .commands row. */
.cmd-param-group {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.cmd-param {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--text-dim);
  white-space: nowrap;
}
.cmd-param-input { width: 70px; }

/* Resizable table columns - see makeColumnsResizable() in common.js. Shared
   here (not per-page) since every list page's table gets the same handle. */
th.col-resizable { position: relative; overflow: hidden; text-overflow: ellipsis; }
.col-resize-handle {
  position: absolute;
  top: 0; right: 0; bottom: 0;
  width: 8px;
  cursor: col-resize;
  -webkit-user-select: none;
  user-select: none;
  touch-action: none;
}
.col-resize-handle:hover,
table.col-resizing .col-resize-handle { background: var(--accent); opacity: 0.35; }
table.col-resizing { cursor: col-resize; -webkit-user-select: none; user-select: none; }

/* .linklike - a button styled as an inline text link, for low-weight row
   actions (remove/reopen) that shouldn't compete visually with .cmd-btn/
   .btn-save. Shared here since more than one page's inline row-actions use it. */
.linklike { background: none; border: none; padding: 0; color: var(--accent); cursor: pointer; font: inherit; text-decoration: underline; }

/* ── Button hierarchy ──────────────────────────────────────────
   .cmd-btn (above) is the primary/solid style, reserved for actions with a
   real-world effect: a command sent to a device, or a real network scan.
   .btn-save is for form saves - deliberate but reversible, no device/network
   side effect - lower visual weight than a command but still a clear
   call-to-action. .btn-nav is for safe navigation (opening another page/app)
   - least weight, styled like a bordered link rather than a button. Without
   this distinction every action on a panel looked equally consequential. */
button.btn-save {
  background: transparent;
  color: var(--accent);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  padding: 9px 17px;
  font-size: 14px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}
button.btn-save:hover { background: rgba(79, 140, 255, 0.1); }
button.btn-save:disabled { opacity: 0.5; cursor: default; }

a.btn-nav {
  background: none;
  color: var(--text-dim);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  padding: 9px 17px;
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
  display: inline-block;
}
a.btn-nav:hover { border-color: var(--accent); color: var(--text); }

.raw-block {
  font-family: ui-monospace, SFMono-Regular, monospace;
  font-size: 12px;
  color: var(--text-dim);
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  padding: 12px 16px;
  white-space: pre-wrap;
  word-break: break-all;
}

.last-updated {
  font-size: 12px;
  color: var(--text-dim);
  margin-top: 6px;
}

/* ── Header controls (theme toggle, scan-now) ─────────────────── */
.header-actions {
  display: flex;
  align-items: center;
  gap: 14px;
}

.icon-btn {
  background: none;
  border: 1px solid var(--panel-border);
  border-radius: 8px;
  width: 32px;
  height: 32px;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  color: var(--text);
  display: flex;
  align-items: center;
  justify-content: center;
}
.icon-btn:hover { border-color: var(--accent); }

/* ── Health summary strip ─────────────────────────────────────── */
.health-summary {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  font-size: 13px;
  color: var(--text-dim);
  padding: 12px 0 4px;
  margin-bottom: 8px;
  border-bottom: 1px solid var(--panel-border);
}
.health-summary strong { color: var(--text); font-weight: 600; }

.scan-now-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 16px;
}
.scan-now-row .last-scanned {
  font-size: 12px;
  color: var(--text-dim);
}

/* ── Card type accents (distinguish MQTT installation vs. discovered
   device vs. a linked/merged card at a glance in the grouped list) ─── */
.card { border-left: 3px solid transparent; }
.card.card-installation { border-left-color: var(--accent); }
.card.card-installation.card-linked { border-left-color: var(--green); }
.card.card-device { border-left-color: var(--gray); }

.card-type-badge {
  display: inline-block;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 1px 6px;
  border-radius: 4px;
  margin-left: 6px;
  vertical-align: middle;
}
.card-type-badge.mqtt { background: rgba(79, 140, 255, 0.15); color: var(--accent); }
.card-type-badge.linked { background: rgba(53, 208, 127, 0.15); color: var(--green); }
.card-type-badge.device { background: rgba(90, 97, 114, 0.2); color: var(--text-dim); }
.card-type-badge.fw-outdated { background: rgba(239, 83, 80, 0.15); color: var(--red); }

/* ── Toasts ────────────────────────────────────────────────────── */
.toast-container {
  position: fixed;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  gap: 8px;
  z-index: 1000;
  align-items: center;
  width: 100%;
  pointer-events: none;
}

.toast {
  pointer-events: auto;
  background: var(--panel);
  border: 1px solid var(--panel-border);
  color: var(--text);
  padding: 10px 16px;
  border-radius: 8px;
  font-size: 13px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.2s ease, transform 0.2s ease;
  max-width: 90vw;
  display: flex;
  align-items: center;
  gap: 10px;
}
.toast.toast-in { opacity: 1; transform: translateY(0); }
.toast.toast-error { border-color: var(--red); }
.toast.toast-success { border-color: var(--green); }
.toast.toast-confirm { border-color: var(--amber); }

.toast-cancel-btn {
  background: none;
  border: 1px solid var(--panel-border);
  color: var(--text);
  border-radius: 6px;
  padding: 3px 9px;
  font-size: 12px;
  cursor: pointer;
}
.toast-cancel-btn:hover { border-color: var(--accent); }

/* ── Unsaved-changes indicator (notes/group editors) ──────────── */
.unsaved-indicator {
  font-size: 12px;
  color: var(--amber);
  margin-left: 8px;
}

/* ── Manage-groups panel (index.html, behind the ⚙ button) ────── */
.group-filter-controls {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.filter-arrow { color: var(--text-dim); font-size: 16px; }
.manage-groups-panel {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  padding: 14px 16px;
  margin-bottom: 16px;
}
.manage-group-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}
.manage-group-row:last-child { margin-bottom: 0; }
.manage-group-row .mg-name { flex: 0 0 200px; max-width: 200px; }
.manage-group-row .mg-notes { flex: 1 1 auto; min-width: 120px; }
.manage-groups-panel > .group-usage {
  font-size: 12px;
  color: var(--text-dim);
  margin-bottom: 10px;
}
button.btn-danger {
  background: none;
  border: 1px solid var(--panel-border);
  color: var(--red);
  border-radius: var(--radius);
  padding: 7px 13px;
  font-size: 13px;
  font-family: inherit;
  cursor: pointer;
}
button.btn-danger:hover { border-color: var(--red); }
button.btn-danger:disabled { opacity: 0.4; cursor: default; }

/* ── Inline warning (re-purposed board, etc.) ─────────────────── */
.warning-text {
  font-size: 13px;
  color: var(--amber);
  border: 1px solid var(--amber);
  border-radius: var(--radius);
  padding: 8px 12px;
  margin: 8px 0 16px;
}

/* ── Tabs + group dropdown ─────────────────────────────────────── */
.filter-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}

.tabs {
  display: flex;
  gap: 4px;
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  padding: 3px;
}

.tab-btn {
  background: none;
  border: none;
  border-radius: 7px;
  padding: 6px 14px;
  font-size: 13px;
  font-family: inherit;
  font-weight: 500;
  color: var(--text-dim);
  cursor: pointer;
}
.tab-btn:hover { color: var(--text); }
.tab-btn.active { background: var(--accent); color: #fff; }

.group-select {
  font-family: inherit;
  font-size: 14px;
  padding: 8px 12px;
  border-radius: var(--radius);
  border: 1px solid var(--panel-border);
  background: var(--panel);
  color: var(--text);
  min-width: 160px;
}
.group-select:focus { outline: none; border-color: var(--accent); }

/* ── Search/filter box ─────────────────────────────────────────── */
.search-row {
  margin-bottom: 16px;
}
.search-row input {
  width: 100%;
  font-family: inherit;
  font-size: 14px;
  padding: 9px 12px;
  border-radius: var(--radius);
  border: 1px solid var(--panel-border);
  background: var(--panel);
  color: var(--text);
}
.search-row input:focus {
  outline: none;
  border-color: var(--accent);
}

/* ── Collapsible group headers ─────────────────────────────────── */
.group-header {
  cursor: pointer;
  -webkit-user-select: none;
  user-select: none;
  display: flex;
  align-items: center;
  gap: 6px;
}
.group-header .collapse-arrow {
  display: inline-block;
  transition: transform 0.15s ease;
  font-size: 10px;
}
.group-section.collapsed .collapse-arrow { transform: rotate(-90deg); }
.group-section.collapsed .card { display: none; }

/* ── Loading skeleton ──────────────────────────────────────────── */
.skeleton-card { pointer-events: none; }
.skeleton-line {
  height: 12px;
  border-radius: 4px;
  margin: 6px 0;
  background: linear-gradient(90deg, var(--panel-border) 25%, var(--bg) 50%, var(--panel-border) 75%);
  background-size: 200% 100%;
  animation: skeleton-shimmer 1.4s ease-in-out infinite;
}
@keyframes skeleton-shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* ── WebSocket connection indicator ────────────────────────────── */
.ws-indicator {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: var(--text-dim);
}

/* ── Command history ───────────────────────────────────────────── */
.command-history {
  margin: 4px 0 20px;
  font-size: 12px;
  color: var(--text-dim);
}
.command-history-item {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 4px 0;
  border-bottom: 1px solid var(--panel-border);
}
.command-history-item:last-child { border-bottom: none; }

/* ── Edit form (group/notes/project fields) ─────────────────────
   Wraps the editable fields in a bordered card matching .field-list above
   it, instead of bare label+input pairs floating on the page background -
   these two sections used to look like different UIs on the same page. */
.edit-form {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  padding: 16px 18px;
  margin-bottom: 20px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.form-field { width: 100%; }
.form-field.narrow { max-width: 280px; }
.form-field.tiny { max-width: 200px; }
/* Grouped form layout (project settings): labeled sections, and rows that
   put related narrow fields side by side instead of one long column. */
.form-section { border-top: 1px solid var(--panel-border); padding-top: 12px; display: flex; flex-direction: column; gap: 12px; }
.form-section:first-child { border-top: none; padding-top: 0; }
.form-section-title { font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-dim); }
.form-row { display: flex; gap: 14px; flex-wrap: wrap; align-items: flex-end; }
.form-row .form-field { flex: 1 1 220px; width: auto; }
.form-row .form-field.narrow { flex: 0 1 240px; }
.form-row .form-field.tiny { flex: 0 1 160px; }
.form-label {
  font-size: 12px;
  color: var(--text-dim);
  display: block;
  margin-bottom: 4px;
}
.form-input,
.form-textarea {
  width: 100%;
  font-family: inherit;
  font-size: 14px;
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid var(--panel-border);
  background: var(--bg);
  color: var(--text);
}
.form-input:focus,
.form-textarea:focus { outline: none; border-color: var(--accent); }
.form-textarea { resize: vertical; }
.form-actions {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* ── Sparkline (inline, next to a numeric field value) ─────────── */
.sparkline {
  color: var(--accent);
  vertical-align: middle;
  margin-left: 8px;
}

/* "N apps down" in the health strip - a watched program (computer agent)
   not running on an otherwise reachable computer. */
.health-summary .apps-down-alert { color: var(--red); }
.health-summary .apps-down-alert strong { color: var(--red); }

/* Danger confirm button inside a confirm toast (restart/shutdown/flash) -
   the deliberate "yes, do it" action, visually distinct from Cancel. */
.toast-danger-btn {
  background: var(--red);
  color: #fff;
  border: none;
  border-radius: 6px;
  padding: 3px 11px;
  font-size: 12px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}
.toast-danger-btn:hover { filter: brightness(1.1); }

/* A stale IP is shown, not offered: an offline device's address was true at
   last contact and will not load now. Dimmed and unlinked, so it still
   identifies the device without inviting a click that hangs. */
.dev-ip-stale { color: var(--text-dim); }

/* A paused project's device: deliberately quiet, not alarming. It reads as a
   scheduled absence, which is what it is. */
.dev-paused { color: var(--text-dim); }

/* A paused project is parked, not gone: dimmed enough to skip while scanning,
   still fully readable and still draggable. */
.board-card.is-paused { opacity: 0.55; }
.board-card.is-paused:hover { opacity: 1; }
.paused-badge {
  font-size: 11px; color: var(--text-dim);
  border: 1px solid var(--panel-border); border-radius: 999px; padding: 1px 7px;
}

/* Nav rearrange mode. Deliberately unobtrusive until entered: the handle is a
   dim glyph at the end of the bar, and only in edit mode do the tabs look
   grabbable. */
.nav-edit {
  background: none; border: 1px solid transparent; color: var(--text-dim);
  border-radius: 7px; cursor: pointer; font-size: 13px; padding: 4px 8px; margin-left: 2px;
}
.nav-edit:hover { color: var(--text); border-color: var(--panel-border); }
.nav-edit.on { color: var(--accent); border-color: var(--accent); }
.navlinks.nav-editing .navlink {
  cursor: grab; border: 1px dashed var(--panel-border); border-radius: 7px;
}
.navlinks.nav-editing .navlink.active { border-style: solid; }
.navlink.nav-dragging { opacity: 0.4; }

/* Overview location picker. Inline in the banner so setting your location is
   where you read it, not buried in a settings page. */
.ov-sitepick {
  background: var(--panel); color: var(--text); border: 1px solid var(--panel-border);
  border-radius: 7px; padding: 3px 8px; font-size: 13px; margin-left: 6px;
}
.ov-setby { color: var(--text-dim); font-size: 12px; }
/* The override and the evidence disagreeing: shown, never silently resolved. */
.ov-conflict { color: var(--amber, #d29922); font-size: 13px; }
.ov-banner-unknown { border-color: rgba(210,153,34,0.45); }

/* Where a unit is physically installed — the answer to "which of these five
   identical boards am I looking at". */
.dev-where { color: var(--accent, #58a6ff); }

/* Breadcrumbs — see breadcrumbHtml() in common.js. Small and quiet: they are
   orientation, not navigation furniture competing with the page title. */
.crumbs { font-size: 12px; margin: 0 0 6px; display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.crumbs a { color: var(--text-dim); text-decoration: none; }
.crumbs a:hover { color: var(--accent); text-decoration: underline; }
.crumb-sep { color: var(--text-dim); opacity: .55; }
.crumb-here { color: var(--text-dim); }
