/* Hannale dashboard
   ------------------------------------------------------------------
   Both themes are deliberately stepped, not an automatic inversion.
   Chrome, ink and series colors come from a validated reference palette;
   status colors are fixed across modes and always ship with icon + label.
*/

:root {
  color-scheme: light;

  /* surfaces & ink */
  --plane:        #f9f9f7;
  --surface:      #fcfcfb;
  --surface-2:    #f2f1ec;
  --ink:          #0b0b0b;
  --ink-2:        #52514e;
  --muted:        #898781;
  --border:       rgba(11, 11, 11, 0.10);
  --grid:         #e1e0d9;
  --axis:         #c3c2b7;

  /* data */
  --series-1:     #2a78d6;
  --series-1-soft:#cde2fb;

  /* status — fixed in both modes, never used for series */
  --good:         #0ca30c;
  --warning:      #fab219;
  --serious:      #ec835a;
  --critical:     #d03b3b;

  --shadow:       0 1px 2px rgba(11, 11, 11, .05), 0 8px 24px rgba(11, 11, 11, .04);

  --radius:       14px;
  --radius-sm:    9px;
  --space:        8px;

  --font: system-ui, -apple-system, "Segoe UI", sans-serif;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --plane:        #0d0d0d;
    --surface:      #1a1a19;
    --surface-2:    #232322;
    --ink:          #ffffff;
    --ink-2:        #c3c2b7;
    --muted:        #898781;
    --border:       rgba(255, 255, 255, 0.10);
    --grid:         #2c2c2a;
    --axis:         #383835;
    --series-1:     #3987e5;
    --series-1-soft:#184f95;
    --shadow:       0 1px 2px rgba(0, 0, 0, .4), 0 8px 24px rgba(0, 0, 0, .3);
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --plane:        #0d0d0d;
  --surface:      #1a1a19;
  --surface-2:    #232322;
  --ink:          #ffffff;
  --ink-2:        #c3c2b7;
  --muted:        #898781;
  --border:       rgba(255, 255, 255, 0.10);
  --grid:         #2c2c2a;
  --axis:         #383835;
  --series-1:     #3987e5;
  --series-1-soft:#184f95;
  --shadow:       0 1px 2px rgba(0, 0, 0, .4), 0 8px 24px rgba(0, 0, 0, .3);
}

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

body {
  margin: 0;
  font-family: var(--font);
  background: var(--plane);
  color: var(--ink);
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--series-1); }

/* ------------------------------------------------------------------ */
/* Brand                                                               */
/* ------------------------------------------------------------------ */

.brand { display: flex; align-items: center; gap: 10px; }
.brand-mark { width: 30px; height: 30px; flex: none; border-radius: 8px; }
.brand-text { display: flex; flex-direction: column; line-height: 1.15; }
.brand-name { font-weight: 650; letter-spacing: -.01em; }
.brand-sub {
  font-size: 11px; color: var(--muted);
  text-transform: uppercase; letter-spacing: .08em;
}

/* ------------------------------------------------------------------ */
/* Login                                                               */
/* ------------------------------------------------------------------ */

.auth-page {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 24px;
}

.auth-card {
  width: 100%;
  max-width: 380px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 32px;
}

.auth-card .brand { justify-content: center; margin-bottom: 4px; }
.auth-title { font-size: 20px; font-weight: 650; margin: 20px 0 4px; text-align: center; }
.auth-hint { color: var(--ink-2); font-size: 13.5px; margin: 0 0 24px; text-align: center; }

.field { margin-bottom: 14px; }
.field label {
  display: block; font-size: 12px; font-weight: 600;
  color: var(--ink-2); margin-bottom: 6px;
}

input[type="text"], input[type="password"] {
  width: 100%;
  padding: 11px 12px;
  font: inherit;
  font-size: 14.5px;
  color: var(--ink);
  background: var(--plane);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  outline: none;
  transition: border-color .15s, box-shadow .15s;
}

input:focus-visible {
  border-color: var(--series-1);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--series-1) 22%, transparent);
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  padding: 10px 16px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--ink);
  cursor: pointer;
  text-decoration: none;
  transition: background .15s, border-color .15s, opacity .15s;
}
.btn:hover { border-color: var(--muted); }
.btn:focus-visible { outline: 2px solid var(--series-1); outline-offset: 2px; }
.btn[disabled] { opacity: .55; cursor: default; }

.btn-primary {
  width: 100%;
  background: var(--series-1);
  border-color: transparent;
  color: #fff;
  padding: 12px 16px;
  margin-top: 6px;
}
.btn-primary:hover { filter: brightness(1.06); border-color: transparent; }

.btn-icon { padding: 8px 10px; }

.alert {
  display: flex;
  gap: 9px;
  align-items: flex-start;
  font-size: 13.5px;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  margin-bottom: 18px;
  border: 1px solid color-mix(in srgb, var(--critical) 40%, transparent);
  background: color-mix(in srgb, var(--critical) 10%, transparent);
  color: var(--ink);
}
.alert svg { flex: none; margin-top: 2px; }

/* ------------------------------------------------------------------ */
/* Shell                                                               */
/* ------------------------------------------------------------------ */

.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  padding: 12px 20px;
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--border);
}

.topbar-spacer { flex: 1; }
.topbar-actions { display: flex; align-items: center; gap: 8px; }

.user-chip {
  display: flex; align-items: center; gap: 8px;
  font-size: 13px; color: var(--ink-2);
  padding-left: 12px; margin-left: 4px;
  border-left: 1px solid var(--border);
}

main { padding: 20px; max-width: 1320px; margin: 0 auto; }

/* ------------------------------------------------------------------ */
/* Status pill — icon + label, never color alone                       */
/* ------------------------------------------------------------------ */

.pill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 12.5px;
  font-weight: 600;
  padding: 6px 11px 6px 9px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--ink);
  white-space: nowrap;
}
.pill .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); flex: none; }
.pill[data-state="good"]     .dot { background: var(--good); }
.pill[data-state="warning"]  .dot { background: var(--warning); }
.pill[data-state="serious"]  .dot { background: var(--serious); }
.pill[data-state="critical"] .dot { background: var(--critical); }
.pill[data-state="good"] .dot { animation: pulse 2.4s ease-in-out infinite; }

@keyframes pulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--good) 55%, transparent); }
  70%      { box-shadow: 0 0 0 5px transparent; }
}
@media (prefers-reduced-motion: reduce) {
  .pill[data-state="good"] .dot { animation: none; }
}

.timestamp { font-size: 12px; color: var(--muted); white-space: nowrap; }

/* ------------------------------------------------------------------ */
/* Cards & tiles                                                       */
/* ------------------------------------------------------------------ */

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 18px;
}

.card-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
}
.card-title { font-size: 14px; font-weight: 650; margin: 0; }
.card-sub { font-size: 12px; color: var(--muted); margin: 2px 0 0; }
.card-head .spacer { flex: 1; }

.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 14px;
  margin-bottom: 14px;
}

.tile { display: flex; flex-direction: column; gap: 6px; }
.tile-label {
  font-size: 11px;
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: .07em;
}
.tile-value {
  font-size: 26px;
  font-weight: 650;
  letter-spacing: -.02em;
  line-height: 1.1;
}
/* The one hero figure on the page. */
.tile-value.hero { font-size: 48px; }
.tile-value.tile-value-sm { font-size: 20px; }
.tile-unit { font-size: 15px; font-weight: 500; color: var(--muted); margin-left: 3px; }
.tile-note { font-size: 12.5px; color: var(--ink-2); }

/* Two columns, not auto-fit. The pairings are deliberate - throughput beside
   signal, router beside solar - and an auto-fit count that changes with the
   viewport cannot hold a pairing. span-2 is therefore "full width". */
.grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  /* stretch, not start: cards on a row share the height of the tallest one
     so their bottom edges line up. With `start` each card was its own
     natural height and every row ended ragged. */
  align-items: stretch;
}
.span-2 { grid-column: span 2; }
@media (max-width: 980px) {
  .grid { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 720px) {
  .span-2 { grid-column: span 1; }
  main { padding: 14px; }
  .topbar { padding: 10px 14px; }
  .tile-value.hero { font-size: 38px; }
}

/* ------------------------------------------------------------------ */
/* Meter — fill carries severity, track is a lighter step of the ramp  */
/* ------------------------------------------------------------------ */

.meter {
  height: 6px;
  border-radius: 999px;
  background: var(--series-1-soft);
  overflow: hidden;
}
.meter > span {
  display: block;
  height: 100%;
  width: var(--fill, 0%);
  border-radius: 999px;
  background: var(--series-1);
  transition: width .5s ease;
}
.meter[data-severity="warning"]  > span { background: var(--warning); }
.meter[data-severity="serious"]  > span { background: var(--serious); }
.meter[data-severity="critical"] > span { background: var(--critical); }

/* ------------------------------------------------------------------ */
/* Signal bars                                                         */
/* ------------------------------------------------------------------ */
/* Fill count and color come from data- attributes rather than inline
   styles, so the strict CSP needs no 'unsafe-inline' for style-src.   */

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

.bars {
  display: flex;
  align-items: flex-end;
  gap: 4px;
  height: 34px;
  padding-bottom: 4px;
  flex: none;
}

.bars .bar {
  width: 7px;
  border-radius: 2px;
  background: var(--series-1-soft);
  transition: background .4s ease, height .4s ease;
}
.bars .bar:nth-child(1) { height: 28%; }
.bars .bar:nth-child(2) { height: 46%; }
.bars .bar:nth-child(3) { height: 64%; }
.bars .bar:nth-child(4) { height: 82%; }
.bars .bar:nth-child(5) { height: 100%; }

/* Filled bars take the status color for the current level. */
.bars[data-bars="1"] .bar:nth-child(-n+1),
.bars[data-bars="2"] .bar:nth-child(-n+2),
.bars[data-bars="3"] .bar:nth-child(-n+3),
.bars[data-bars="4"] .bar:nth-child(-n+4),
.bars[data-bars="5"] .bar:nth-child(-n+5) { background: var(--good); }

.bars[data-level="fair"][data-bars] .bar:nth-child(-n+3) { background: var(--warning); }
.bars[data-level="poor"][data-bars] .bar:nth-child(-n+2) { background: var(--serious); }
.bars[data-level="bad"][data-bars]  .bar:nth-child(-n+1) { background: var(--critical); }

.bars[data-level="unknown"] .bar { background: var(--series-1-soft); }

.meter-block { margin: 16px 0 0; }
.meter-block-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 13.5px;
  margin-bottom: 8px;
}
.meter-block-head dt { color: var(--ink-2); margin: 0; }
.meter-block-head dd { margin: 0; font-weight: 550; font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------------ */
/* Definition rows                                                     */
/* ------------------------------------------------------------------ */

.rows { display: grid; gap: 1px; background: var(--border); border-radius: var(--radius-sm); overflow: hidden; }
.rows-spaced { margin-top: 12px; }
.rows:empty, .rows[hidden] { display: none; }
.row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 16px;
  padding: 9px 12px;
  background: var(--surface);
  font-size: 13.5px;
}
.row dt { color: var(--ink-2); margin: 0; }
.row dd { margin: 0; font-weight: 550; text-align: right; font-variant-numeric: tabular-nums; }
.row dd.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; }

/* ------------------------------------------------------------------ */
/* Chart                                                               */
/* ------------------------------------------------------------------ */

.chart-wrap { position: relative; }
.chart { display: block; width: 100%; height: var(--chart-h, 180px);
         touch-action: none; }

.chart .grid-line { stroke: var(--grid); stroke-width: 1; }
.chart .axis-line  { stroke: var(--axis); stroke-width: 1; }
.chart .tick       { fill: var(--muted); font-size: 10.5px; font-variant-numeric: tabular-nums; }
.chart .area       { fill: var(--series-1); opacity: .10; }
.chart .line       { fill: none; stroke: var(--series-1); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.chart .end-dot    { fill: var(--series-1); stroke: var(--surface); stroke-width: 2; }
.chart .crosshair  { stroke: var(--axis); stroke-width: 1; }
.chart .hover-dot  { fill: var(--series-1); stroke: var(--surface); stroke-width: 2; }
.chart .hidden     { display: none; }

.tooltip {
  position: absolute;
  pointer-events: none;
  z-index: 5;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
  padding: 7px 10px;
  font-size: 12.5px;
  white-space: nowrap;
  transform: translate(-50%, -100%);
  transition: opacity .12s;
}
.tooltip .tt-time { color: var(--muted); font-size: 11px; }
.tooltip .tt-value { font-weight: 650; font-variant-numeric: tabular-nums; }
.tooltip[hidden] { display: none; }

.empty {
  display: grid;
  place-items: center;
  height: 180px;
  color: var(--muted);
  font-size: 13px;
}

/* ------------------------------------------------------------------ */
/* Solar / MPPT                                                        */
/* ------------------------------------------------------------------ */

.solar-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 14px;
  padding-bottom: 16px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--border);
}

.solar-charts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 18px;
  margin-top: 16px;
}

.solar-figure { margin: 0; }
.solar-figure figcaption {
  font-size: 12.5px;
  font-weight: 650;
  margin-bottom: 8px;
  color: var(--ink);
}
.figure-unit { font-weight: 400; color: var(--muted); font-size: 11.5px; }

.chart-sm { height: calc(var(--chart-h, 180px) * 0.62); }

.solar-foot {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 14px;
}

/* The charge state is a status, so it carries an icon and a word — never
   colour alone. */
.solar-flags { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 10px; }

#solar-empty { display: none; }
#solar-card[data-empty="yes"] #solar-empty { display: grid; }
#solar-card[data-empty="yes"] .solar-charts,
#solar-card[data-empty="yes"] .solar-foot { display: none; }

/* ------------------------------------------------------------------ */
/* Tables                                                              */
/* ------------------------------------------------------------------ */

table { width: 100%; border-collapse: collapse; font-size: 13px; }
th {
  text-align: left;
  font-size: 11px;
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: .06em;
  padding: 0 10px 8px;
  border-bottom: 1px solid var(--border);
}
td {
  padding: 9px 10px;
  border-bottom: 1px solid var(--border);
  font-variant-numeric: tabular-nums;
}
tr:last-child td { border-bottom: 0; }
.td-muted { color: var(--muted); }
.cell-status { display: inline-flex; align-items: center; gap: 7px; white-space: nowrap; }
.cell-status .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--muted); flex: none; }
.cell-status[data-state="good"] .dot { background: var(--good); }
.cell-status[data-state="warning"] .dot { background: var(--warning); }
.table-scroll { overflow-x: auto; max-height: 260px; overflow-y: auto; }
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; }

/* ------------------------------------------------------------------ */
/* Camera                                                              */
/* ------------------------------------------------------------------ */

.camera-body { display: flex; flex-direction: column; gap: 14px; }
.camera-preview {
  position: relative;
  display: grid;
  place-items: center;
  height: 120px;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--muted);
}
.camera-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.camera-actions .btn { flex: 1; min-width: 130px; }

/* ------------------------------------------------------------------ */
/* Live view stage                                                     */
/* ------------------------------------------------------------------ */
/* The stage owns the height. `resize: vertical` gives a native drag
   handle, and the size select sets discrete heights; both write to the
   same box so they can't disagree.                                     */

.stage {
  position: relative;
  overflow: hidden;
  resize: vertical;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: #000;
  min-height: 160px;
}
.stage[data-size="sm"] { height: 220px; }
.stage[data-size="md"] { height: 340px; }
.stage[data-size="lg"] { height: 480px; }
.stage[data-size="xl"] { height: 680px; }

.stage .camera-preview {
  position: relative;
  height: 100%;
  width: 100%;
  border: 0;
  border-radius: 0;
  background: #000;
  cursor: crosshair;
  touch-action: none;
  outline: none;
}
.stage .camera-preview:focus-visible { box-shadow: inset 0 0 0 2px var(--series-1); }
.stage .camera-preview.dragging { cursor: grabbing; }

#camera-frame, #camera-video {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  background: #000;
  -webkit-user-drag: none;
  user-select: none;
}
#camera-frame[hidden], #camera-video[hidden] { display: none; }

/* Fullscreen: the stage becomes the whole screen, so drop the fixed height. */
.stage:fullscreen { height: 100%; width: 100%; border-radius: 0; resize: none; }
.stage:fullscreen .camera-preview { height: 100%; }
.stage:-webkit-full-screen { height: 100%; width: 100%; }

/* Aim marker showing where a click will centre the view. */
.aim {
  position: absolute;
  width: 34px; height: 34px;
  margin: -17px 0 0 -17px;
  border: 2px solid #fff;
  border-radius: 50%;
  box-shadow: 0 0 0 1px rgba(0,0,0,.5), inset 0 0 0 1px rgba(0,0,0,.5);
  pointer-events: none;
  opacity: .9;
  animation: aim-pop .5s ease-out forwards;
}
@keyframes aim-pop {
  from { transform: scale(.4); opacity: 1; }
  to   { transform: scale(1.3); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) { .aim { animation: none; opacity: .6; } }

/* Rubber-band selection for zoom-to-area. */
.selection {
  position: absolute;
  border: 1.5px solid #fff;
  background: rgba(255, 255, 255, .16);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .55), 0 0 0 9999px rgba(0, 0, 0, .28);
  pointer-events: none;
  border-radius: 2px;
}
.selection[hidden] { display: none; }

/* Compass overlay. */
.compass {
  position: absolute;
  top: 10px; right: 10px;
  width: 64px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  pointer-events: none;
}
.compass[hidden] { display: none; }
.compass svg { width: 64px; height: 64px; }
.compass-ring {
  fill: rgba(0, 0, 0, .45);
  stroke: rgba(255, 255, 255, .55);
  stroke-width: 1.5;
}
.compass-cardinal {
  fill: rgba(255, 255, 255, .8);
  font-size: 9px;
  font-weight: 600;
  text-anchor: middle;
  font-family: var(--font);
}
.compass-north { fill: var(--critical); }
.compass-south { fill: rgba(255, 255, 255, .75); }
.compass-hub { fill: #fff; }
#compass-needle { transition: transform .45s ease; transform-origin: 32px 32px; }
@media (prefers-reduced-motion: reduce) { #compass-needle { transition: none; } }

.compass-readout {
  font-size: 11px;
  font-weight: 600;
  color: #fff;
  background: rgba(0, 0, 0, .55);
  padding: 2px 7px;
  border-radius: 999px;
  font-variant-numeric: tabular-nums;
}

/* Segmented control for the drag mode. */
.segmented {
  display: inline-flex;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.seg-btn {
  font: inherit;
  font-size: 12.5px;
  padding: 7px 11px;
  border: 0;
  background: var(--surface-2);
  color: var(--ink-2);
  cursor: pointer;
}
.seg-btn + .seg-btn { border-left: 1px solid var(--border); }
.seg-btn.active { background: var(--series-1); color: #fff; }
.seg-btn:focus-visible { outline: 2px solid var(--series-1); outline-offset: -2px; }

/* Imaging + compass panels. */
.imaging { margin-top: 18px; display: flex; flex-direction: column; gap: 10px; }
.imaging[hidden] { display: none; }
.imaging .inline-field { justify-content: space-between; }
#imaging-sliders { display: flex; flex-direction: column; gap: 10px; }
.slider-row label {
  display: flex; justify-content: space-between;
  font-size: 12px; font-weight: 600; color: var(--ink-2); margin-bottom: 5px;
}
.slider-row input[type="range"] { width: 100%; accent-color: var(--series-1); }
#imaging-note, #compass-status { margin: 0; }

.stage-hint, .stage-badge {
  position: absolute;
  bottom: 8px;
  padding: 4px 9px;
  border-radius: 999px;
  font-size: 11.5px;
  color: #fff;
  background: rgba(0, 0, 0, .55);
  pointer-events: none;
  backdrop-filter: blur(4px);
}
.stage-hint { left: 8px; transition: opacity .3s; }
.stage-hint.faded { opacity: 0; }
.stage-badge { right: 8px; font-weight: 600; }

.stage-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 12px;
}
.stage-bar .spacer { flex: 1; }

.inline-field {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--ink-2);
}
.inline-field select {
  font: inherit;
  font-size: 13px;
  padding: 7px 8px;
  color: var(--ink);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.inline-field select:focus-visible { outline: 2px solid var(--series-1); outline-offset: 1px; }
.camera-placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  font-size: 12.5px;
  color: var(--muted);
}
.camera-placeholder[hidden] { display: none; }

/* ------------------------------------------------------------------ */
/* PTZ control                                                         */
/* ------------------------------------------------------------------ */

.ptz-body {
  display: flex;
  gap: 20px;
  align-items: center;
  flex-wrap: wrap;
}

.dpad {
  display: grid;
  grid-template-columns: repeat(3, 46px);
  grid-template-rows: repeat(3, 46px);
  gap: 6px;
  flex: none;
}

.dpad-btn {
  font: inherit;
  font-size: 17px;
  line-height: 1;
  display: grid;
  place-items: center;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--ink);
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  touch-action: none;
  transition: background .12s, transform .08s, border-color .12s;
}
.dpad-btn:hover:not([disabled]) { border-color: var(--muted); }
.dpad-btn:focus-visible { outline: 2px solid var(--series-1); outline-offset: 2px; }
.dpad-btn.active {
  background: var(--series-1);
  border-color: transparent;
  color: #fff;
  transform: scale(.94);
}
.dpad-btn[disabled] { opacity: .4; cursor: not-allowed; }
.dpad-home { font-size: 19px; }

.ptz-side { display: flex; flex-direction: column; gap: 16px; flex: 1; min-width: 150px; }

.ptz-zoom { display: flex; align-items: center; gap: 8px; }
.ptz-zoom .btn { width: 44px; font-size: 19px; padding: 8px 0; }
.ptz-zoom .btn.active { background: var(--series-1); border-color: transparent; color: #fff; }
.ptz-zoom-label {
  font-size: 11px; font-weight: 600; color: var(--muted);
  text-transform: uppercase; letter-spacing: .07em;
}

.ptz-speed { margin: 0; }
.ptz-speed label {
  display: flex; justify-content: space-between;
  font-size: 12px; font-weight: 600; color: var(--ink-2); margin-bottom: 6px;
}
.ptz-speed input[type="range"] { width: 100%; accent-color: var(--series-1); }

.ptz-presets { margin-top: 18px; display: flex; flex-direction: column; gap: 10px; }
.ptz-presets[hidden] { display: none; }
.preset-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.preset-head .tile-label { flex: 1; }
.btn-small { font-size: 12px; padding: 6px 10px; }

.preset-list { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.preset-empty { font-size: 12.5px; }

/* A preset is a recall button with a delete affordance attached. */
.preset {
  display: inline-flex;
  align-items: stretch;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--surface-2);
}
.preset button {
  font: inherit;
  font-size: 13px;
  border: 0;
  background: none;
  color: var(--ink);
  cursor: pointer;
  padding: 7px 11px;
}
.preset button:hover { background: var(--border); }
.preset .preset-del {
  padding: 7px 9px;
  color: var(--muted);
  border-left: 1px solid var(--border);
  font-size: 14px;
  line-height: 1;
}
.preset .preset-del:hover { color: #fff; background: var(--critical); }
.preset button:focus-visible { outline: 2px solid var(--series-1); outline-offset: -2px; }

#ptz-position { margin: 0; font-variant-numeric: tabular-nums; }

#ptz-card[data-enabled="no"] .ptz-body { opacity: .4; pointer-events: none; }
#ptz-banner { margin: 16px 0 0; }

/* ------------------------------------------------------------------ */
/* Misc                                                                */
/* ------------------------------------------------------------------ */

.banner {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 12px 14px;
  margin-bottom: 14px;
  border-radius: var(--radius-sm);
  font-size: 13.5px;
  border: 1px solid color-mix(in srgb, var(--critical) 40%, transparent);
  background: color-mix(in srgb, var(--critical) 9%, transparent);
}
.banner[data-state="warning"] {
  border-color: color-mix(in srgb, var(--warning) 45%, transparent);
  background: color-mix(in srgb, var(--warning) 10%, transparent);
}
.banner svg { flex: none; margin-top: 1px; }
.banner code { font-size: 12px; word-break: break-all; }
.banner[hidden] { display: none; }

.error-page { display: grid; place-items: center; min-height: 100vh; text-align: center; padding: 24px; }
.error-code { font-size: 56px; font-weight: 650; letter-spacing: -.03em; }

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

[hidden] { display: none !important; }

footer {
  padding: 24px 20px;
  text-align: center;
  color: var(--muted);
  font-size: 12px;
}



/* ------------------------------------------------------------------ */
/* Card reordering                                                     */
/* ------------------------------------------------------------------ */

/* The grip is its own element so the header text stays selectable, and a
   <button> so it is reachable by keyboard. */
.drag-handle {
  flex: none;
  align-self: stretch;
  width: 14px;
  min-height: 22px;
  padding: 0;
  border: 0;
  margin-left: -4px;
  border-radius: 3px;
  background-color: transparent;
  background-image: radial-gradient(circle, var(--muted) 1px, transparent 1px);
  background-size: 4px 4px;
  background-position: center;
  background-repeat: repeat-y;
  cursor: grab;
  opacity: 0;
  transition: opacity .15s;
  touch-action: none;          /* or the browser scrolls instead of dragging */
}
.card:hover .drag-handle { opacity: .7; }
.drag-handle:focus-visible {
  opacity: 1;
  outline: 2px solid var(--series-1);
  outline-offset: 2px;
}
.drag-handle:active { cursor: grabbing; opacity: 1; }

/* The lifted card follows the pointer, so it leaves the grid entirely. */
.card.dragging {
  position: fixed;
  z-index: 100;
  margin: 0;
  pointer-events: none;
  box-shadow: 0 12px 32px rgba(0, 0, 0, .32);
  transform: rotate(.6deg);
  opacity: .95;
}

/* Holds the slot open so the grid does not collapse the moment a card is
   lifted out of flow. */
.card-placeholder {
  border: 2px dashed color-mix(in srgb, var(--series-1) 55%, transparent);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--series-1) 6%, transparent);
}
.card-placeholder.span-2 { grid-column: span 2; }
@media (max-width: 720px) { .card-placeholder.span-2 { grid-column: span 1; } }

/* Text selection while dragging looks like a bug. */
body.reordering { user-select: none; cursor: grabbing; }

/* ------------------------------------------------------------------ */
/* Data usage chart                                                    */
/* ------------------------------------------------------------------ */

.chart-title {
  font-size: 13px; font-weight: 650; margin: 22px 0 6px;
  padding-top: 16px; border-top: 1px solid var(--border);
}
.chart-short { height: calc(var(--chart-h, 180px) * 0.72); }
.ir-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin: 10px 0 2px;
}
.ir-label { font-size: 12px; font-weight: 600; color: var(--ink-2); }
.ir-row .seg-btn[aria-pressed="true"] { background: var(--surface); font-weight: 650; }
.ir-row[data-busy="yes"] { opacity: .6; pointer-events: none; }
/* An author `display: flex` beats the UA sheet's [hidden] rule, so the focus
   row needs this to actually disappear on cameras with no focus control. */
.ir-row[hidden] { display: none; }

/* Absolute focus slider. Sits on its own row under the focus buttons and
   only appears when the camera reports absolute positioning. */
.focus-slide[hidden] { display: none; }
.focus-range {
  flex: 1 1 160px;
  min-width: 120px;
  max-width: 260px;
  accent-color: var(--series-1);
  cursor: pointer;
}
.focus-range:disabled { opacity: .5; cursor: default; }

.usage-bar { fill: var(--series-1); transition: opacity .12s; }
.usage-bar:hover { opacity: .75; }

/* Outages are a fault, so they get the reserved status colours rather than
   a series colour: the modem dropping and the whole router vanishing are
   different problems and should not look alike. */
.outage-bar { fill: var(--serious); transition: opacity .12s; }
.outage-bar[data-kind="router"] { fill: var(--critical); }
.outage-bar[data-ongoing="yes"] { stroke: var(--fg); stroke-width: 1.5; }
.outage-bar:hover { opacity: .75; }
#usage-summary { margin: 6px 0 0; }

/* The vendor UI is the primary action on the camera card, so it looks it. */
.btn-vendor {
  display: inline-flex;
  margin-top: 14px;
  background: var(--series-1);
  border-color: transparent;
  color: #fff;
  padding: 11px 18px;
}
.btn-vendor:hover { filter: brightness(1.06); border-color: transparent; }
.vendor-note { margin: 8px 0 0; }


/* ------------------------------------------------------------------ */
/* Card resizing                                                       */
/* ------------------------------------------------------------------ */

/* Bottom-right corner grip. Horizontal drag changes how many grid columns
   the card spans; vertical drag grows the content inside it. */
.resize-handle {
  position: absolute;
  right: 2px;
  bottom: 2px;
  width: 18px;
  height: 18px;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: nwse-resize;
  opacity: 0;
  transition: opacity .15s;
  touch-action: none;
}
.resize-handle::after {
  content: "";
  position: absolute;
  right: 4px;
  bottom: 4px;
  width: 8px;
  height: 8px;
  border-right: 2px solid var(--muted);
  border-bottom: 2px solid var(--muted);
  border-bottom-right-radius: 3px;
}
.card:hover .resize-handle { opacity: .7; }
.resize-handle:focus-visible {
  opacity: 1;
  outline: 2px solid var(--series-1);
  outline-offset: 2px;
}
.card { position: relative; }

/* While resizing, the card must not animate or the drag feels laggy. */
.card.resizing { transition: none; user-select: none; }
body.resizing-card { cursor: nwse-resize; user-select: none; }

/* A live readout of the span, so the snapping is not a mystery. */
.resize-badge {
  position: absolute;
  right: 8px;
  bottom: 24px;
  z-index: 5;
  padding: 3px 8px;
  border-radius: 999px;
  font-size: 11.5px;
  font-weight: 600;
  color: #fff;
  background: rgba(0, 0, 0, .7);
  pointer-events: none;
}

/* --- Recordings ---------------------------------------------------------
   A player over a coverage strip. The strip replaces a 144-entry dropdown:
   the shape of the day - where footage exists, where the link dropped - is
   the thing you want to see, and clicking a moment beats naming it. */
.rec-pickers { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.rec-select, .rec-date {
  font: inherit;
  font-size: 13px;
  padding: 5px 8px;
  color: var(--fg);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 7px;
}
.rec-date { font-variant-numeric: tabular-nums; }
.rec-daynav { display: flex; align-items: center; gap: 4px; }

.rec-stage { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.rec-video {
  width: 100%;
  max-height: 440px;
  background: #000;
  border-radius: 9px;
  display: block;
}

.rec-timeline-wrap { width: 100%; }
.rec-timeline { width: 100%; height: 58px; display: block; cursor: pointer; }

/* The empty track is only 1.1:1 against the card, so its extent is drawn
   with an outline rather than left to fill contrast. */
.rec-track { fill: var(--surface-2); stroke: var(--line); stroke-width: 1; }
/* One series, so one hue - identity is carried by position, not colour. */
.rec-run { fill: var(--series-1); }
.rec-tick { stroke: var(--line); stroke-width: 1; }
.rec-tick-label { fill: var(--muted); font-size: 10.5px; font-variant-numeric: tabular-nums; }
.rec-playhead { stroke: var(--fg); stroke-width: 2; }
.rec-playhead-cap { fill: var(--fg); }
.rec-hover-line { stroke: var(--muted); stroke-width: 1; stroke-dasharray: 3 3; }
.rec-hover-box { fill: var(--fg); }
.rec-hover-text { fill: var(--surface); font-size: 11px; font-variant-numeric: tabular-nums; }

.rec-controls { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.rec-now {
  font-size: 12.5px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  margin-left: auto;
}
.rec-empty { padding: 14px; color: var(--muted); font-size: 13px; }

/* --- Advanced section ---------------------------------------------------
   The dashboard answers three questions on sight: is the link up, what does
   the camera see, what did it record. The charts and detail below are real
   but secondary, so they start collapsed instead of competing for the top
   of the page. */
.advanced-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin: 6px 0 2px;
}
.btn-advanced { font-weight: 600; }
.advanced-chevron { transition: transform .15s ease; }
.btn-advanced[aria-expanded="true"] .advanced-chevron { transform: rotate(180deg); }
.advanced-hint { margin: 0; }
@media (max-width: 620px) { .advanced-hint { display: none; } }

/* `hidden` alone loses to the grid's own display, the same trap as the
   focus row. */
.grid[hidden] { display: none; }

/* --- Live view: latency, quality, PTZ ------------------------------------
   Latency and quality sit directly above the picture. Both describe the
   picture, and putting them anywhere else means looking away from it to
   find out whether what you are seeing is current. */
.stage-meta { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; flex-wrap: wrap; }
.stage-meta-spacer { flex: 1 1 auto; }

.lat {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 12.5px; font-variant-numeric: tabular-nums; color: var(--ink-2);
}
.lat-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); }
/* Reserved status colours, and never colour alone - the number is always
   there, so a red dot is emphasis rather than the only signal. */
.lat[data-grade="good"]    .lat-dot { background: var(--good); }
.lat[data-grade="warning"] .lat-dot { background: var(--warning); }
.lat[data-grade="bad"]     .lat-dot { background: var(--critical); }

.quality { display: inline-flex; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; }
.q-btn {
  font: inherit; font-size: 12px; padding: 4px 10px; border: 0;
  background: var(--surface); color: var(--ink-2); cursor: pointer;
}
.q-btn + .q-btn { border-left: 1px solid var(--line); }
.q-btn:hover { background: var(--surface-2); }
.q-btn.active { background: var(--series-1); color: #fff; font-weight: 650; }

.ptz-row { display: flex; gap: 16px; align-items: flex-start; margin-top: 12px; flex-wrap: wrap; }
.ptz-row[hidden] { display: none; }
.ptz-side { flex: 1 1 260px; min-width: 0; display: flex; flex-direction: column; gap: 2px; }

.ptz-pad {
  display: grid;
  grid-template-columns: repeat(3, 38px);
  grid-template-rows: repeat(3, 38px);
  gap: 3px;
  flex: 0 0 auto;
}
.ptz-btn {
  border: 1px solid var(--line);
  background: var(--surface);
  border-radius: 7px;
  cursor: pointer;
  position: relative;
}
.ptz-btn:hover { background: var(--surface-2); }
.ptz-btn:active { background: var(--series-1); }
/* Arrows drawn with a rotated chevron rather than glyphs, so they stay put
   at any font size and inherit the theme's ink. */
.ptz-btn::after {
  content: ""; position: absolute; inset: 0; margin: auto;
  width: 8px; height: 8px;
  border-top: 2px solid var(--ink-2); border-right: 2px solid var(--ink-2);
}
.ptz-btn:active::after { border-color: #fff; }
.ptz-n::after  { transform: rotate(-45deg)  translate(-1px, 1px); }
.ptz-ne::after { transform: rotate(0deg); }
.ptz-e::after  { transform: rotate(45deg)   translate(-1px, 1px); }
.ptz-se::after { transform: rotate(90deg); }
.ptz-s::after  { transform: rotate(135deg)  translate(-1px, 1px); }
.ptz-sw::after { transform: rotate(180deg); }
.ptz-w::after  { transform: rotate(225deg)  translate(-1px, 1px); }
.ptz-nw::after { transform: rotate(270deg); }
.ptz-home::after {
  width: 10px; height: 10px; border: 2px solid var(--ink-2); border-radius: 2px;
  transform: none;
}
.ptz-row[data-busy="yes"] { opacity: .6; pointer-events: none; }

.chips { display: flex; flex-wrap: wrap; gap: 5px; }
.chip {
  font: inherit; font-size: 12.5px; padding: 4px 10px;
  border: 1px solid var(--line); border-radius: 999px;
  background: var(--surface); color: var(--fg); cursor: pointer;
}
.chip:hover { background: var(--surface-2); }
.chip[data-state="running"] { background: var(--series-1); color: #fff; border-color: var(--series-1); }
.chip-del {
  margin-left: 6px; color: var(--muted); font-weight: 700;
  border: 0; background: none; cursor: pointer; padding: 0 2px;
}
.chip-del:hover { color: var(--critical); }
