/* ---- status bar ---- */
.statusbar {
  display: flex;
  align-items: center;
  gap: 0.86rem;
  padding: 0.71rem 1.43rem;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}
.machine-name { font: 600 1rem var(--mono); }
.spacer { flex: 1; }
.age { color: var(--muted); font-size: 0.86rem; }
.age.stale { color: var(--warning); }

.badge {
  padding: 0.14rem 0.57rem;
  font-size: 0.79rem;
  font-weight: 600;
  color: var(--muted);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 999px;
}

.conn {
  display: inline-flex;
  align-items: center;
  gap: 0.43rem;
  font-size: 0.86rem;
  font-weight: 600;
  color: var(--muted);
}
.conn::before {
  content: "";
  width: 0.57rem; height: 0.57rem; border-radius: 50%;
  background: currentColor;
}
.conn[data-state="live"] { color: #3fb950; }
.conn[data-state="offline"] { color: var(--critical); }
.conn[data-state="waiting"] { color: var(--warning); }
.conn[data-state="connecting"] { color: var(--muted); }

/* Connecting and waiting are the loading states: switching source, or a reconnect, passes through
   them before "live". The status dot becomes a small spinner so the switch shows visible progress
   instead of a label that can look stuck. */
.conn[data-state="connecting"]::before,
.conn[data-state="waiting"]::before {
  width: 0.72rem;
  height: 0.72rem;
  background: transparent;
  border: 2px solid currentColor;
  border-top-color: transparent;
  animation: conn-spin 0.7s linear infinite;
}

@keyframes conn-spin { to { transform: rotate(360deg); } }

/* Honour a reduced-motion preference: keep the ring, drop the rotation. */
@media (prefers-reduced-motion: reduce) {
  .conn[data-state="connecting"]::before,
  .conn[data-state="waiting"]::before { animation: none; }
}

/* ---- grid of cards ----
   Explicit columns filled shortest-first by the view, rather than CSS grid or multi-column. A grid
   row is as tall as its tallest cell, so short cards leave dead space beneath them; multi-column
   balances to equal heights, so a few cards fill a few columns and leave the rest of the width
   empty. Flex columns each take an equal share of the full width, always.

   The column *count* is computed in views/live.js from a minimum width in em, so it follows the root
   font size — a bigger display gets wider columns, not merely more of them. */
.grid {
  display: flex;
  align-items: flex-start;
  gap: 0.86rem;
  padding: 1rem 1.14rem;
}

/* Loading the sensors area — switching source or machine, or waiting on the first frame. The stale
   cards dim and a spinner overlays the grid, so the switch or fetch reads as loading rather than a
   dashboard frozen on the previous numbers. The spinner is a pseudo-element so it survives the
   replaceChildren() that paints each frame. */
.grid.is-loading {
  position: relative;
  min-height: 8rem;
}
.grid.is-loading > * {
  opacity: 0.35;
  pointer-events: none;
  transition: opacity 0.2s;
}
.grid.is-loading::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 2.4rem;
  height: 2.4rem;
  margin: -1.2rem 0 0 -1.2rem;
  border: 3px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: grid-spin 0.8s linear infinite;
}

@keyframes grid-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .grid.is-loading::after { animation: none; }
}

.col {
  flex: 1 1 0;
  /* Without this a wide card would push its column past its share and unbalance the row. */
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.86rem;
}

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  overflow: hidden;
  width: 100%;
}
.card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.57rem;
  padding: 0.57rem 0.86rem;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
}
.card-title { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.badge.kind { text-transform: uppercase; letter-spacing: .4px; flex: none; }

/*
 * Whose hardware is on screen. Coloured rather than left neutral because it answers a question the
 * numbers themselves cannot: two machines' dashboards look identical, and a temperature read as
 * "mine" when it is somebody else's is the kind of wrong that gets acted on.
 *
 * Deliberately not red/green. Neither state is a problem — watching another machine is the whole
 * point of the cloud source — so this distinguishes without alarming. It also carries a text label,
 * not just a colour, which is what makes it work for a colourblind reader and in a screenshot.
 */
.badge.origin { flex: none; }

.badge.origin[data-origin="self"] {
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}

.badge.origin[data-origin="remote"] {
  color: var(--warning);
  border-color: color-mix(in srgb, var(--warning) 45%, transparent);
  background: color-mix(in srgb, var(--warning) 12%, transparent);
}

.group { padding: 0.14rem 0.86rem 0.43rem; }
.group-label {
  display: block;
  margin: 0.43rem 0 0.14rem;
  font-size: 1.3rem;
  font-weight: 700;
  letter-spacing: .6px;
  text-transform: uppercase;
  color: var(--muted);
  /* border-style: dotted; */

}

.sensor {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.57rem;
  padding: 0.07rem 0.29rem;
  margin: 0 -0.29rem;
  border-radius: 0.36rem;
}
.sensor.is-clickable { cursor: pointer; }
.sensor.is-clickable:hover { background: var(--surface-2); }
.sensor.is-clickable:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* The charted row stays marked while its chart is open, so the panel below is never orphaned. */
.sensor.is-charted { background: var(--surface-2); }
.sensor.is-charted .s-name { color: var(--accent); font-weight: 600; }

/* A derived rail, indented under the raw channel it is computed from. Its value is styled as a
   derivation rather than a reading — it is inferred from a divider ratio, not measured. */
.sensor-rail { padding-top: 0; }
.sensor-rail .s-name { padding-left: 0.14rem; }
.rail-arrow { color: var(--border); }
.rail-alias { font-weight: 600; }
.rail-tag {
  margin-left: 0.43rem;
  padding: 0 0.36rem;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: .4px;
  text-transform: uppercase;
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 0.29rem;
}
.sensor-rail .s-val { color: var(--accent); }

/* An alias on a channel that has no divider maths — a rename, so it sits beside the channel rather
   than generating a second row that would repeat the same number. */
.rail-alias-inline {
  margin-left: 0.43rem;
  font-weight: 600;
  color: var(--accent);
}

/* Column captions for the value block, once per group. Sentence case at a readable size — the
   previous 10px uppercase tracking was decorative and genuinely hard to read. */
.sensor-head {
  font-size: 0.79rem;
  color: var(--muted);
  padding-bottom: 0;
  pointer-events: none;
}
.sensor-head .s-val, .sensor-head .s-range {
  font-family: var(--sans);
  font-size: 1rem;
  font-weight: 600;
}

/* Three columns and no more: name, current value, range. The name takes whatever is left, because
   it is the column you scan. */
.s-name {
  flex: 1 1 auto;
  min-width: 0;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.s-right { display: inline-flex; align-items: center; gap: 0.57rem; flex: none; }
/* Sized for the widest reading these columns carry — "11.980 V" — so voltages at three decimals
   do not shunt the range cell around as values change. */
.s-val {
  font: 0.93rem var(--mono);
  font-variant-numeric: tabular-nums;
  min-width: 4.9rem;
  text-align: right;
}
.s-val.is-warning { color: var(--warning); }
.s-val.is-critical { color: var(--critical); font-weight: 700; }

/* Min and max share one cell, recessive, so "now" stays the thing you read first. */
.s-range {
  font: 1rem var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  min-width: 7.15rem;
  text-align: right;
}

/* percentage meter */
.meter {
  width: 4.6rem; height: 0.43rem;
  background: var(--track);
  border-radius: 999px;
  overflow: hidden;
}
.meter i {
  display: block;
  height: 100%;
  background: var(--accent);
  border-radius: 999px;
  transition: width .3s ease;
}
.meter i.is-high { background: var(--warning); }

/* ---- states ---- */
.empty, .empty-row {
  color: var(--muted);
  font-size: 0.93rem;
}
.empty {
  flex: 1;
  padding: 2.86rem 1.43rem;
  text-align: center;
  border: 1px dashed var(--border);
  border-radius: var(--radius);
}
.empty-row { padding: 0.57rem 1rem 0.86rem; }

/* ---- footer ---- */
.footer {
  display: flex;
  justify-content: space-between;
  gap: 0.86rem;
  padding: 1rem 1.43rem 1.71rem;
  color: var(--muted);
  font-size: 0.86rem;
}
.footer code { font-family: var(--mono); color: var(--text); }

/* The 60s/90s chart-window toggle in the footer. */
.live-window { display: flex; align-items: center; gap: 0.36rem; }
.live-window-label { color: var(--muted); }
.live-win {
  padding: 0.14rem 0.5rem;
  font: 600 0.79rem var(--sans);
  color: var(--muted);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 0.43rem;
  cursor: pointer;
}
.live-win:hover { color: var(--text); border-color: var(--muted); }
.live-win.is-on {
  color: #fff;
  background: var(--accent);
  border-color: var(--accent);
}

/* ---- 1-minute chart ----
   Inline, directly under the sensor it belongs to, rather than a modal: the rest of the dashboard
   stays visible and live, which is the point of watching one value in context. It inherits the
   card's width, so it is responsive for free. */
.chart-inline {
  margin: 0.29rem 0 0.57rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 0.57rem;
  overflow: hidden;
  cursor: default;
}

.chart-head {
  display: flex;
  align-items: center;
  gap: 0.57rem;
  padding: 0.36rem 0.57rem;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
}
.chart-window {
  font-size: 0.71rem;
  letter-spacing: .3px;
  text-transform: uppercase;
  color: var(--muted);
  flex: none;
}
.chart-stats {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: right;
  font: 0.79rem var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.chart-close {
  flex: none;
  padding: 0.07rem 0.43rem;
  font-size: 0.86rem;
  line-height: 1.3;
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 0.43rem;
  cursor: pointer;
}
.chart-close:hover { color: var(--text); }

.chart-body { position: relative; padding: 0.57rem 0.57rem 0.14rem; }

/* The row's tooltip text, made permanent while the chart is open. pre-line keeps the line breaks
   without needing markup per line. */
.chart-details {
  margin: 0;
  padding: 0.5rem 0.71rem 0.64rem;
  font-size: 0.79rem;
  line-height: 1.5;
  white-space: pre-line;
  color: var(--muted);
  border-top: 1px solid var(--border);
}
.chart-details:empty { display: none; }
/* The viewBox is set in JS to the element's measured pixel size, so this height is the drawing's real
   height — not a box a fixed viewBox is stretched into. Bigger than it was: an inline chart is easier
   to read a shape off when it has room to show one. */
.chart-svg { display: block; width: 100%; height: 14rem; overflow: visible; }

/* Recessive axes and grid; the data line is the only assertive mark. */
.chart-grid { stroke: var(--border); stroke-width: 1; vector-effect: non-scaling-stroke; }
.chart-axis {
  fill: var(--muted);
  font: 11px var(--mono);
}
.chart-axis-y { text-anchor: end; }
.chart-axis-end { text-anchor: end; }
.chart-line {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
.chart-cross { stroke: var(--muted); stroke-width: 1; stroke-dasharray: 3 3; vector-effect: non-scaling-stroke; }
.chart-dot {
  fill: var(--accent);
  /* A surface-coloured ring keeps the marker legible where it sits on the line. */
  stroke: var(--surface);
  stroke-width: 2;
}
/* The lowest and highest readings in the window, marked persistently so the extremes are legible
   without hovering. */
.chart-min-dot, .chart-max-dot {
  stroke: var(--surface);
  stroke-width: 2.5;
  pointer-events: none;
}
.chart-min-dot { fill: var(--min); }
.chart-max-dot { fill: var(--max); }
.chart-min-label, .chart-max-label {
  font: 700 11px var(--mono);
  paint-order: stroke;
  stroke: var(--surface);
  stroke-width: 3px;
  stroke-linejoin: round;
  pointer-events: none;
}
.chart-min-label { fill: var(--min); }
.chart-max-label { fill: var(--max); }
.chart-tip {
  position: absolute;
  transform: translate(-50%, -160%);
  padding: 0.21rem 0.57rem;
  font: 0.79rem var(--mono);
  white-space: nowrap;
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 0.43rem;
  pointer-events: none;
}

/* ---- responsive ----
   The shared half of these breakpoints (top bar, controls, source picker, tabs) lives in
   styles.css; what remains here is the Live view's own reflow. */
@media (max-width: 860px) {
  .grid { padding: 0.86rem; }
}

@media (max-width: 560px) {
  /* The column count itself is computed in views/live.js from the available width, so a phone gets
     one column without needing a rule here. */
  .grid { padding: 0.71rem; }
  .statusbar { flex-wrap: wrap; gap: 0.57rem 0.71rem; padding: 0.57rem 0.86rem; }
  .statusbar .spacer { flex-basis: 100%; height: 0; }
  .footer { flex-direction: column; gap: 0.29rem; padding: 0.86rem 0.86rem 1.43rem; }
  /* A grow-spacer in a column would add a tall gap; the stacked items need none. */
  .footer .spacer { display: none; }
  .chart-svg { height: 10.7rem; }
}
