/* IP Monitor styles.
   Colours are defined once as variables and used by role. The dark-theme values
   are chosen for the dark surface, not derived by flipping the light ones. */

:root {
  color-scheme: light;
  --surface-0: #f4f3f0;
  --surface-1: #fcfcfb;
  --surface-2: #f0efec;
  --text-primary: #0b0b0b;
  --text-secondary: #52514e;
  --grid: #e6e5e1;
  --border: #dddcd7;

  /* SmokePing draws its median line in #202020. p95 is ours and borrows the same
     plain ink, because every hue on the chart already means a loss level. */
  /* SmokePing draws the median core in #202020. */
  --median-core: #202020;

  /* Statuses. The first four are SmokePing's own loss colours, so a green dot in
     the list means exactly what a green line means on the chart. "Degraded" is
     deliberately outside that palette: it is about latency, not loss, and
     SmokePing has no colour for it. */
  --st-ok: #26ff00;
  --st-loss: #ff0000;
  --st-down: #a00000;
  --st-degraded: #fab219;
  --st-error: #ff00ff;
  --st-neutral: #8a8980;

  --shadow: 0 1px 2px rgb(0 0 0 / 6%), 0 4px 12px rgb(0 0 0 / 4%);
  --radius: 10px;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --surface-0: #121211;
    --surface-1: #1a1a19;
    --surface-2: #252523;
    --text-primary: #ffffff;
    --text-secondary: #c3c2b7;
    --grid: #343431;
    --border: #3a3a36;
    --median-core: #d8d7cf;
    --st-neutral: #6f6e67;
    --shadow: 0 1px 2px rgb(0 0 0 / 40%), 0 4px 12px rgb(0 0 0 / 30%);
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --surface-0: #121211;
  --surface-1: #1a1a19;
  --surface-2: #252523;
  --text-primary: #ffffff;
  --text-secondary: #c3c2b7;
  --grid: #343431;
  --border: #3a3a36;
  --median-core: #d8d7cf;
  --st-neutral: #6f6e67;
  --shadow: 0 1px 2px rgb(0 0 0 / 40%), 0 4px 12px rgb(0 0 0 / 30%);
}

* { box-sizing: border-box; }

/* The browser's own rule for [hidden] is display:none, but any display we set on
   a class overrides it. Without this, elements hidden from JS stay on screen. */
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--surface-0);
  color: var(--text-primary);
  font: 14px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
}

h1, h2, h3 { margin: 0 0 .25rem; font-weight: 600; line-height: 1.25; }
h1 { font-size: 1.4rem; }
h2 { font-size: 1.15rem; }
h3 { font-size: .95rem; }

.mono { font-family: ui-monospace, "Cascadia Mono", Consolas, monospace; }
.muted { color: var(--text-secondary); font-size: .85rem; margin: .15rem 0; }
.centred { display: grid; place-items: center; min-height: 60vh; padding: 1rem; }
.error { color: var(--st-down); font-size: .85rem; }
.spacer { flex: 1; }

/* --- Controls ------------------------------------------------------------- */

button { font: inherit; cursor: pointer; border-radius: 7px; border: 1px solid transparent; padding: .45rem .85rem; }
button.primary { background: var(--text-primary); color: var(--surface-1); }
button.secondary { background: var(--surface-2); color: var(--text-primary); border-color: var(--border); }
button.plain { background: none; color: var(--text-secondary); }
button.plain:hover { color: var(--text-primary); }
button.danger { background: none; color: var(--st-down); border-color: var(--st-down); }
button.icon { background: none; color: var(--text-secondary); padding: .3rem .5rem; font-size: 1rem; }
button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--text-primary); outline-offset: 2px;
}
button[disabled] { opacity: .5; cursor: default; }

label { display: block; margin: .6rem 0; font-size: .85rem; color: var(--text-secondary); }
input, select, textarea {
  display: block; width: 100%; margin-top: .25rem; padding: .45rem .6rem;
  font: inherit; color: var(--text-primary);
  background: var(--surface-1); border: 1px solid var(--border); border-radius: 7px;
}
textarea { resize: vertical; }
.with-suffix { display: flex; align-items: center; gap: .5rem; }
.with-suffix i { font-style: normal; font-size: .8rem; color: var(--text-secondary); white-space: nowrap; }
.checkbox-row { display: flex; align-items: center; gap: .5rem; }
.checkbox-row input { width: auto; margin: 0; }

/* --- Layout --------------------------------------------------------------- */

.topbar {
  display: flex; align-items: center; gap: 1rem;
  padding: .6rem 1.25rem; background: var(--surface-1);
  border-bottom: 1px solid var(--border); position: sticky; top: 0; z-index: 5;
}
.brand-icon { width: 26px; height: 26px; border-radius: 7px; display: block; flex: none; }
.topbar nav { display: flex; gap: .25rem; }
.topbar-right { margin-left: auto; display: flex; align-items: center; gap: .5rem; }
/* Quiet on purpose: it is there to be glanced at, not read. */
.freshness { font-size: .72rem; color: var(--text-secondary); white-space: nowrap; }

/* The bar wraps rather than squeezing: on a narrow window it becomes two rows and
   keeps every control, which is the opposite of hiding one of them. */
.topbar { flex-wrap: wrap; }
.topbar-actions { display: flex; align-items: center; gap: .5rem; margin-left: 1rem; }
/* No label beside it - the bar is full, and "name or address" inside the field
   says the same thing in none of the width. */
.topbar-actions input[type="search"] { width: 13rem; margin: 0; }
.tab { background: none; color: var(--text-secondary); }
.tab.active { background: var(--surface-2); color: var(--text-primary); }

main { max-width: 1180px; margin: 0 auto; padding: 1.25rem; }

/* 1180px is a reading width, and it is right for the forms and the single chart.
   The board is not prose: it wants every column the screen can give it, because
   one more column is one less screenful to scroll. */
main:has(#page-overview:not([hidden])) { max-width: 1760px; }

.bar { display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem; }
.bar-filters { justify-content: space-between; }
.target-heading { flex: 1; }

.card {
  background: var(--surface-1); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 1rem; box-shadow: var(--shadow);
}
.card-login { width: min(360px, 100%); }
.columns { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 1rem; align-items: start; }
.stack { display: grid; gap: 1rem; }

.banner {
  padding: .6rem .85rem; border-radius: 7px; font-size: .85rem;
  background: color-mix(in srgb, var(--st-degraded) 18%, var(--surface-1));
  border: 1px solid var(--st-degraded); margin: 0 0 1rem;
}

/* --- Target list ---------------------------------------------------------- */

.tag-filter { display: flex; flex-wrap: wrap; gap: .35rem; margin-bottom: 1rem; }

/* The target list inside the category dialog. Scrolls rather than growing, so
   the Save button stays on screen with two hundred targets. */
dialog.wide { width: min(560px, 92vw); }
.pick-list {
  max-height: 46vh; overflow-y: auto; margin-top: .25rem;
  border: 1px solid var(--border); border-radius: 7px; background: var(--surface-1);
}
.pick-row {
  display: flex; align-items: center; gap: .55rem;
  padding: .35rem .6rem; border-bottom: 1px solid var(--grid); cursor: pointer;
}
.pick-row:last-child { border-bottom: 0; }
.pick-row:hover { background: var(--surface-2); }
.pick-row input { width: auto; margin: 0; flex: none; }
.pick-row .pick-name { flex: 1; color: var(--text-primary); }
.pick-row .pick-where { font-size: .75rem; color: var(--text-secondary); white-space: nowrap; }
.pick-empty { padding: .6rem; color: var(--text-secondary); font-size: .85rem; }

/* A heading that can be renamed says so by behaving like a control. */
.group-name-button {
  font: inherit; color: inherit; letter-spacing: inherit; text-transform: inherit;
  background: none; border: 0; padding: 0; cursor: pointer;
}
.group-name-button:hover { text-decoration: underline; }
.tag {
  font-size: .78rem; padding: .2rem .6rem; border-radius: 999px;
  background: var(--surface-1); border: 1px solid var(--border); color: var(--text-secondary);
}
.tag:hover { border-color: var(--text-secondary); }
.tag.on { background: var(--text-primary); color: var(--surface-1); border-color: var(--text-primary); }
.tag.clear { border-style: dashed; }

/* Chips on a row are labels, not controls: no border, no hover, nothing that
   suggests they can be clicked. */

/* One wordmark per theme, and CSS picks: the theme can change without the page
   reloading, so swapping a src in JavaScript would need a listener that this
   does not. */
.wordmark { display: none; width: 100%; max-width: 200px; height: auto; margin: 0 auto .35rem; }
.wordmark-light { display: block; }
.wordmark-dark { display: none; }
:root[data-theme="dark"] .wordmark-light { display: none; }
:root[data-theme="dark"] .wordmark-dark { display: block; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .wordmark-light { display: none; }
  :root:not([data-theme="light"]) .wordmark-dark { display: block; }
}


/* --- The counters --------------------------------------------------------- */

.counters {
  display: flex; align-items: center; gap: 1.5rem; flex-wrap: wrap;
  margin-bottom: 1rem; padding-bottom: .9rem; border-bottom: 1px solid var(--grid);
}

/* Exactly one big number on the page, and it is the one somebody walking past
   the screen needs. Proportional figures, not tabular: at this size tabular
   digits are all as wide as a zero and a number like 121 reads loose. */
.hero { display: flex; align-items: baseline; gap: .6rem; }
.hero strong { font-size: 2.6rem; line-height: 1; font-weight: 600; }
.hero span { font-size: .82rem; color: var(--text-secondary); max-width: 9rem; }
.hero-bad strong { color: var(--st-down); }

.chips { display: flex; flex-wrap: wrap; gap: .4rem; }
.chip {
  display: inline-flex; align-items: baseline; gap: .35rem;
  padding: .3rem .6rem; border-radius: 999px; cursor: pointer;
  background: var(--surface-1); border: 1px solid var(--border);
  color: inherit; font: inherit; font-size: .78rem;
}
.chip .dot { align-self: center; }
.chip b { font-variant-numeric: tabular-nums; }
.chip span { color: var(--text-secondary); }
.chip:hover { border-color: var(--text-secondary); }
.chip.on { border-color: var(--text-primary); background: var(--surface-2); }


/* --- The board ------------------------------------------------------------ */

/* How many columns of groups fit is a question about the window, so auto-fill
   answers it - no resize listener, and it keeps up while the edge is dragged.
   What each ROW carries is a question about how many targets there are, and
   that is decided in app.js. */
.board {
  display: grid; gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--group-min, 16rem)), 1fr));
}
.board[data-density="compact"] { --group-min: 15rem; }
.board[data-density="dense"] { --group-min: 13rem; }

/* Each group is its own container, so a row sizes itself against the width of
   ITS OWN column rather than the window's. That is the whole trick behind the
   board getting narrower as more columns appear: nothing has to know how many
   columns there happen to be - not the media queries, and not JavaScript. */
.group {
  container-type: inline-size;
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface-1); overflow: hidden;
}

.group-head {
  display: flex; align-items: baseline; gap: .6rem; margin: 0;
  font-size: .7rem; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  background: var(--surface-2); border-bottom: 1px solid var(--border);
  padding: .3rem .55rem;
}
.group-count { margin-left: auto; font-weight: 400; letter-spacing: 0; text-transform: none; color: var(--text-secondary); }
.group-rows { display: grid; padding: .15rem; }

.row {
  display: grid; align-items: center; gap: .55rem;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  padding: .22rem .35rem; border: 0; border-radius: 5px;
  background: none; color: inherit; font: inherit; font-size: .82rem; text-align: left;
  width: 100%; cursor: pointer;
}
.row:hover { background: var(--surface-2); }
.row + .row { border-top: 1px solid var(--grid); }

/* The state is a dot AND the word. Colour alone fails for about one man in
   twelve, and this is the one column on the board that must not be guessable -
   which is why, when the word has to go, the dot changes shape instead. */
.row-state { display: flex; align-items: center; gap: .4rem; min-width: 0; }
.row-state-text { display: none; font-size: .72rem; color: var(--text-secondary); width: 5.4rem; }

/* Both lines clip rather than overflow: without this the host runs out of its
   box and under the numbers next to it. */
.row-name { display: flex; flex-direction: column; min-width: 0; }
.row-title, .row-host { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row-title { font-weight: 500; line-height: 1.25; }

/* The address costs a second line on every row, and at three columns there is no
   width for it anyway. It comes back together with the state word - and until
   then it is a hover away and on the target's own page. */
.row-host { display: none; font-size: .7rem; line-height: 1.2; }

/* No min-width: these sit in auto grid tracks, and a track is already as wide as
   the widest cell in it - so the numbers line up down a box for free. Reserving
   width on top of that only steals it from the name, which is the one column
   that cannot be guessed from anything else. */
.num { font-variant-numeric: tabular-nums; text-align: right; font-size: .8rem; }
/* How long it has been down, in the cell the latency would be in - which is empty
   on a target that is not answering. */
.num-down { color: var(--st-down); }

.uptimes { display: flex; gap: .9rem; align-items: baseline; }
.uptime { font-size: .78rem; color: var(--text-secondary); white-space: nowrap; }
.uptime b { font-variant-numeric: tabular-nums; color: var(--text-primary); font-weight: 600; }

/* Everything a narrow column gives up, given back as the column earns it. These
   blocks sit AFTER the rules they override, because they carry the same weight
   and the later one wins.

   Note these are @container, not @media: what matters is the width of the COLUMN
   a row sits in, not of the window. A 1400px window showing three columns gives
   each row less room than a 780px window showing one, and a media query cannot
   tell those apart - it was getting this exactly backwards before. */
@container (min-width: 34rem) {
  .row-state-text { display: block; }
  .row-host { display: block; }
}
.board[data-density="dense"] .row-state-text,
.board[data-density="dense"] .row-host { display: none; }


/* A status is never carried by colour alone. Where there is room the word is
   written beside the dot; where there is not - the dense board, where a column
   of words would cost more than it gives - the MARK ITSELF changes shape. A
   circle is fine and anything that is not a circle is not fine, which survives
   both colour blindness and a photocopier.
   The inner ring keeps SmokePing's bright green visible on a white surface,
   where on its own it almost vanishes. */
.dot {
  width: 10px; height: 10px; border-radius: 50%; flex: none;
  box-shadow: inset 0 0 0 1px rgb(0 0 0 / 25%);
}
.dot.st-degraded { border-radius: 2px; transform: rotate(45deg) scale(.85); }
.dot.st-loss { border-radius: 1px; clip-path: polygon(50% 0, 100% 100%, 0 100%); box-shadow: none; }
.dot.st-down, .dot.st-error { border-radius: 1px; }
.dot.st-unknown, .dot.st-paused { box-shadow: inset 0 0 0 2px var(--surface-1); }
.st-ok        { background: var(--st-ok); }
.st-degraded  { background: var(--st-degraded); }
.st-loss      { background: var(--st-loss); }
.st-down      { background: var(--st-down); }
.st-error     { background: var(--st-error); }
.st-unknown, .st-paused { background: var(--st-neutral); }

.number { font-variant-numeric: tabular-nums; text-align: right; font-size: .9rem; }
.number small { display: block; font-size: .7rem; color: var(--text-secondary); font-variant-numeric: normal; }

.badge {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .2rem .6rem; border-radius: 999px; font-size: .8rem;
  background: var(--surface-2); border: 1px solid var(--border);
}

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

.segmented { display: inline-flex; border: 1px solid var(--border); border-radius: 7px; overflow: hidden; }
.segmented button { border-radius: 0; border: none; background: var(--surface-1); color: var(--text-secondary); padding: .35rem .7rem; font-size: .85rem; }
.segmented button + button { border-left: 1px solid var(--border); }
.segmented button.active { background: var(--surface-2); color: var(--text-primary); }

/* SmokePing stacks one graph per fixed window down the page, each in its own
   frame. Ten of them is a long page - that is what the original does too. */
.range-stack { display: grid; gap: 1rem; }
.stack-card .chart-header { cursor: default; }

.chart-header {
  font-size: .8rem; color: var(--text-secondary);
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 6px;
  padding: .3rem .6rem; margin-bottom: .8rem;
}

.range-group { display: flex; align-items: center; gap: .5rem; }

.canvas-wrap { position: relative; }

/* The drag selection. Drawn in CSS rather than on the canvas so that dragging
   does not force a full chart redraw on every pointer move. */
.selection {
  position: absolute; top: 12px; bottom: 26px;
  background: color-mix(in srgb, var(--text-primary) 12%, transparent);
  border-left: 1px solid var(--text-secondary);
  border-right: 1px solid var(--text-secondary);
  pointer-events: none;
}
/* The plot keeps one appearance whatever the interface theme is - SmokePing has
   only one, and a chart that inverts with the theme is a second one it does not
   have. The rounded edge keeps it from reading as a hole punched in a dark card. */
.canvas-wrap canvas { display: block; width: 100%; border-radius: 4px; }
/* The cursor hairline is drawn in CSS, not on the canvas: that way it does not
   force a full redraw on every mouse move. */
.canvas-wrap.has-cursor::after {
  content: ""; position: absolute; top: 12px; bottom: 26px; left: var(--cursor-x);
  width: 1px; background: var(--text-secondary); opacity: .55; pointer-events: none;
}
/* While data reloads the chart stays on screen, dimmed. A skeleton flashing in
   its place would move the page under someone mid-way through reading a value. */
.canvas-wrap.loading canvas { opacity: .45; transition: opacity .15s; }

.tooltip {
  position: absolute; pointer-events: none; z-index: 3; min-width: 9rem;
  background: var(--surface-1); border: 1px solid var(--border); border-radius: 8px;
  padding: .5rem .65rem; box-shadow: var(--shadow); font-size: .8rem;
}
.tt-t { color: var(--text-secondary); margin-bottom: .3rem; font-size: .75rem; }
.tt-r { display: flex; align-items: center; gap: .4rem; }
.tt-r b { margin-left: auto; font-variant-numeric: tabular-nums; }
.tt-loss { margin-top: .25rem; padding-top: .25rem; border-top: 1px solid var(--grid); }

/* --- Statistics under the chart ------------------------------------------- */

/* RRDtool renders this block in a fixed-width font and lines the columns up; a
   proportional font puts "18.0 ms avg" and "0.00 % avg" at different widths and
   the rows stop reading as a table. Hence monospace, and a min-width in ch so
   avg/max/min/now sit in the same columns from one row to the next. */
.stats {
  margin-top: .9rem; padding-top: .85rem; border-top: 1px solid var(--grid);
  display: grid; gap: .28rem; font-size: .78rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.stat-row { display: grid; grid-template-columns: 13ch 1fr; gap: .5rem 1rem; align-items: baseline; }
.stat-label { color: var(--text-secondary); }
.stat-values { display: flex; flex-wrap: wrap; align-items: baseline; gap: .2rem 1.1ch; }

.stat-pair { white-space: nowrap; flex: none; min-width: 11ch; }
.stat-pair i {
  font-style: normal; color: var(--text-secondary); margin-left: .6ch;
}
.key-pair { display: inline-flex; align-items: center; gap: .5ch; flex: none; white-space: nowrap; }
.swatch { width: 10px; height: 10px; border-radius: 2px; box-shadow: inset 0 0 0 1px rgb(0 0 0 / 25%); }

/* "end:" sits at the right edge of the probe line, as RRDtool stamps it. */
.stat-probe { color: var(--text-secondary); width: 100%; }
.stat-end { margin-left: auto; }

/* --- Table ---------------------------------------------------------------- */

.table { width: 100%; border-collapse: collapse; font-size: .85rem; }
.table th, .table td { padding: .35rem .5rem; text-align: right; border-bottom: 1px solid var(--grid); }
.table th:first-child, .table td:first-child { text-align: left; }
.table td { font-variant-numeric: tabular-nums; }
.table th { color: var(--text-secondary); font-weight: 500; position: sticky; top: 0; background: var(--surface-1); }
#table-card { max-height: 26rem; overflow: auto; }


/* The first column is not crushed by a long value (the prober's config path),
   and the value wraps instead of escaping the card. */
.definitions { display: grid; grid-template-columns: minmax(8rem, auto) 1fr; gap: .3rem .75rem; margin: 0 0 .75rem; font-size: .85rem; }
.definitions dt { color: var(--text-secondary); }
.definitions dd { margin: 0; text-align: right; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }

.incident.open { border-left: 3px solid var(--st-down); }
.incident-head { display: flex; align-items: center; gap: .6rem; margin-bottom: .5rem; flex-wrap: wrap; }
.incident-head time { margin-left: auto; font-size: .78rem; }

/* The verdict is the reason the page exists, so it is the one thing set apart
   from the list of symptoms below it. */
.cause {
  margin: 0 0 .6rem; padding: .45rem .7rem; border-radius: 7px;
  background: var(--surface-2); font-size: .85rem;
}

.trace { margin-top: .6rem; padding-top: .5rem; border-top: 1px solid var(--grid); font-size: .8rem; }
.hop { display: flex; align-items: baseline; gap: .5rem; padding: .12rem 0; }
.hop-n { color: var(--text-secondary); min-width: 1.2rem; text-align: right; }

.alert-row { display: flex; align-items: center; gap: .5rem; padding: .35rem 0; font-size: .85rem; border-bottom: 1px solid var(--grid); }
.alert-row:last-child { border-bottom: none; }
.alert-row time { margin-left: auto; color: var(--text-secondary); font-size: .78rem; }

/* --- Dialog and toast ----------------------------------------------------- */

dialog {
  border: 1px solid var(--border); border-radius: var(--radius); padding: 1.25rem;
  background: var(--surface-1); color: var(--text-primary); width: min(420px, 92vw);
  box-shadow: var(--shadow);
}
dialog::backdrop { background: rgb(0 0 0 / 45%); }
.actions { display: flex; align-items: center; gap: .5rem; margin-top: 1rem; }

.toast {
  position: fixed; bottom: 1.25rem; left: 50%; transform: translateX(-50%);
  background: var(--text-primary); color: var(--surface-1);
  padding: .55rem 1rem; border-radius: 8px; box-shadow: var(--shadow); z-index: 10; font-size: .875rem;
}
.toast.bad { background: var(--st-down); color: #fff; }

/* Grouped settings. The border is the quietest thing that still says "these
   three belong together"; a heavier one turns a form into a stack of boxes. */
fieldset {
  border: 0; border-top: 1px solid var(--border);
  margin: 1.1rem 0 0; padding: .9rem 0 0;
}
fieldset legend {
  padding: 0; font-size: .78rem; font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase; color: var(--text-secondary);
}
fieldset > .muted { margin: .3rem 0 .8rem; }
fieldset label .muted { display: block; margin-top: .25rem; font-size: .75rem; }

.footer {
  max-width: 1180px; margin: 0 auto; padding: 0 1.25rem 1.5rem;
  font-size: .75rem; color: var(--text-secondary); text-align: center;
}
.login-footer { max-width: none; margin: .9rem 0 0; padding: 0; text-align: center; }

.system-status { font-size: .78rem; color: var(--text-secondary); display: flex; align-items: center; gap: .4rem; }

@media (max-width: 820px) {
  .columns { grid-template-columns: 1fr; }
  .bar { flex-wrap: wrap; }
}

@media (max-width: 560px) {
  main { padding: .75rem; }
  .topbar { padding: .6rem .75rem; gap: .5rem; }
  /* On a narrow screen the status text wraps onto three lines and pushes the
     sign-out button off screen. The coloured dot says the same thing, and the
     detail is in the list below anyway. */
  .system-status { font-size: 0; }
  .system-status .dot { width: 8px; height: 8px; }
  .hero strong { font-size: 2rem; }
  .counters { gap: 1rem; }
  .num { font-size: .78rem; }
  .row { gap: .5rem; }

  /* The four tabs plus the title and the sign-out button do not fit across a
     phone, and pushing them off the edge takes the whole page with them: the
     board then scrolls sideways under a viewport that cannot show it. The nav
     drops to its own line and scrolls by itself if it still has to. */
  .topbar { flex-wrap: wrap; row-gap: .5rem; }
  .topbar nav {
    order: 3; width: 100%; overflow-x: auto; scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .topbar nav::-webkit-scrollbar { display: none; }
  .topbar-right { margin-left: auto; }
  /* The label moves above the values: at 375px a 7.5rem column leaves no room
     for numbers. */
  .stat-row { grid-template-columns: 1fr; gap: .1rem; }
  .stat-label { font-size: .76rem; }
  /* The search field gives up its fixed width and takes the row. */
  .topbar-actions { width: 100%; }
  .topbar-actions input[type="search"] { flex: 1; width: auto; }
}
