/* Hoptech Monitor: light/dark tokens per the validated reference palette. */
:root {
  color-scheme: light;
  --page: #f9f9f7;
  --surface: #fcfcfb;
  --ink: #0b0b0b;
  --ink-2: #52514e;
  --muted: #898781;
  --grid: #e1e0d9;
  --baseline: #c3c2b7;
  --border: rgba(11, 11, 11, 0.10);
  --series-1: #2a78d6;
  --band: rgba(42, 120, 214, 0.10);
  --band-edge: rgba(42, 120, 214, 0.30);
  --good: #0ca30c;
  --warning: #fab219;
  --serious: #ec835a;
  --critical: #d03b3b;
  --good-text: #006300;
  /* The logo's leaf green, held as a token because it is the one part of the
     mark that sits against the page rather than against the rabbit. At
     #105342 on a near-black page it disappears, so dark mode lightens it;
     the eye keeps the brand green in both themes because it sits on orange. */
  --logo-leaf: #105342;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --page: #0d0d0d;
    --surface: #1a1a19;
    --ink: #ffffff;
    --ink-2: #c3c2b7;
    --muted: #898781;
    --grid: #2c2c2a;
    --baseline: #383835;
    --border: rgba(255, 255, 255, 0.10);
    --series-1: #3987e5;
    --band: rgba(57, 135, 229, 0.14);
    --band-edge: rgba(57, 135, 229, 0.34);
    --good-text: #0ca30c;
    --logo-leaf: #2fb790;
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --page: #0d0d0d;
  --surface: #1a1a19;
  --ink: #ffffff;
  --ink-2: #c3c2b7;
  --muted: #898781;
  --grid: #2c2c2a;
  --baseline: #383835;
  --border: rgba(255, 255, 255, 0.10);
  --series-1: #3987e5;
  --band: rgba(57, 135, 229, 0.14);
  --band-edge: rgba(57, 135, 229, 0.34);
  --good-text: #0ca30c;
  --logo-leaf: #2fb790;
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  font: 15px/1.45 system-ui, -apple-system, "Segoe UI", sans-serif;
  background: var(--page);
  color: var(--ink);
  padding-bottom: env(safe-area-inset-bottom);
}
a { color: var(--series-1); text-decoration: none; }
button {
  font: inherit; cursor: pointer; border: 1px solid var(--border);
  background: var(--surface); color: var(--ink);
  border-radius: 8px; padding: 7px 14px;
}
button.primary { background: var(--series-1); border-color: var(--series-1); color: #fff; }
button.small { padding: 3px 10px; font-size: 13px; }
button.ghost { border: none; background: none; color: var(--series-1); padding: 4px 6px; }
button.danger { color: var(--critical); }
input, select {
  font: inherit; color: var(--ink); background: var(--surface);
  border: 1px solid var(--baseline); border-radius: 8px; padding: 8px 10px;
  width: 100%;
}
label { font-size: 13px; color: var(--ink-2); display: block; margin: 10px 0 4px; }

header.topbar {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 16px; background: var(--surface);
  border-bottom: 1px solid var(--border);
  position: sticky; top: 0; z-index: 10;
}
/* The lockup: the rabbit, then the name. "hoptech" is the company and
   "monitor" is which of its products this is, so they get different weights
   rather than a separator character doing that job silently.

   The gap is smaller than it looks it should be, because the mark is a
   side-on animal and its muzzle and sprig reach out to the right. Measured
   from the artwork box the spacing looks generous; measured from the ink it
   is about right. */
.logo { display: inline-flex; align-items: center; gap: 7px; color: var(--ink); }
.logo .hoplogo { display: block; flex: 0 0 auto; }
.logo .wordmark {
  font-weight: 500; letter-spacing: -0.01em; font-size: 17px;
  color: var(--ink-2); white-space: nowrap;
}
.logo .wordmark b { font-weight: 700; color: var(--ink); }
/* The sign-in lockup stacks, because side by side at this scale the rabbit
   is 60px wide and pushes the name off centre. */
.logo-lg {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  margin-bottom: 18px;
}
.logo-lg .wordmark { font-size: 22px; }
.topbar nav { display: flex; gap: 4px; margin-left: auto; }
.topbar nav a {
  padding: 6px 12px; border-radius: 8px; color: var(--ink-2); font-size: 14px;
}
.topbar nav a.active { background: var(--band); color: var(--series-1); font-weight: 600; }

main { max-width: 1060px; margin: 0 auto; padding: 16px; }

h1 { font-size: 20px; margin: 10px 0 2px; }
h2 { font-size: 15px; color: var(--ink-2); font-weight: 600; margin: 22px 0 10px; }
.sub { color: var(--muted); font-size: 13px; }

/* 270px, not 240px. The tile name is held to one line (see .tile-name), so a
   column too narrow does not wrap the name, it truncates it: at 240px a real
   device called "Front of house fridge" arrived as "FRONT OF HOUSE FRI...".
   270px fits the longest names people actually type while still giving three
   columns at the 1060px page width and two on a tablet. */
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); gap: 12px; }

/* Sites on the dashboard, once there is more than one of them. The header is
   a real summary rather than just a name: a folded site still has to be able
   to tell you something in it is in alert, or folding would mean not
   noticing. */
.site-search { display: flex; gap: 8px; align-items: center; margin: 4px 0 14px; }
.site-search input {
  flex: 1; min-width: 0; font: inherit; color: var(--ink);
  background: var(--surface); border: 1px solid var(--baseline);
  border-radius: 8px; padding: 8px 11px;
}
.site-search input:focus { outline: none; border-color: var(--series-1); }
/* input and select both inherit width:100% from the shared rule, which in a
   flex row lets the sort dropdown take the whole line and squeeze the search
   box down to its clear button. Both are sized here instead. */
.site-search select { width: auto; flex: 0 0 auto; max-width: 46%; }
.site-search button { flex: 0 0 auto; white-space: nowrap; }

details.site { margin-bottom: 14px; border-bottom: 1px solid var(--border); padding-bottom: 12px; }
details.site:last-of-type { border-bottom: 0; }
details.site > summary {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  cursor: pointer; padding: 6px 0; list-style: none;
}
details.site > summary::-webkit-details-marker { display: none; }
/* The twisty is drawn rather than borrowed, so it points the same way in
   every browser and turns with the fold. */
details.site > summary::before {
  content: ""; flex: 0 0 auto; width: 0; height: 0;
  border-left: 5px solid currentColor;
  border-top: 4px solid transparent; border-bottom: 4px solid transparent;
  color: var(--muted); transition: transform 0.15s; transform-origin: 30% 50%;
}
details.site[open] > summary::before { transform: rotate(90deg); }
details.site > summary:hover .site-name { color: var(--series-1); }
.site-name { font-size: 1.05rem; font-weight: 600; }
.site-summary { margin-left: auto; display: flex; align-items: center; gap: 10px; color: var(--muted); font-size: 0.86rem; }
@media (max-width: 560px) {
  .site-summary { margin-left: 0; width: 100%; }
}

.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 12px; padding: 14px;
}
/* Device tiles, with the instrument look carried by two corner ticks. A
   dot-grid field used to run under the whole tile, which put texture behind
   every piece of text on the dashboard; the ticks say the same thing at the
   edges, where nothing has to be read through them. */
.card.device {
  cursor: pointer; transition: border-color 0.15s; position: relative;
}
.card.device::before, .card.device::after {
  content: ""; position: absolute; width: 9px; height: 9px;
  border: 1.5px solid var(--baseline); pointer-events: none;
  transition: border-color 0.15s;
}
.card.device::before { top: -1px; left: -1px; border-right: 0; border-bottom: 0; border-top-left-radius: 12px; }
.card.device::after { bottom: -1px; right: -1px; border-left: 0; border-top: 0; border-bottom-right-radius: 12px; }
.card.device:hover { border-color: var(--series-1); }
.card.device:hover::before, .card.device:hover::after { border-color: var(--series-1); }

/* A device that has not been released to the customer. Only ever rendered for
   an admin: the API does not send these to anyone else. Dashed, and dimmed
   until hovered, so a shelf of overheard beacons reads as a holding area
   rather than as part of the customer's fleet. */
.card.device.unreleased { border-style: dashed; opacity: 0.62; }
.card.device.unreleased:hover { opacity: 1; }

/* The public demo's banner. It is tinted with the series blue rather than a
   status hue on purpose: a visitor has to be told plainly that this is a
   demo, but nothing on the banner is a device state, and good/warning/
   serious/critical stay reserved for things that actually are. No new colour
   is introduced, so both dark themes pick it up from --band unchanged. */
.demo-banner {
  background: var(--band); border: 1px solid var(--band-edge);
  border-radius: 12px; padding: 12px 14px; margin: 12px 0 4px;
  display: flex; flex-direction: column; gap: 8px;
}
.demo-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  font-size: 13.5px; color: var(--ink-2);
}
.demo-row b { color: var(--ink); }
.demo-actions { gap: 6px 14px; }
.demo-left {
  display: inline-flex; align-items: center; gap: 6px;
  color: var(--ink); font-weight: 600;
}
.demo-left .ic { color: var(--series-1); }
/* The reply to a button press gets the whole width, so a long sentence does
   not squeeze the buttons it is sitting beside. */
.demo-msg { flex-basis: 100%; color: var(--muted); font-size: 12.5px; }
.demo-actions button.ghost {
  border: 1px solid var(--band-edge); border-radius: 8px; padding: 4px 10px;
  background: var(--surface);
}

.ic { width: 17px; height: 17px; vertical-align: -3.5px; }
.st-good { color: var(--good-text); }
.st-critical { color: var(--critical); }
.st-muted { color: var(--muted); }
.st-accent { color: var(--series-1); }
.devicon { display: inline-flex; margin-right: 7px; color: var(--series-1); vertical-align: -4px; }
.devicon .ic { width: 19px; height: 19px; }

/* The name is held to one line. It used to wrap, and a two-line name pushed
   that one tile's reading, sparkline and footer down out of step with the
   rest of the row, so the eye could not scan the numbers across. */
.tile-top { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.tile-name {
  font-family: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, monospace;
  font-weight: 600; font-size: 11.5px; letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--ink-2);
  flex: 1 1 auto; min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tile-top .status { flex: 0 0 auto; }
.tile-main { display: flex; align-items: center; gap: 10px; margin-top: 10px; }
.tile-glyph svg { display: block; }
.tile-read { min-width: 0; }
.tile-glyph { flex: 0 0 auto; }
.glyph-strip { display: flex; gap: 26px; flex-wrap: wrap; margin: 14px 0 6px; padding: 18px 20px 14px; }
.glyph-cell { display: flex; flex-direction: column; align-items: center; gap: 2px; }
.glyph-cell svg { display: block; }

/* The reading under each glyph. Same tabular figures as the dashboard tiles,
   two steps smaller, so the two screens agree about what a number looks like. */
.glyph-val {
  font-family: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, monospace;
  font-size: 19px; font-weight: 700; letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums; margin-top: 6px;
}
.glyph-val .unit {
  font-size: 12px; font-weight: 500; color: var(--muted);
  margin-left: 3px; letter-spacing: 0;
}
.glyph-lab {
  font-size: 11px; letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--muted);
}

/* The tile's headline figure. It is a large standalone number rather than a
   column of aligned ones, so it is set proportionally and only the digits
   are tabular, which keeps it from jittering as a temperature ticks over. */
.tile-value {
  font-family: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, monospace;
  font-size: 30px; font-weight: 700; letter-spacing: -0.02em; margin: 0;
  line-height: 1.05;
  font-variant-numeric: tabular-nums;
}
.tile-value .unit {
  font-size: 14px; font-weight: 500; color: var(--muted);
  margin-left: 3px; letter-spacing: 0;
}
/* The change across the sparkline's window, in plain ink. Not red or green:
   for a fridge neither direction is good news by itself, and the status pill
   already says whether anything is wrong. */
.tile-delta {
  font-size: 12px; color: var(--ink-2); margin-top: 3px;
  font-variant-numeric: tabular-nums; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}

/* Reserve the sparkline's height. Without this the tiles jumped as the
   curves arrived, which on a phone moved whatever you were about to tap.
   No dot-grid field here: a 2px curve 44 pixels tall has no room to spare,
   and the texture both crowded the line and showed through the "no
   readings" message on a quiet device. The threshold band is the only
   backdrop the curve needs. */
.spark { height: 44px; margin: 10px 0 2px; }
.spark svg { display: block; }
.spark-empty { fill: var(--muted); font-size: 11.5px; }

.tile-sub {
  display: flex; gap: 12px; margin-top: 8px; padding-top: 8px;
  border-top: 1px solid var(--grid);
  font-size: 12.5px; color: var(--muted); align-items: center;
}
.tile-sub .ic { width: 15px; height: 15px; vertical-align: -3px; }

.status {
  display: inline-flex; align-items: center; gap: 5px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 11px; font-weight: 600; letter-spacing: 0.07em; text-transform: uppercase;
}
.status::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.status.ok::before { animation: pulse 2.4s ease-in-out infinite; }
.status.alert::before { animation: pulse 0.9s ease-in-out infinite; }
@keyframes pulse { 0%,100% { opacity: 1 } 50% { opacity: 0.35 } }
@media (prefers-reduced-motion: reduce) { .status::before { animation: none !important; } }
.status.ok { color: var(--good-text); }
.status.alert { color: var(--critical); }
.status.warn { color: var(--serious); }
.status.off { color: var(--muted); }

.alert-row {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 10px 4px; border-bottom: 1px solid var(--grid); font-size: 14px;
}
.alert-row .icon { line-height: 1.3; }
.alert-row .icon .ic { width: 18px; height: 18px; }
.alert-row .when { color: var(--muted); font-size: 12.5px; white-space: nowrap; }
.alert-row.resolved { color: var(--ink-2); }

.chart-wrap { position: relative; }
.chart-tip {
  position: absolute; pointer-events: none; display: none;
  background: var(--surface); border: 1px solid var(--border); border-radius: 8px;
  padding: 6px 9px; font-size: 12.5px; box-shadow: 0 2px 10px rgba(0,0,0,0.18);
  white-space: nowrap; z-index: 5;
}
.chart-tip b { font-variant-numeric: tabular-nums; }
.range-row { display: flex; gap: 6px; margin: 10px 0; flex-wrap: wrap; }
.range-row button.active { background: var(--band); border-color: var(--series-1); color: var(--series-1); font-weight: 600; }

.tick { fill: var(--muted); font-size: 11px; font-variant-numeric: tabular-nums; }
.gridline { stroke: var(--grid); stroke-width: 1; }
.baseline { stroke: var(--baseline); stroke-width: 1; }

.login-box { max-width: 360px; margin: 12vh auto 0; }
.login-box .card { padding: 22px; }
.err { color: var(--critical); font-size: 13.5px; margin-top: 8px; min-height: 18px; }

.modal-back {
  position: fixed; inset: 0; background: rgba(0,0,0,0.45); z-index: 20;
  display: flex; align-items: flex-start; justify-content: center; padding: 4vh 12px;
  overflow-y: auto;
}
.modal { background: var(--page); border-radius: 14px; max-width: 760px; width: 100%; padding: 18px; }
.modal .close { float: right; }

.row { display: flex; gap: 10px; }
.row > * { flex: 1; }
.pill {
  font-size: 11.5px; padding: 2px 8px; border-radius: 999px;
  background: var(--band); color: var(--series-1); font-weight: 600;
  white-space: nowrap;
}
.pill.muted-pill { background: none; color: var(--muted); border: 1px solid var(--grid); }
/* A gateway still reaching the broker in clear. Not an error: it is the
   expected state mid-migration, and it should stop being true. */
.pill.warn-pill { background: none; color: var(--warning); border: 1px solid var(--warning); }
.muted { color: var(--muted); }
.mt { margin-top: 14px; }
.right { text-align: right; }
footer { text-align: center; color: var(--muted); font-size: 12px; padding: 30px 0 20px; }

/* ---------- records and the forms that create them ----------
   A card lists what exists; the form that adds another one lives behind a
   disclosure underneath it. That way a page of real records reads as a
   list rather than a wall of empty inputs. */
.list-row {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 11px 2px; border-bottom: 1px solid var(--grid);
}
.list-row:last-of-type { border-bottom: 0; }
.list-main { flex: 1 1 190px; min-width: 0; }
.list-title { font-weight: 600; overflow-wrap: anywhere; }
.list-sub { color: var(--muted); font-size: 12.5px; margin-top: 2px; overflow-wrap: anywhere; }
.list-sub code { font-size: 12px; }
.list-row > button { flex: 0 0 auto; opacity: 0.6; }
.list-row > button:hover, .list-row > button:focus { opacity: 1; }
/* Pills and the row's action button travel together, so a narrow screen
   wraps them as one block instead of scattering them down the card. */
.list-actions {
  flex: 0 0 auto; display: flex; align-items: center;
  gap: 8px; flex-wrap: wrap; justify-content: flex-end;
}
.list-actions button { opacity: 0.6; }
.list-actions button:hover, .list-actions button:focus { opacity: 1; }

details.adder { border-top: 1px solid var(--grid); margin-top: 6px; }
details.adder > summary {
  cursor: pointer; list-style: none; color: var(--series-1);
  font-size: 14px; font-weight: 600;
  display: flex; align-items: center; gap: 9px; min-height: 44px;
}
details.adder > summary::-webkit-details-marker { display: none; }
details.adder > summary::before {
  content: "+"; font-size: 18px; line-height: 1; width: 15px; text-align: center;
}
details.adder[open] > summary::before { content: "\2212"; }
details.adder[open] { padding-bottom: 6px; }

.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 16px; }
.form-grid .field.wide { grid-column: 1 / -1; }
.field > label:first-child { margin-top: 12px; }
.hint { color: var(--muted); font-size: 12.5px; margin: 5px 0 0; }
.hint code { font-size: 12px; }

/* Segmented control: exclusive choices you can read and hit, in place of a
   select whose options read like database values. */
.seg { display: flex; margin-top: 4px; max-width: 440px; }
.seg button { flex: 1; border-radius: 0; min-height: 42px; border-left-width: 0; }
.seg button:first-child { border-radius: 8px 0 0 8px; border-left-width: 1px; }
.seg button:last-child { border-radius: 0 8px 8px 0; }
.seg button.active {
  background: var(--band); border-color: var(--series-1);
  color: var(--series-1); font-weight: 600;
}

.check {
  display: flex; align-items: center; gap: 10px; min-height: 44px;
  margin: 8px 0 0; font-size: 14px; color: var(--ink); cursor: pointer;
}
.check input { width: 19px; height: 19px; flex: 0 0 auto; margin: 0; }

/* Month links wrap to a tidy grid of tap targets rather than a run-on
   sentence of dot-separated dates. */
.report-months { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.report-months a {
  font-size: 13px; padding: 6px 10px; border-radius: 8px;
  border: 1px solid var(--grid); font-variant-numeric: tabular-nums;
}
.report-months a:hover, .report-months a:focus { border-color: var(--series-1); }

.topic-preview {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 13px; color: var(--ink-2); margin: 14px 0 0;
}
.topic-preview code {
  background: var(--band); color: var(--series-1);
  padding: 3px 8px; border-radius: 6px; overflow-wrap: anywhere;
}

/* ---------- phones ----------
   Everything here is either a touch target growing to a thumb-sized 44px,
   a multi-column layout collapsing to one, or the 16px input font iOS
   Safari needs to stop zooming the page in when a field takes focus. */
@media (max-width: 640px) {
  main { padding: 12px calc(12px + env(safe-area-inset-left)) 12px calc(12px + env(safe-area-inset-right)); }
  header.topbar {
    flex-wrap: wrap; gap: 4px 12px; padding: 8px 12px;
    padding-left: calc(12px + env(safe-area-inset-left));
    padding-right: calc(12px + env(safe-area-inset-right));
  }
  .topbar nav {
    margin-left: 0; width: 100%; gap: 2px;
    overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
  }
  .topbar nav::-webkit-scrollbar { display: none; }
  .topbar nav a { white-space: nowrap; padding: 9px 12px; }

  input, select, textarea { font-size: 16px; padding: 10px 12px; }
  button { padding: 10px 16px; min-height: 44px; }
  button.small { padding: 6px 12px; min-height: 38px; font-size: 13.5px; }
  button.ghost { min-height: 40px; padding: 6px 8px; }

  .row { flex-direction: column; gap: 0; }
  .row > * { flex: none; }
  .form-grid { grid-template-columns: minmax(0, 1fr); }
  .grid { grid-template-columns: minmax(0, 1fr); }

  .glyph-strip { gap: 14px 20px; padding: 14px; justify-content: space-around; }
  .range-row { gap: 6px; }
  .alert-row { flex-wrap: wrap; }
  .alert-row .when { width: 100%; padding-left: 28px; }
  .modal { padding: 14px; }
  h1 { font-size: 19px; }
}

/* The inline "edit this person" form, opened under the row it belongs to.
   Indented and tinted so it reads as attached to that row rather than as
   another card in the list. */
.card.edit-user {
  margin: 2px 0 10px 12px;
  border-left: 3px solid var(--series-1);
}
