/* Insta-Trac Locus — operator dashboard. Light chrome, Insta-Trac design system.
   Brand tokens below; state colours kept distinct for map/status readability. */

:root{
  --it-ink:#0A0A0A;
  --it-blue:#0058D0;
  --it-azure:#1E7BF2;
  --it-deep:#0A4FA8;
  --it-cyan:#16C7E6;
  --it-slate:#5B6B7A;
  --it-surface:#F5F8FC;
  --it-line:#DCE5F0;
  --it-white:#FFFFFF;
  --it-curve:linear-gradient(45deg,#0A4FA8,#1E7BF2);

  /* Asset-state colours — NOT brand colours; kept so state meaning stays readable. */
  --present:#16C7E6;
  --unseen:#F59E0B;
  /* LOCUS024ZJ (024ZA M9): the old red was 3.8:1 on white. Missing is #DC2626 (4.8:1); anything
     destructive, every error and badge is --danger, #B91C1C (6.5:1); a form control's edge is
     --input-border (3.4:1, where --it-line is 1.3:1). */
  --missing:#DC2626;
  --danger:#B91C1C;
  --input-border:#7A8699;
  /* One type scale for both consoles: 14 px for data, labels and controls; 12 px only for captions. */
  --fs-body:14px;
  --fs-caption:12px;
  --retired:#94A3B8;
  --lastknown:#7AA2F7;   /* RFID108A: frozen last-known records -- must match app.js LASTKNOWN_COLOR */

  /* LOCUS020J: superseded history, greyed out. NEUTRAL grey on purpose -- blue already means the
     Locus Network and selection, and yellow/orange mean unseen or a warning. */
  --superseded-bg:#E3E6EA;           /* the band: not white (a live row), not --it-surface (selected, page) */
  --superseded-bg-selected:#D0D4DA;  /* a selected superseded row: a darker grey, never the live blue */
  --superseded-ink:#50575F;          /* every line of the row: mid-grey, AA on both bands */
  --superseded-edge:#50575F;         /* the selected row's left edge, where a live row's is blue */
  --superseded-pill:#5F6770;         /* the SUPERSEDED badge, solid, with white text */

  /* Compatibility alias: app.js sets an inline style using var(--muted-2). */
  --muted-2:var(--it-slate);

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

* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body {
  font-family: var(--font);
  background: var(--it-surface);
  color: var(--it-ink);
  font-size: 14px;
  -webkit-font-smoothing: antialiased;
}
button { font-family: inherit; cursor: pointer; }
input, select, button { font-size: 14px; font-family: inherit; }
[hidden] { display: none !important; }

/* ---------- Brand lockup ---------- */
.brand-logo { display: block; height: auto; max-width: 100%; }
.login-logo { width: 180px; margin: 0 auto 2px; }
.brand-sub { color: var(--it-slate); font-size: 12px; letter-spacing: 0.02em; text-align: center; }
/* LOCUS024ZJ: the page's name is its <h1> on both consoles; it keeps the brand line's look. */
h1.brand-sub { margin: 0; font-weight: 400; }
h1.brand-locus { margin: 0; }

/* ---------- Login view ---------- */
.login-view {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  background: var(--it-surface);
  padding: 24px;
}
.login-card {
  width: 100%;
  max-width: 420px;
  background: var(--it-white);
  border: 1px solid var(--it-line);
  border-radius: 12px;
  padding: 32px 30px 26px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  box-shadow: 0 12px 40px rgba(10,79,168,0.10);
  position: relative;
  overflow: hidden;
}
.login-card::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 3px;
  background: var(--it-curve);
}
.login-card .brand { display: flex; flex-direction: column; align-items: center; gap: 8px; margin-bottom: 6px; }
.field { display: flex; flex-direction: column; gap: 6px; }
.field span { color: var(--it-slate); font-size: var(--fs-caption); font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; }
.field input {
  background: var(--it-white);
  border: 1px solid var(--input-border);
  border-radius: 8px;
  padding: 10px 12px;
  color: var(--it-ink);
  outline: none;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.field input:focus { border-color: var(--it-blue); box-shadow: 0 0 0 3px rgba(0,88,208,0.15); }
/* LOCUS012A: the show/hide eye. The wrapper is a <div>, never a <span>, because ".field span"
   above is the uppercase field LABEL and a span here would inherit it. The input keeps its own
   look; all the wrapper does is give the button something to be absolute inside, and reserve the
   room on the right so a long password never runs under the glyph. */
.pw-wrap { position: relative; display: block; }
.pw-wrap input { width: 100%; padding-right: 40px; }
.pw-eye {
  position: absolute;
  right: 6px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: none;
  border-radius: 6px;
  background: none;
  color: var(--it-slate);
}
.pw-eye:hover { background: var(--it-surface); color: var(--it-ink); }
.pw-eye:focus-visible { outline: 2px solid var(--it-blue); outline-offset: 1px; }
.pw-eye svg { width: 16px; height: 16px; display: block; }
.btn-primary {
  background: var(--it-blue);
  color: var(--it-white);
  border: none;
  border-radius: 8px;
  padding: 11px 14px;
  font-weight: 600;
  transition: background 0.15s;
}
.btn-primary:hover { background: var(--it-deep); }
.btn-primary:disabled { opacity: 0.6; cursor: default; }
.login-error {
  background: #FEF2F2;
  border: 1px solid rgba(185,28,28,0.35);
  color: var(--danger);
  border-radius: 8px;
  padding: 9px 11px;
  font-size: 14px;
}
.login-footer {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--it-slate);
  font-size: 12px;
}
.login-footer .res-logo { width: 90px; height: auto; opacity: 0.9; }

/* ---------- Dashboard layout ---------- */
.dash-view { height: 100%; display: flex; flex-direction: column; }
.topbar {
  height: 56px;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 18px;
  background: var(--it-white);
  border-bottom: 1px solid var(--it-line);
  gap: 16px;
}
.topbar-left { display: flex; align-items: center; gap: 14px; }
.topbar-left .brand-logo { width: 150px; }
.brand-divider { width: 1px; height: 22px; background: var(--it-line); }
.brand-locus { color: var(--it-ink); font-weight: 700; letter-spacing: 0.01em; font-size: 16px; }
.topbar-mid { flex: 1; display: flex; justify-content: center; }
.site-select {
  background: var(--it-white);
  color: var(--it-ink);
  border: 1px solid var(--input-border);
  border-radius: 8px;
  padding: 7px 12px;
  min-width: 220px;
  max-width: 360px;
}
.site-select:focus { outline: none; border-color: var(--it-blue); box-shadow: 0 0 0 3px rgba(0,88,208,0.15); }
.topbar-right { display: flex; align-items: center; gap: 14px; }
.user-org { color: var(--it-slate); font-size: 14px; }
.btn-ghost {
  background: transparent;
  color: var(--it-blue);
  border: none;
  border-radius: 6px;
  padding: 6px 8px;
  font-weight: 600;
}
.btn-ghost:hover { color: var(--it-deep); text-decoration: underline; }

/* ---------- Control bar (RFID069B) ----------
   Full-width chrome row between the topbar and the dash body: view toggle | time range |
   basemap + count chips. The segmented controls keep their visual language but sit on
   solid chrome now, so the translucent overlay treatment is gone. */
.controlbar {
  flex: 0 0 auto;
  background: var(--it-white);
  border-bottom: 1px solid var(--it-line);
}
.cb-row {
  display: flex; align-items: center; gap: 12px;
  flex-wrap: wrap;                /* below ~1100px the three zones wrap to two rows */
  padding: 8px 18px;
}
.cb-left { display: flex; align-items: center; }
.cb-mid { flex: 1 1 auto; display: flex; justify-content: center; }
.cb-right { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* Map / Table toggle: same segmented language as the basemap switch, but its own class so
   the basemap active-state logic (which keys on .bm-btn[data-style]) never touches it. */
.view-switch {
  display: flex;
  background: var(--it-white);
  border: 1px solid var(--it-line);
  border-radius: 8px;
  overflow: hidden;
}
.vw-btn {
  border: none; background: transparent; cursor: pointer;
  padding: 5px 12px; font-size: 12px; color: var(--it-slate);
  font-family: inherit; transition: background 0.15s, color 0.15s;
}
.vw-btn + .vw-btn { border-left: 1px solid var(--it-line); }
.vw-btn:hover { color: var(--it-ink); }
.vw-btn.active { background: var(--it-blue); color: var(--it-white); }

.dash-body { flex: 1; display: flex; min-height: 0; position: relative; }

/* ---------- Sidebar / tag list ---------- */
.sidebar {
  width: 320px;
  flex: 0 0 320px;
  background: var(--it-white);
  border-right: 1px solid var(--it-line);
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.search-wrap { padding: 12px; border-bottom: 1px solid var(--it-line); }
.search-wrap input {
  width: 100%;
  background: var(--it-white);
  border: 1px solid var(--input-border);
  border-radius: 8px;
  padding: 9px 12px;
  color: var(--it-ink);
  outline: none;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.search-wrap input:focus { border-color: var(--it-blue); box-shadow: 0 0 0 3px rgba(0,88,208,0.15); }
.tag-list { flex: 1; overflow-y: auto; }
.list-status { padding: 10px 14px; color: var(--it-slate); font-size: 12px; border-top: 1px solid var(--it-line); }

.tag-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--it-line);
  border-left: 3px solid transparent;
  cursor: pointer;
}
.tag-row:hover { background: var(--it-surface); }
.tag-row.selected { background: var(--it-surface); border-left-color: var(--it-blue); }
.tag-row .dot { flex: 0 0 auto; }
.tag-row-main { flex: 1; min-width: 0; }
.tag-row-label { font-weight: 600; color: var(--it-ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tag-row-sub { color: var(--it-slate); font-size: 12px; display: flex; gap: 8px; margin-top: 2px; }
.tag-row-batt { color: var(--it-slate); font-size: 12px; white-space: nowrap; }
.role-badge {
  font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--it-blue); border: 1px solid var(--it-blue); border-radius: 4px;
  padding: 0 4px; margin-left: 6px;
}

/* ---------- Superseded history (RFID106F) ----------
   A superseded row is retired history whose physical tag lives on as another row. Hidden by
   default everywhere; when the reveal toggle shows one it must read as an archive entry, not
   a fleet member -- dimmed, struck nowhere (the text stays legible for audit), badged.
   The row treatment is LOCUS020J's, below the last-known block so it wins over its opacity.
   This outlined badge is what the detail panel still wears; a ROW wears the solid pill. */
.superseded-badge {
  font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--muted-2); border: 1px dashed var(--muted-2); border-radius: 4px;
  padding: 0 4px; margin-left: 6px;
}

/* ---------- Frozen last-known records (RFID108A) ----------
   A satellite-sourced row is a frozen last-known record: the cloud feed is off and the row
   will never move again. It renders muted -- distinct at a glance from a live row, but a step
   above superseded history (which is retired; these are not) -- with a slate-blue dot in place
   of a state colour, because a tag whose feed was switched off is not a missing tag. */
.dot.lastknown { background: var(--lastknown); }
.tag-row.lastknown, tr.lastknown { opacity: 0.78; }
.lk-badge {
  font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em;
  color: #5B7BC0; border: 1px solid var(--lastknown); border-radius: 4px;
  padding: 0 4px; margin-left: 6px; white-space: nowrap;
}

/* ---------- A superseded row is greyed out (LOCUS020J) ----------
   David, 9/29: at opacity .55 the Palisade's title still read as black, and a click gave it the
   live selection. Now: a grey band, every line mid-grey (never ink, and AA on the band), a solid
   grey pill, and a selected superseded row that goes a darker grey with a grey left edge -- it
   never takes the live selection's colour. Opacity is gone (1, even on a row that is also last
   known) because a see-through row's contrast depends on whatever is under it. The anchor, NET,
   NOT LINKED and LAST KNOWN badges keep their own colours: they are separate facts. Hover keeps
   the band. Surfaces: the Map's list (.tag-row), the Table (tr), the holding bucket
   (.unpromoted-row) -- every list Show superseded brings a row into. */
.tag-row.superseded, .unpromoted-row.superseded, .tag-table tbody tr.superseded {
  background: var(--superseded-bg); opacity: 1;
}
.tag-row.superseded.selected, .tag-table tbody tr.superseded.selected { background: var(--superseded-bg-selected); }
.tag-row.superseded.selected,
.tag-table tbody tr.superseded.selected td:first-child { border-left-color: var(--superseded-edge); }
.tag-row.superseded .tag-row-label,
.tag-row.superseded .tag-row-sub,
.tag-row.superseded .tag-row-alt,
.tag-row.superseded .tag-row-asset,
.tag-row.superseded .tag-row-batt,
.tag-row.superseded .asset-name,
.tag-row.superseded .asset-ident,
.tag-row.superseded .asset-ident-secondary,
.tag-row.superseded .asset-ident code,
.unpromoted-row.superseded .unpromoted-label,
.unpromoted-row.superseded .unpromoted-sub,
.tag-table tbody tr.superseded td,
.tag-table tbody tr.superseded .tt-muted,
.tag-table tbody tr.superseded .batt-word,
.tag-table tbody tr.superseded .asset-name,
.tag-table tbody tr.superseded .asset-ident,
.tag-table tbody tr.superseded .asset-ident-secondary,
.tag-table tbody tr.superseded .asset-ident code { color: var(--superseded-ink); }
.tag-row.superseded .superseded-badge,
.unpromoted-row.superseded .superseded-badge,
.tag-table tbody tr.superseded .superseded-badge {
  background: var(--superseded-pill); color: var(--it-white);
  border: 1px solid var(--superseded-pill); border-radius: 9px; padding: 0 6px;
}

.supersede-note {
  font-size: 12px; color: var(--muted-2); margin: 2px 0 6px;
}
.supersede-note code { font-size: 12px; }
.supersede-note a { color: var(--it-blue); text-decoration: none; }
.supersede-note a:hover { text-decoration: underline; }

/* ---------- Unpromoted bucket (RFID089D 3.3) ----------
   Tags heard and registered to this org that no human has accepted yet. Deliberately quiet:
   a muted panel pinned below the tag list, NOT an alert. These are not a problem to fix, they
   are a decision waiting to be made, and styling them as a warning would train the operator to
   dismiss the one surface that says a tag exists. The whole block is hidden when empty. */
.unpromoted-wrap { border-top: 1px solid var(--it-line); background: var(--it-mist, #f6f8fa); }
.unpromoted-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 14px 6px;
  font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--it-slate);
}
.unpromoted-count {
  display: inline-block; min-width: 18px; padding: 1px 7px; border-radius: 20px;
  background: var(--it-slate); color: var(--it-white);
  font-size: 12px; font-weight: 700; letter-spacing: 0; text-align: center;
}
.unpromoted-list { max-height: 220px; overflow-y: auto; }
.unpromoted-row {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 14px; border-top: 1px solid var(--it-line);
}
.unpromoted-main { flex: 1; min-width: 0; }
.unpromoted-label {
  font-size: 14px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.unpromoted-sub { color: var(--it-slate); font-size: 12px; margin-top: 2px; }
.unpromoted-promote {
  flex: none;
  background: var(--it-blue); color: var(--it-white); border: none; border-radius: 6px;
  padding: 5px 10px; font-size: 12px; font-weight: 600;
  transition: background 0.15s;
}
.unpromoted-promote:hover { background: var(--it-deep); }
.unpromoted-note { padding: 8px 14px 12px; color: var(--it-slate); font-size: 12px; }

/* Demote is a secondary action: it changes visibility and destroys nothing, so it must not
   read as a delete button. Outlined, not filled, and not red. */
.btn-demote {
  width: 100%; margin-top: 10px;
  background: transparent; color: var(--it-slate);
  border: 1px solid var(--it-line); border-radius: 8px;
  padding: 9px; font-size: 14px; font-weight: 600;
  transition: border-color 0.15s, color 0.15s;
}
.btn-demote:hover { border-color: var(--it-slate); color: var(--it-ink, #1a2230); }

.holding-badge {
  font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--it-slate); border: 1px solid var(--it-slate); border-radius: 4px;
  padding: 0 4px;
}

/* status dots (asset-state colours) */
.dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; }
.dot.present { background: var(--present); }
.dot.unseen  { background: var(--unseen); }
.dot.missing { background: var(--missing); }
.dot.retired { background: var(--retired); }

/* ---------- Map ---------- */
.map-wrap { flex: 1; position: relative; min-width: 0; }
#map { position: absolute; inset: 0; background: var(--it-surface); }

.maplibregl-ctrl-attrib { font-size: 12px; }

/* Count chips (control bar). The four state chips double as filters (RFID069B). */
.count-chips {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.chip {
  background: var(--it-white);
  border: 1px solid var(--it-line);
  border-radius: 20px;
  padding: 5px 12px;
  font-size: 12px;
  color: var(--it-slate);
}
.chip-filter { cursor: pointer; transition: opacity 0.15s, border-color 0.15s; }
.chip-filter:hover { border-color: var(--it-blue); }
.chip-filter.filter-off { opacity: 0.45; background: var(--it-surface); }
.chip b { color: var(--it-ink); font-size: 14px; }
.chip-present b { color: var(--present); }
.chip-unseen b  { color: var(--unseen); }
.chip-missing b { color: var(--missing); }
.chip-retired b { color: var(--retired); }
.chip-btn {
  cursor: pointer;
  color: var(--it-slate);
  transition: border-color 0.15s, color 0.15s;
}
.chip-btn:hover { border-color: var(--it-blue); color: var(--it-ink); }

/* Control-bar switch family (born as the RFID106B source filter, which RFID108A removed;
   the RFID106F superseded reveal still wears it). Same visual family as the basemap switch
   so the control bar reads as one row. */
.source-switch {
  display: flex;
  align-items: center;
  background: var(--it-white);
  border: 1px solid var(--it-line);
  border-radius: 8px;
  overflow: hidden;
}
.src-btn {
  border: none; background: transparent; cursor: pointer;
  padding: 5px 12px; font-size: 12px; color: var(--it-slate);
  font-family: inherit; white-space: nowrap;
  transition: background 0.15s, color 0.15s;
}
.src-btn + .src-btn { border-left: 1px solid var(--it-line); }
.src-btn:hover { color: var(--it-ink); }
.src-btn.active { background: var(--it-blue); color: var(--it-white); }

/* Basemap switcher (control bar) */
.basemap-switch {
  display: flex;
  background: var(--it-white);
  border: 1px solid var(--it-line);
  border-radius: 8px;
  overflow: hidden;
}
.bm-btn {
  border: none; background: transparent; cursor: pointer;
  padding: 5px 12px; font-size: 12px; color: var(--it-slate);
  font-family: inherit; transition: background 0.15s, color 0.15s;
}
.bm-btn + .bm-btn { border-left: 1px solid var(--it-line); }
.bm-btn:hover { color: var(--it-ink); }
.bm-btn.active { background: var(--it-blue); color: var(--it-white); }

/* Time-range control (RFID064A) -- same visual family as the basemap switch, in the control bar. */
.range-switch {
  display: flex;
}
.range-seg {
  display: flex;
  background: var(--it-white);
  border: 1px solid var(--it-line);
  border-radius: 8px;
  overflow: hidden;
}
.rg-btn {
  border: none; background: transparent; cursor: pointer;
  padding: 5px 12px; font-size: 12px; color: var(--it-slate);
  font-family: inherit; transition: background 0.15s, color 0.15s;
}
.rg-btn + .rg-btn { border-left: 1px solid var(--it-line); }
.rg-btn:hover { color: var(--it-ink); }
.rg-btn.active { background: var(--it-blue); color: var(--it-white); }
/* Custom-range editor: an inline second row of the control bar, not a floating panel. */
.range-custom {
  display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap;
  padding: 0 18px 10px;
}
.range-field { display: flex; flex-direction: column; gap: 3px; }
.range-field span {
  color: var(--it-slate); font-size: 12px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.06em;
}
.range-field input {
  background: var(--it-white); border: 1px solid var(--it-line);
  border-radius: 6px; padding: 5px 8px; color: var(--it-ink); outline: none;
  font-family: inherit;
}
.range-field input:focus { border-color: var(--it-blue); box-shadow: 0 0 0 3px rgba(0,88,208,0.15); }
.rg-apply {
  background: var(--it-blue); color: var(--it-white); border: none;
  border-radius: 6px; padding: 7px 10px; font-weight: 600; transition: background 0.15s;
}
.rg-apply:hover { background: var(--it-deep); }
.rg-cancel {
  background: transparent; color: var(--it-slate);
  border: 1px solid var(--it-line);
  border-radius: 6px; padding: 7px 10px; font-weight: 600;
  transition: color 0.15s, border-color 0.15s;
}
.rg-cancel:hover { color: var(--it-ink); border-color: var(--it-slate); }
.range-error {
  color: var(--danger); font-size: 12px;
  background: #FEF2F2; border: 1px solid rgba(185,28,28,0.35);
  border-radius: 6px; padding: 6px 8px;
  align-self: center;
}

/* Live map is "now", never windowed by the time-range control -- static clarifier. */
.live-note {
  position: absolute; top: 12px; right: 12px; z-index: 5;
  color: var(--it-slate); font-size: 12px;
  background: rgba(255,255,255,0.82); border-radius: 6px; padding: 2px 8px;
}
@supports (backdrop-filter: blur(4px)) or (-webkit-backdrop-filter: blur(4px)) {
  .live-note { -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px); }
}

/* Legend */
.legend {
  position: absolute; bottom: 24px; left: 12px; z-index: 5;
  background: rgba(255,255,255,0.92);
  border: 1px solid var(--it-line);
  border-radius: 10px;
  padding: 8px 12px;
  display: flex; flex-direction: column; gap: 5px;
  font-size: 12px; color: var(--it-slate);
  box-shadow: 0 2px 8px rgba(10,79,168,0.08);
}
@supports (backdrop-filter: blur(4px)) or (-webkit-backdrop-filter: blur(4px)) {
  .legend { -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px); }
}
.legend span { display: flex; align-items: center; gap: 7px; }
.legend .anchor-key {
  width: 11px; height: 11px; background: var(--it-blue);
  transform: rotate(45deg); border: 1px solid var(--it-white); display: inline-block;
}
/* Matches .reporter-pin: small slate square, unrotated, so the legend swatch is the marker. */
.legend .reporter-key {
  width: 11px; height: 11px; border-radius: 2px;
  background: rgba(91,107,122,0.75); border: 1px solid var(--it-white);
  box-shadow: 0 1px 4px rgba(10,10,10,0.35);
  display: inline-block;
}
.legend .acc-key {
  width: 12px; height: 12px; border-radius: 50%;
  background: rgba(22,199,230,0.18); border: 1px solid var(--present);
  display: inline-block;
}
/* RFID106A: a GL circle layer cannot draw a dashed stroke, so "accuracy not reported" is now
   a solid DARK ring where a reported accuracy gets a white one. The swatch follows the map. */
.legend .dot.no-acc-key {
  background: transparent; border: 2px solid rgba(15,23,42,0.75);
}
.legend .cluster-key {
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--it-blue); border: 2px solid rgba(255,255,255,0.95);
  color: var(--it-white); font-size: 12px; font-weight: 700; font-style: normal;
  display: inline-flex; align-items: center; justify-content: center;
  box-shadow: 0 1px 3px rgba(10,10,10,0.3);
}
.legend .gw-key {
  width: 14px; height: 14px; border-radius: 50%;
  background: transparent; border: 1.5px solid #7aa2f7; display: inline-block;
}

/* Map markers (HTML).
   ============================================================================
   RFID106A B6 -- THE DISPLACEMENT MECHANISM, AND WHY IT IS GONE.

   This block used to open with `.pin { ... position: relative; }`, the CSS for the per-tag
   DOM marker. This stylesheet is linked AFTER maplibre-gl.css (index.html), and `.pin` and
   `.maplibregl-marker` have identical specificity, so `position: relative` beat MapLibre's
   own `position: absolute`. Every marker therefore stayed in DOCUMENT FLOW inside
   .maplibregl-canvas-container and the browser stacked them 16 px apart down the page in DOM
   insertion order, with MapLibre's `translate(-50%,-50%) translate(Xpx,Ypx)` applied on top
   of that flow offset. The 16 px step is screen-constant, so at continental zoom it was
   drawn at continental scale: the vertical column of markers at a fixed longitude, ordered
   by tag state because that is the order /live returns them in. It was never geography, and
   it was never a deliberate anti-overlap jitter -- it was this one declaration.

   Asset tags are now a clustered GeoJSON source (app.js, TAG_SRC), so the whole `.pin` family
   is deleted rather than repaired. The two DOM markers that remain -- anchors and reporters
   -- carried the same `position: relative` and are corrected to `absolute` below.
   ============================================================================ */
.pin-label {
  position: absolute; left: 50%; bottom: 130%;
  transform: translateX(-50%);
  background: var(--it-white);
  border: 1px solid var(--it-line);
  color: var(--it-ink);
  padding: 3px 7px; border-radius: 6px;
  white-space: nowrap; font-size: 12px;
  box-shadow: 0 2px 8px rgba(10,10,10,0.25);
  pointer-events: none; opacity: 0; transition: opacity 0.12s;
}

/* The anchor marker is a WRAPPER whose child does the rotating. MapLibre writes its
   positioning transform inline on the marker element, and an inline transform beats any
   stylesheet one -- so the old `.anchor-pin { transform: rotate(45deg) }` never applied and
   the map drew a square where the legend drew a diamond. Rotating the child fixes that
   without fighting MapLibre for the same property. */
.anchor-mk { position: absolute; width: 15px; height: 15px; cursor: pointer; }
.anchor-pin {
  width: 15px; height: 15px; background: var(--it-blue);
  border: 2px solid var(--it-white); transform: rotate(45deg);
  box-shadow: 0 2px 6px rgba(10,10,10,0.4); cursor: pointer;
  box-sizing: border-box;
}
.anchor-mk .pin-label { bottom: 150%; }
.anchor-mk:hover .pin-label { opacity: 1; }

/* LOCUS013D, board L-38: "Maybe make the position go to red on the map" (David, 2026-08-29).
   Same diamond, same shape -- disputed is a STATE, not a second marker type -- so this only ever
   overrides the background colour. --missing is the dashboard's own established red (#DC2626 since LOCUS024ZJ);
   this ticket does not invent a new one. */
.anchor-pin.disputed { background: var(--danger); }

/* Reporter markers (RFID063A): the phones/gateways that reported the selected tag. A small
   hollow slate square -- deliberately secondary to the round tag dots and the blue anchor
   diamonds. Context, not the subject. */
.reporter-pin {
  width: 11px; height: 11px; border-radius: 2px;
  background: rgba(91,107,122,0.75); border: 1.5px solid var(--it-white);
  box-shadow: 0 1px 4px rgba(10,10,10,0.35); cursor: pointer; position: absolute;
}
.reporter-pin-label { bottom: 150%; }
.reporter-pin:hover .reporter-pin-label { opacity: 1; }

/* The second position (LOCUS013D): where a disputed anchor is actually being heard, drawn only
   for the selected tag, connected to the anchor's own pin by the same dashed-connector language
   the reporter overlay already uses -- a relationship, not a measured path. /code-review: this
   is `.reporter-pin` (same box, same size, same shadow) worn as a SECOND class on the same
   element (app.js: `class="reporter-pin dispute-heard-pin"`), not a duplicate of it -- only what
   actually differs (round instead of square, red instead of slate, no click affordance) is
   overridden here. */
.dispute-heard-pin {
  border-radius: 50%; background: var(--danger); cursor: default;
}
.dispute-heard-pin-label { bottom: 150%; }
.dispute-heard-pin:hover .dispute-heard-pin-label { opacity: 1; }
/* ---- cluster popups (RFID106A B3/B4) ---------------------------------------------
   Two popups, deliberately different weights. The HOVER preview (.cl-hov) is a read-only
   peek at the first few members. The CLICK popup (.cl-pop) is the CO-LOCATED TERMINUS: the
   end of the drill-down for tags that share a coordinate exactly and can never separate by
   zooming, so it is a scrollable list of buttons, each of which opens the detail panel. */
.cl-pop { font-size: 12px; color: var(--it-ink); line-height: 1.4; min-width: 220px; }
.cl-pop-head { font-weight: 700; font-size: 14px; margin-bottom: 2px; }
.cl-pop-sub { color: var(--it-slate); margin-bottom: 6px; }
.cl-pop-note {
  color: var(--it-slate); font-size: 12px; line-height: 1.35;
  background: rgba(122,162,247,0.10); border-left: 2px solid #7aa2f7;
  padding: 4px 6px; border-radius: 0 4px 4px 0; margin-bottom: 6px;
}
.cl-pop-list { max-height: 260px; overflow-y: auto; overflow-x: hidden; }
.cl-row {
  display: flex; align-items: center; gap: 8px; width: 100%;
  background: none; border: 0; border-top: 1px solid var(--it-line);
  padding: 6px 2px; text-align: left; cursor: pointer; font: inherit; color: inherit;
}
.cl-row:first-child { border-top: 0; }
.cl-row .dot { flex: 0 0 auto; }
.cl-hov-row .dot { flex: 0 0 auto; }
.cl-row:hover { background: rgba(0,88,208,0.07); }
.cl-row-main { display: flex; flex-direction: column; min-width: 0; }
.cl-row-name { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cl-row-sub { color: var(--it-slate); font-size: 12px; }

.cl-hov { font-size: 12px; color: var(--it-ink); line-height: 1.45; }
.cl-hov-head { font-weight: 700; margin-bottom: 3px; }
.cl-hov-row { display: flex; align-items: center; gap: 6px; color: var(--it-slate); }
.cl-hov-more { color: var(--it-slate); font-style: italic; margin-top: 2px; }
.cl-hov-note { color: var(--it-slate); font-size: 12px; margin-top: 4px; }

/* ---- sidebar location groups (RFID106A B5) ---------------------------------------
   One header per distinct position, mirroring the map's clusters. Clicking collapses. */
.tg-head {
  display: flex; align-items: center; gap: 7px;
  padding: 5px 10px; cursor: pointer; user-select: none;
  background: var(--it-surface);
  border-top: 1px solid var(--it-line);
  font-size: 12px; color: var(--it-slate);
  position: sticky; top: 0; z-index: 1;
}
.tg-head:hover { background: rgba(0,88,208,0.07); }
.tg-caret { width: 10px; color: var(--it-slate); }
.tg-title { font-variant-numeric: tabular-nums; letter-spacing: .01em; }
.tg-count {
  margin-left: auto; font-weight: 700; color: var(--it-ink);
  background: var(--it-white); border: 1px solid var(--it-line);
  border-radius: 9px; padding: 0 6px; min-width: 20px; text-align: center;
}
.tag-row-alt { color: var(--it-slate); font-style: italic; }

/* ---------- Attached asset on a tag row (LOCUS006A) ----------
   What the tag is currently ON: a third line under the sub-line, at the same 12px the sub-line
   uses. NO NEW TOKENS -- --it-ink is the same variable .tag-row-label already reads.

   ITS OWN LINE, not a fifth item on the .tag-row-sub flex row. That row already carries the state
   word, the age, the legacy label and the supersede provenance, and the first version of this did
   append to it -- which squeezed a vehicle name down to "2003 Hon..." on a 260px sidebar. A name
   nobody can read is worse than one line more of height.

   INK, and NOT ITALIC. Slate italic on this row already means "the other name this tag goes by"
   (.tag-row-alt); the vehicle is a different KIND of fact and must not read as a third alias for
   the tag. Ink and upright is the contrast, and it is the whole treatment: no badge, no colour,
   no icon. An attached tag is not an alert.

   There is no rule for the absent case ON PURPOSE. An unattached tag emits no element at all, so
   there is nothing here to style; a ".tag-row-asset:empty { content: '—' }" placeholder would be
   exactly the dash-shaped gap this ticket forbids. */
.tag-row-asset {
  color: var(--it-ink); font-size: 12px; margin-top: 2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ---------- A name and a code are different facts (LOCUS007F) ----------
   Rendered by asset-label.js for the tag row, the table's Asset cell and the detail panel.

   NAMED: the name leads in ink (.asset-name) and the scanned code follows, secondary --
   slate, monospace, smaller, still visible. Nobody should have to read a VIN to know which car
   they are looking at, and nobody should have to hover to find the VIN either.

   UNNAMED: the code, MARKED AS A CODE. A small boxed "code" mark in front of it, the code itself
   in monospace slate rather than ink, and no .asset-name anywhere. The mark is text, not just a
   colour, so a monochrome print and a screen reader both get the distinction. No new tokens:
   --it-slate, --it-line and --it-surface are the ones the sidebar already reads. */
.asset-name { color: var(--it-ink); }
.asset-ident-secondary, .asset-ident code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px; color: var(--it-slate);
}
.asset-ident-secondary { margin-left: 6px; }
.asset-ident { color: var(--it-slate); }
.asset-ident-mark {
  display: inline-block; font-size: 12px; line-height: 14px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--it-slate); background: var(--it-surface);
  border: 1px solid var(--it-line); border-radius: 3px;
  padding: 0 4px; margin-right: 6px; vertical-align: 1px;
}
/* In the .kv grid the code and the secondary ident read at the grid's own size. */
.kv .asset-ident code, .kv .asset-ident-secondary { font-size: 12px; }
.kv .asset-desc { white-space: pre-wrap; }

.reporter-pop { font-size: 12px; color: var(--it-ink); line-height: 1.4; }
.reporter-pop-label { font-weight: 600; margin-bottom: 2px; }
.reporter-pop-row { color: var(--it-slate); }

/* moving playback marker (transport accent) */
.trail-cursor {
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--it-white); border: 3px solid var(--it-blue);
  box-shadow: 0 0 12px rgba(0,88,208,0.6), 0 0 0 2px rgba(10,10,10,0.3);
}

/* open (current) stay: pulsing cyan ring — "still here", not departed */
.trail-open-ring {
  width: 26px; height: 26px; border-radius: 50%;
  border: 2px solid var(--it-cyan);
  box-shadow: 0 0 0 2px rgba(22,199,230,0.25);
  pointer-events: none;
  animation: pulse-ring 1.6s ease-out infinite;
}
@keyframes pulse-ring {
  0%   { transform: scale(0.7); opacity: 0.9; }
  70%  { transform: scale(1.3); opacity: 0.12; }
  100% { transform: scale(0.7); opacity: 0.9; }
}

/* ---------- Table view (RFID069B) ----------
   Same product language as the sidebar: same border colour, row rhythm and type scale.
   Scrolls inside its container; the header row stays stuck to the top. */
.table-view {
  position: absolute; inset: 0;
  overflow: auto;
  background: var(--it-white);
}
.tag-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.tag-table th {
  position: sticky; top: 0; z-index: 2;
  background: var(--it-white);
  text-align: left; padding: 9px 14px;
  font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--it-slate);
  border-bottom: 1px solid var(--it-line);
  cursor: pointer; user-select: none; white-space: nowrap;
}
.tag-table th:hover { color: var(--it-ink); }
.tag-table th.sort-asc::after { content: " \2191"; }
.tag-table th.sort-desc::after { content: " \2193"; }
.tag-table td {
  padding: 9px 14px;
  border-bottom: 1px solid var(--it-line);
  white-space: nowrap; color: var(--it-ink);
}
/* Selected row gets the same left-border treatment as .tag-row.selected. */
.tag-table th:first-child, .tag-table td:first-child { border-left: 3px solid transparent; }
.tag-table tbody tr { cursor: pointer; }
.tag-table tbody tr:hover { background: var(--it-surface); }
.tag-table tbody tr.selected { background: var(--it-surface); }
.tag-table tbody tr.selected td:first-child { border-left-color: var(--it-blue); }
.tt-state { display: inline-flex; align-items: center; gap: 7px; }
.tt-muted { color: var(--it-slate); }
/* LOCUS024L: the Battery status word. Good keeps the row's ink; Low and Replace take the LOCUS020L
   notice and alarm inks (Android notice_ink / alarm_ink): 6.9:1 and 5.6:1 on white, 6.5:1 and 5.3:1 on
   the selected row. A superseded row greys the word with the rest of the row (rule above). */
.batt-low { color: #7A5300; }
.batt-replace { color: #C62828; }
.tt-empty { padding: 24px 14px; color: var(--it-slate); white-space: normal; cursor: default; }

/* ---------- Tag detail panel ---------- */
.detail-panel {
  width: 340px; flex: 0 0 340px;
  background: var(--it-white);
  border-left: 1px solid var(--it-line);
  overflow-y: auto; position: relative;
}
.panel-close {
  position: absolute; top: 10px; right: 12px;
  background: transparent; border: none; color: var(--it-slate);
  font-size: 22px; line-height: 1; padding: 4px;
}
.panel-close:hover { color: var(--it-ink); }
#detail-content { padding: 18px; }
.detail-title { font-size: 17px; font-weight: 700; color: var(--it-ink); margin: 0 26px 2px 0; }
.detail-id { color: var(--it-slate); font-size: 12px; margin-bottom: 14px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
/* LOCUS016C Phase 3c (LOCUS016D 4): a Locus Network row prints its uuid once, on the UUID row, so
   its panel has no .detail-id sub-title; the title carries the gap the sub-title used to. */
.detail-title.detail-title-noid { margin-bottom: 14px; }
.detail-state {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 4px 10px; border-radius: 20px; font-size: 12px; font-weight: 600;
  background: var(--it-surface); border: 1px solid var(--it-line); color: var(--it-ink); margin-bottom: 16px;
}
.kv { display: grid; grid-template-columns: 110px 1fr; gap: 6px 10px; font-size: 14px; }
.kv dt { color: var(--it-slate); }
.kv dd { margin: 0; color: var(--it-ink); word-break: break-word; }
.detail-section-title {
  margin: 18px 0 8px; font-size: 12px; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--it-slate); font-weight: 600;
}
.btn-trail {
  width: 100%; margin-top: 18px;
  background: var(--it-blue); color: var(--it-white); border: none; border-radius: 8px;
  padding: 11px; font-weight: 600;
  transition: background 0.15s;
}
.btn-trail:hover { background: var(--it-deep); }
.btn-trail:disabled { opacity: 0.5; cursor: default; }
.detail-note { color: var(--it-slate); font-size: 12px; margin-top: 10px; }

/* LOCUS013D, board L-38: the detail panel's own copy of the red the diamond turns. Sits above
   the state row so it reads before "present"/"missing" -- the dispute is the more urgent fact. */
.dispute-note {
  color: var(--danger); font-weight: 600; font-size: 14px;
  margin: 4px 0 8px;
}

/* ---------- Reported by (reporter attribution) ---------- */
.reporter-count {
  display: inline-block; margin-left: 6px; padding: 1px 7px; border-radius: 20px;
  background: var(--it-blue); color: var(--it-white);
  font-size: 12px; font-weight: 700; letter-spacing: 0.02em;
  text-transform: none; vertical-align: middle;
}
.reporters { display: flex; flex-direction: column; gap: 8px; }
.reporter-row {
  display: flex; align-items: center; gap: 9px;
  padding: 8px 10px; border-radius: 8px;
  background: var(--it-surface); border: 1px solid var(--it-line);
}
.reporter-dot {
  flex: 0 0 auto; width: 8px; height: 8px; border-radius: 50%;
  background: var(--it-blue);
}
.reporter-main { min-width: 0; flex: 1 1 auto; }
.reporter-label {
  font-size: 14px; font-weight: 600; color: var(--it-ink);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.reporter-sub { font-size: 12px; color: var(--it-slate); margin-top: 2px; }
.reporter-empty { font-size: 12px; color: var(--it-slate); }

/* ---------- Zone membership (RFID105A) ----------
   A stale derived zone is STILL READ AT FULL STRENGTH: --it-ink, same size as a fresh one. The
   badge carries the staleness, not the legibility of the value. Greying the name down would be
   the same failure as hiding it -- the ruling is that the value is always shown and only the
   presentation says how old it is.
   --zone-stale is its own token and deliberately NOT --unseen: that is an asset-STATE colour
   meaning "not heard lately", and a zone's evidence being old is a different claim from a tag's
   state. Two meanings sharing one swatch is how they end up read as one meaning. */
:root { --zone-stale: #B45309; }

.zone-line { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
.zone-name { font-size: 14px; font-weight: 600; color: var(--it-ink); }
.zone-name.unknown { font-weight: 600; color: var(--it-slate); font-style: italic; }
.zone-site { font-size: 12px; font-weight: 500; color: var(--it-slate); }
.zone-sub { font-size: 12px; color: var(--it-slate); margin-top: 3px; }
.zone-note { font-size: 12px; color: var(--it-slate); margin-top: 5px; line-height: 1.45; }
.zone-badge {
  font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em; font-weight: 700;
  color: var(--it-slate); border: 1px solid var(--it-line); border-radius: 4px;
  padding: 1px 5px; white-space: nowrap;
}
.zone-badge.stale { color: var(--zone-stale); border-color: var(--zone-stale); }
.zone-badge.set { color: var(--it-blue); border-color: var(--it-blue); }

/* ---------- Trail bar (transport) ---------- */
.trail-bar {
  flex: 0 0 auto;
  height: 60px;
  display: flex; align-items: center; gap: 14px;
  padding: 0 16px;
  background: var(--it-white);
  border-top: 1px solid var(--it-line);
}
.tb-btn {
  width: 40px; height: 40px; border-radius: 50%;
  background: var(--it-blue); color: var(--it-white); border: none; font-size: 15px;
  flex: 0 0 auto; display: flex; align-items: center; justify-content: center;
  transition: background 0.15s;
}
.tb-btn:hover { background: var(--it-deep); }
.tb-scrub-wrap { flex: 1; display: flex; align-items: center; }
.tb-scrub { width: 100%; accent-color: var(--it-blue); }
.tb-speed { display: flex; gap: 4px; }
.tb-speed-btn {
  background: var(--it-surface); color: var(--it-slate); border: 1px solid var(--it-line);
  border-radius: 20px; padding: 5px 12px; font-size: 12px; font-weight: 600;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.tb-speed-btn:hover { border-color: var(--it-cyan); color: var(--it-ink); }
.tb-speed-btn.active { background: var(--it-cyan); color: var(--it-ink); border-color: var(--it-cyan); }
.tb-toggle {
  flex: 0 0 auto;
  background: var(--it-surface); color: var(--it-slate); border: 1px solid var(--it-line);
  border-radius: 20px; padding: 5px 12px; font-size: 12px; font-weight: 600;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.tb-toggle:hover { border-color: var(--it-cyan); color: var(--it-ink); }
.tb-toggle.active { background: var(--it-cyan); color: var(--it-ink); border-color: var(--it-cyan); }
.tb-readout {
  min-width: 230px; color: var(--it-slate);
  font-size: 12px; font-variant-numeric: tabular-nums;
  display: flex; flex-direction: column; justify-content: center; gap: 1px;
  text-align: right; line-height: 1.3;
}
.tb-readout b { color: var(--it-ink); }
.tb-readout .ro1 { color: var(--it-ink); font-weight: 600; }
.tb-readout .ro2 { color: var(--it-slate); }
.tb-readout .ro-open { color: var(--it-cyan); font-weight: 700; }
/* A stop we never saw the tag leave — it just went quiet (end_basis = assumed_last_seen).
   Amber, matching the 'unseen' tag state: inferred, not observed. */
.tb-readout .ro-assumed { color: var(--unseen); font-weight: 700; }
.tb-exit {
  background: transparent; color: var(--it-slate); border: 1px solid var(--it-line);
  border-radius: 8px; padding: 7px 12px;
}
.tb-exit:hover { color: var(--it-ink); border-color: var(--it-slate); }

/* ---------- Loading + toast ---------- */
.map-loading {
  position: absolute; inset: 0; z-index: 20;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px;
  background: rgba(245,248,252,0.7); color: var(--it-ink);
}
@supports (backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px)) {
  .map-loading { -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px); }
}
.spinner {
  width: 34px; height: 34px; border-radius: 50%;
  border: 3px solid var(--it-line); border-top-color: var(--it-blue);
  animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.toast-wrap {
  position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%);
  z-index: 1000; display: flex; flex-direction: column; gap: 8px; align-items: center;
}
.toast {
  background: var(--it-white); border: 1px solid var(--it-line);
  color: var(--it-ink); padding: 10px 16px; border-radius: 10px;
  box-shadow: 0 8px 24px rgba(10,79,168,0.18); font-size: 14px;
  max-width: 480px; animation: toast-in 0.2s ease;
}
.toast.error { border-color: var(--danger); background: #FEF2F2; color: var(--danger); }
@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

/* ---------- Get-the-app button + modal ---------- */
.btn-app {
  background: var(--it-surface);
  color: var(--it-blue);
  border: 1px solid var(--it-line);
  border-radius: 8px;
  padding: 7px 12px;
  font-weight: 600;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.btn-app:hover { border-color: var(--it-blue); color: var(--it-deep); }

.app-modal { position: fixed; inset: 0; z-index: 1100; display: flex; align-items: center; justify-content: center; }
/* LOCUS013E: the backdrop and card are siblings and neither used to set a z-index, so the card
   sat on top only by DOM paint order -- a guarantee compositing does not honour once a sibling is
   promoted to its own layer (backdrop-filter does exactly that). Explicit z-index on both ends the
   dependency on paint order entirely, regardless of why the backdrop got promoted. */
.app-modal-backdrop { position: absolute; inset: 0; z-index: 0; background: rgba(10,10,10,0.45); }
@supports (backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px)) {
  .app-modal-backdrop { -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px); }
}
.app-modal-card {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 340px;
  background: var(--it-white);
  border: 1px solid var(--it-line);
  border-radius: 12px;
  padding: 26px 24px 22px;
  box-shadow: 0 20px 60px rgba(10,79,168,0.28);
  text-align: center;
  overflow: hidden;
}
.app-modal-card::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px; background: var(--it-curve);
}
.app-modal-card .panel-close { top: 8px; right: 10px; }
.app-modal-title { font-size: 16px; font-weight: 700; color: var(--it-ink); margin: 2px 20px 16px; line-height: 1.35; }
.app-qr-wrap {
  display: inline-block; padding: 12px; background: var(--it-white);
  border: 1px solid var(--it-line); border-radius: 10px;
}
.app-qr-wrap svg { display: block; width: 200px; height: 200px; }
.app-qr-fail { width: 200px; font-size: 12px; color: var(--it-slate); }
.app-qr-cap { color: var(--it-slate); font-size: 12px; margin: 10px 0 16px; }
.app-download { display: block; width: 100%; text-decoration: none; text-align: center; }
.app-download.disabled { opacity: 0.5; pointer-events: none; }
.app-build-info { color: var(--it-slate); font-size: 12px; margin: 12px 0 4px; font-variant-numeric: tabular-nums; }

/* LOCUS013F section 3, board L-38: the static fallback address, styled by CONTEXT alone -- the
   div carrying the address has no id or class of its own (a test pins this: nothing may write to
   it, which is trivially true of an element nothing can select), so it is reached here only as
   the last child of .app-fallback. */
.app-fallback { text-align: left; margin: 14px 0 0; padding-top: 12px; border-top: 1px solid var(--it-line); }
.app-fallback-label { color: var(--it-slate); font-size: 12px; margin-bottom: 4px; }
.app-fallback div:last-child {
  font: 13px/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  color: var(--it-ink); user-select: all; word-break: break-all;
}
.app-guide {
  text-align: left; margin: 14px 0 0; padding-left: 18px;
  color: var(--it-slate); font-size: 12px; line-height: 1.5;
}
.app-guide li { margin-bottom: 6px; }
.app-guide li:last-child { margin-bottom: 0; }
.app-guide b { color: var(--it-ink); }

/* ---------- Responsive (usable at 1280px, degrades below) ---------- */
@media (max-width: 1100px) {
  .sidebar { flex-basis: 260px; width: 260px; }
  .detail-panel { flex-basis: 300px; width: 300px; }
  .tb-readout { min-width: 150px; }
  /* Control bar wraps to two rows rather than clipping; centre zone gives up centring. */
  .cb-row { gap: 8px; padding: 8px 12px; }
  .cb-mid { justify-content: flex-start; }
}

/* ---------- Site -> zone -> tag hierarchy (RFID100E) ---------- */
/* Takes the sidebar's place in the Sites view, at the mockup's ~46% split, so the map beside
   it stays large enough to be worth linking to. Fixed min-width because a zone board with
   200px columns stops being readable below it. */
.hier-panel {
  flex: 0 0 46%;
  min-width: 420px;
  max-width: 720px;
  background: var(--it-white);
  border-right: 1px solid var(--it-line);
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.hier-head {
  display: flex; align-items: center; gap: 10px;
  padding: 11px 16px; border-bottom: 1px solid var(--it-line);
}
.hier-crumb { font-size: 14px; color: var(--it-ink); flex: 1; min-width: 0; }
.hier-crumb a { color: var(--it-blue); font-weight: 600; cursor: pointer; text-decoration: none; }
.hier-crumb a:hover { text-decoration: underline; }
.hier-crumb b { font-weight: 700; }
.hier-crumb .crumb-sep { color: var(--it-slate); margin: 0 6px; }

/* The excluded-by-design buckets, always on screen as numbers. */
.fleet-strip {
  display: flex; flex-wrap: wrap; gap: 6px 18px; align-items: baseline;
  padding: 9px 16px; border-bottom: 1px solid var(--it-line);
  background: var(--it-surface); font-size: 12px;
}
.fleet-item { display: inline-flex; align-items: baseline; gap: 6px; white-space: nowrap; }
.fleet-k {
  color: var(--it-slate); text-transform: uppercase;
  letter-spacing: 0.05em; font-size: 12px; font-weight: 700;
}
.fleet-v { color: var(--it-ink); font-weight: 700; font-variant-numeric: tabular-nums; }
.fleet-total .fleet-v { color: var(--it-blue); }
.fleet-note { color: var(--it-slate); font-size: 12px; width: 100%; }

.hier-body { flex: 1; min-height: 0; display: flex; flex-direction: column; }

/* ---- Level 1: site cards ---- */
.site-cards {
  flex: 1; min-height: 0; overflow-y: auto; padding: 16px;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 14px; align-content: start;
}
/* The left edge carries the worst state contained anywhere in the site, so a site with a
   missing tag is identifiable without reading any number on the card. */
.s-card {
  background: var(--it-white);
  border: 1px solid var(--it-line);
  border-left: 5px solid var(--present);
  border-radius: 12px; padding: 14px;
  cursor: pointer; text-align: left;
  box-shadow: 0 1px 4px rgba(10, 10, 10, 0.05);
  transition: transform 0.1s, box-shadow 0.1s;
}
.s-card:hover, .s-card.hl { transform: translateY(-2px); box-shadow: 0 4px 14px rgba(10, 10, 10, 0.14); }
.s-card.warn { border-left-color: var(--unseen); }
.s-card.bad { border-left-color: var(--missing); }
.s-card h3 { font-size: 15px; margin: 0; color: var(--it-ink); }
.s-card .s-sub { font-size: 12px; color: var(--it-slate); margin: 3px 0 8px; }
.s-card .s-big { font-size: 26px; font-weight: 800; line-height: 1.1; color: var(--it-ink); }
.s-card .s-cap { font-size: 12px; color: var(--it-slate); }
.s-pills { display: flex; gap: 5px; margin-top: 8px; flex-wrap: wrap; }
.s-pill { font-size: 12px; font-weight: 700; padding: 2px 8px; border-radius: 9px; white-space: nowrap; }
.s-pill.p-present { background: #E4F8FC; color: #0A6F82; }
.s-pill.p-unseen  { background: #FDF1DC; color: #96660A; }
.s-pill.p-missing { background: #FCE4E5; color: #B02A30; }
.s-pill.p-retired { background: #EEF1F5; color: #5B6B7A; }
.s-pill.p-none    { background: var(--it-surface); color: var(--it-slate); font-weight: 600; }

.hier-empty { padding: 24px 16px; color: var(--it-slate); font-size: 14px; line-height: 1.6; }

/* ---- Level 1: the No-site bucket, as a card beside the sites ---- */
/* Dashed, and never clickable: it is not a place, it is the tags that are not at one.
   RFID100D2's GET /v1/sites/{id}/tags is site-scoped, so there are no rows to drill into. */
.s-card.s-nosite {
  border-style: dashed; border-left-style: solid; border-left-color: var(--it-slate);
  background: var(--it-surface); cursor: default;
}
.s-card.s-nosite:hover { transform: none; box-shadow: 0 1px 4px rgba(10, 10, 10, 0.05); }

/* ---- Level 2: zone board ---- */
.zone-board {
  flex: 1; min-height: 0;
  display: flex; gap: 12px; padding: 14px 16px;
  overflow-x: auto; align-items: flex-start;
}
.zb-col {
  min-width: 200px; max-width: 230px; flex-shrink: 0;
  display: flex; flex-direction: column; max-height: 100%;
  background: var(--it-surface); border: 2px solid transparent; border-radius: 12px;
}
.zb-col.hl { border-color: var(--it-blue); }
.zb-col.zb-unzoned, .zb-col.zb-nosite { background: #EFF3F8; }
.zb-head {
  padding: 10px 12px; display: flex; align-items: center; gap: 7px;
  font-weight: 700; font-size: 14px; color: var(--it-ink);
  border-bottom: 2px solid var(--it-white); cursor: pointer; text-align: left;
  background: none; border-top: 0; border-left: 0; border-right: 0; width: 100%;
}
.zb-head:disabled { cursor: default; }
.zb-head .zb-n { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.zb-head .zb-cnt {
  background: var(--it-blue); color: var(--it-white); border-radius: 9px;
  padding: 1px 7px; font-size: 12px; font-variant-numeric: tabular-nums;
}
.zb-head.warn .zb-cnt { background: var(--unseen); }
.zb-head.bad .zb-cnt { background: var(--danger); }
.zb-sub { padding: 0 12px 8px; font-size: 12px; color: var(--it-slate); }
.zb-chips { padding: 9px; overflow-y: auto; display: flex; flex-direction: column; gap: 6px; }
.zb-chip {
  background: var(--it-white); border: 2px solid transparent; border-radius: 9px;
  padding: 7px 9px; display: flex; align-items: center; gap: 7px;
  font-size: 12px; text-align: left; width: 100%;
  box-shadow: 0 1px 3px rgba(10, 10, 10, 0.07);
}
.zb-chip:hover { box-shadow: 0 2px 8px rgba(10, 10, 10, 0.16); }
.zb-chip.selected { border-color: var(--it-blue); }
.zb-chip .zb-nm { font-weight: 600; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--it-ink); }
.zb-chip .zb-ago { font-size: 12px; color: var(--it-slate); white-space: nowrap; }
.zb-hub {
  font-size: 12px; padding: 1px 4px; border-radius: 3px; font-weight: 700;
  background: rgba(122, 162, 247, 0.16); color: #4C6FBF; letter-spacing: 0.04em;
}
.zb-empty { padding: 10px 12px 14px; font-size: 12px; color: var(--it-slate); }

/* ---- Map markers for the hierarchy (site pins, zone count bubbles) ---- */
.mk-site {
  padding: 5px 11px; border-radius: 16px; background: var(--it-blue); color: var(--it-white);
  font-weight: 700; font-size: 12px; border: 2px solid var(--it-white);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.35); cursor: pointer; white-space: nowrap;
  font-family: var(--font);
}
.mk-site.warn { background: var(--unseen); }
.mk-site.bad { background: var(--danger); }
.mk-zone {
  width: 34px; height: 34px; border-radius: 50%; background: var(--it-blue); color: var(--it-white);
  display: flex; align-items: center; justify-content: center;
  font-weight: 800; font-size: 12px; border: 3px solid var(--it-white);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.35); cursor: pointer; font-family: var(--font);
}
.mk-zone.warn { background: var(--unseen); }
.mk-zone.bad { background: var(--danger); }
@keyframes hier-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(0, 88, 208, 0.55); }
  70%  { box-shadow: 0 0 0 18px rgba(0, 88, 208, 0); }
  100% { box-shadow: 0 0 0 0 rgba(0, 88, 208, 0); }
}
.mk-site.pulsing, .mk-zone.pulsing { animation: hier-pulse 1.2s ease-out 3; }

/* ---- Site / zone editor (RFID100E phase 3) ---- */
.hier-add {
  background: var(--it-blue); color: var(--it-white); border: 0;
  border-radius: 8px; padding: 6px 12px; font-size: 12px; font-weight: 600; white-space: nowrap;
}
.hier-add:hover { background: var(--it-deep); }

.site-editor {
  border-bottom: 1px solid var(--it-line); background: var(--it-surface);
  padding: 12px 16px 14px; max-height: 60%; overflow-y: auto;
}
.ed-title { font-size: 14px; font-weight: 700; color: var(--it-ink); margin-bottom: 10px; }
.ed-form { display: flex; flex-direction: column; gap: 9px; }
.ed-row { display: flex; gap: 9px; }
.ed-row .ed-field { flex: 1; min-width: 0; }
.ed-field { display: flex; flex-direction: column; gap: 3px; }
.ed-field span {
  font-size: 12px; font-weight: 700; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--it-slate);
}
.ed-field input {
  width: 100%; background: var(--it-white); border: 1px solid var(--input-border);
  border-radius: 6px; padding: 6px 9px; color: var(--it-ink); outline: none;
  font-size: 14px; font-variant-numeric: tabular-nums;
}
.ed-field input:focus { border-color: var(--it-blue); box-shadow: 0 0 0 3px rgba(0,88,208,0.15); }
.ed-place {
  align-self: flex-start; background: none; border: 1px solid var(--it-line);
  border-radius: 6px; padding: 5px 10px; font-size: 12px; color: var(--it-slate);
}
.ed-place:hover { color: var(--it-ink); border-color: var(--it-blue); }
.ed-place.armed { background: var(--it-blue); color: var(--it-white); border-color: var(--it-blue); }
.ed-hint { font-size: 12px; color: var(--it-slate); line-height: 1.5; }
.ed-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.ed-save {
  background: var(--it-blue); color: var(--it-white); border: 0;
  border-radius: 7px; padding: 7px 16px; font-size: 14px; font-weight: 600;
}
.ed-save:hover { background: var(--it-deep); }
.ed-save:disabled { opacity: 0.6; }
.ed-cancel {
  background: none; border: 1px solid var(--it-line); border-radius: 7px;
  padding: 7px 14px; font-size: 14px; color: var(--it-slate);
}
.ed-cancel:hover { color: var(--it-ink); }
.ed-deactivate {
  margin-left: auto; background: none; border: 1px solid var(--missing);
  border-radius: 7px; padding: 7px 12px; font-size: 12px; color: var(--danger);
}
.ed-deactivate:hover { background: var(--danger); color: var(--it-white); }
.ed-deactivate.confirming { background: var(--danger); color: var(--it-white); }

/* LOCUS009J/L-51. Same two-click armed pattern as .ed-deactivate: destructive-looking but
   reversible-in-intent (the asset survives, only the link is cut), so it starts outlined and
   only fills on the SECOND click, which is also its confirmation. */
.btn-detach {
  font: inherit; font-size: 12px; padding: 3px 10px; min-height: 24px; margin-left: 6px;
  border: 1px solid var(--it-line); border-radius: 4px; background: none; color: var(--it-slate);
  cursor: pointer;
}
.btn-detach:hover { border-color: var(--danger); color: var(--danger); }
.btn-detach.confirming { background: var(--danger); border-color: var(--danger); color: var(--it-white); }
/* .detach-msg and .reanchor-msg share everything but their margin (/code-review) -- the shared
   rule carries the common declarations, and each keeps its own one-line margin override. */
.detach-msg, .reanchor-msg { font-size: 12px; color: var(--it-slate); min-height: 14px; }
.detach-msg.bad, .reanchor-msg.bad { color: var(--danger); }
.detach-msg { margin: 4px 14px 0; }
.reanchor-msg { margin: 6px 2px 0; }

/* LOCUS013F section 2, board L-38. A corrective action, not a destructive one -- outlined in the
   ordinary accent colour, never the "missing"/red treatment .btn-detach and .ed-deactivate use,
   and it only fills on the second (confirming) click, the same two-click pattern as those. */
.reanchor-block { margin-top: 10px; }
.btn-reanchor {
  font: inherit; font-size: 14px; font-weight: 600; padding: 8px 12px; width: 100%;
  border: 1px solid var(--it-blue); border-radius: 8px; background: none; color: var(--it-blue);
  cursor: pointer; transition: background 0.15s, color 0.15s;
}
.btn-reanchor:hover { background: var(--it-blue); color: var(--it-white); }
.btn-reanchor.confirming { background: var(--it-blue); color: var(--it-white); }
.ed-status { font-size: 12px; min-height: 16px; }
.ed-status.ok { color: #0A6F82; }
.ed-status.bad { color: var(--danger); }
.ed-status.busy { color: var(--it-slate); }

/* Edit affordance on a card / column. Kept as its own control rather than making the whole
   row an edit target: the row's job is to open the thing, not to reconfigure it. */
.row-edit {
  background: none; border: 1px solid var(--it-line); border-radius: 6px;
  padding: 1px 7px; font-size: 12px; font-weight: 600; color: var(--it-slate);
}
.row-edit:hover { color: var(--it-blue); border-color: var(--it-blue); }
.s-card { position: relative; }
.s-card .row-edit { position: absolute; top: 12px; right: 12px; }
.s-card h3 { padding-right: 44px; }
.zb-sub .row-edit { margin-left: 6px; }

/* ---- Reporters -> zones (RFID103C) ----
   Sits under the hierarchy body inside the Sites panel, at a fixed max height so the site
   cards / zone board keep the space they had: this is configuration, consulted occasionally,
   and must not push the thing an operator came here to look at off the screen. */
.rz-panel {
  flex: 0 0 auto; max-height: 34%; display: flex; flex-direction: column;
  border-top: 1px solid var(--it-line); background: var(--it-white);
}
.rz-head {
  padding: 9px 16px 6px; display: flex; align-items: baseline; gap: 8px;
  font-size: 12px; font-weight: 700; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--it-slate);
}
.rz-sub { text-transform: none; letter-spacing: 0; font-weight: 400; font-size: 12px; }
/* Both bounds are deliberate. The panel's 34% is a percentage of the aside and only resolves
   while the aside has a definite height; the 200px here is the fallback that keeps the list
   scrolling rather than growing without limit if it ever does not. */
.rz-rows {
  overflow-y: auto; max-height: 200px; padding: 0 16px 8px;
  display: flex; flex-direction: column; gap: 5px;
}
.rz-row { display: flex; align-items: center; gap: 8px; font-size: 12px; }
.rz-nm {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--it-ink); font-weight: 600;
}
.rz-nm .rz-live { font-weight: 400; font-size: 12px; color: var(--it-slate); margin-left: 6px; }
.rz-sel {
  flex: 0 0 190px; max-width: 190px; font-size: 12px; padding: 3px 6px;
  border: 1px solid var(--input-border); border-radius: 7px; background: var(--it-white);
  color: var(--it-ink);
}
.rz-sel:disabled { color: var(--it-slate); background: var(--it-surface); }
.rz-none { color: var(--it-slate); font-size: 12px; padding: 2px 0 6px; }
.rz-status { padding: 0 16px 9px; font-size: 12px; color: var(--it-slate); min-height: 14px; }
.rz-status.bad { color: var(--danger); }

/* ---------- Not a Locus Network device (LOCUS008F, board L-45) ----------
   `hubdev-<uuid>` holds two populations. A row a contact pairing MINTED carries an id this
   server generated, which names no device anywhere: its name can never be sent, and it can
   never be checked. Marked with a dashed slate badge -- the same weight as `superseded`,
   because both are statements about what a row IS rather than warnings about its state.
   Deliberately not red: this is a report with a repair (the detail panel's device picker),
   not a fault. */
.notlinked-badge {
  font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--it-slate); border: 1px dashed var(--it-slate); border-radius: 4px;
  padding: 0 4px; margin-left: 6px;
}

/* ---------- The unbound-pairing worklist (LOCUS008H 3) ----------
   The backlog of rows a contact pairing minted and nobody has linked to a Locus Network tag, rendered in
   the detail panel so there is ONE right-hand surface rather than a second kind of overlay to
   learn. Plain cards, the sheet's own variables, no colour that is not already in use here.

   The duplicate marker is a LEFT RULE and not a red card. Two rows on one radio identity is
   evidence a person reads, not a fault the page is reporting -- and nothing on this screen ever
   decides which of them is which. */
.wl-intro { font-size: 14px; color: var(--it-slate); line-height: 1.45; margin-bottom: 10px; }
.wl-count { font-size: 12px; color: var(--it-slate); margin-bottom: 10px; }
.wl-note { font-size: 14px; color: var(--it-slate); margin: 10px 0; }
.wl-bad { color: var(--danger); }
.wl-none { color: var(--it-slate); font-style: italic; }
.wl-rel { color: var(--it-slate); }
.wl-row {
  border: 1px solid var(--it-line); border-radius: 6px;
  padding: 10px 12px; margin-bottom: 10px;
}
.wl-row-dupe { border-left: 3px solid var(--it-blue); }
.wl-name { font-size: 14px; font-weight: 700; color: var(--it-ink); }
.wl-id {
  font-size: 12px; color: var(--it-slate); margin-bottom: 8px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.wl-dupe {
  font-size: 12px; color: var(--it-ink); background: rgba(0,88,208,0.06);
  border-radius: 4px; padding: 6px 8px; margin-bottom: 8px; line-height: 1.4;
}
.wl-kv { margin: 0 0 10px 0; }
.wl-kv code { font-size: 12px; }
.wl-pick {
  font: inherit; font-size: 12px; padding: 4px 12px; min-height: 28px;
  border: 1px solid var(--it-blue); border-radius: 4px; background: none;
  color: var(--it-blue); cursor: pointer;
}
.wl-host { display: block; }
/* ---------- Discard, and the rows that have been discarded (LOCUS008J 2) ----------
   THE DISCARD CONTROL IS NOT RED AND IS NOT THE PRIMARY BUTTON ON ITS ROW. Linking a row to the
   Locus Network tag it actually is remains the better answer wherever it is available, so it keeps
   the blue; discard is the quieter neighbour. Nor is it a danger colour: nothing is deleted, and
   painting it as destruction would misdescribe what the press does.

   The discarded section is set apart by a rule and by muted text rather than by being hidden
   behind a toggle -- the whole reason it exists is that a person can find the undo. */
.wl-discard { border-color: var(--it-line); color: var(--it-slate); }
.wl-discarded {
  border-top: 1px solid var(--it-line); margin-top: 16px; padding-top: 12px;
}
.wl-row-discarded { opacity: 0.78; }
.wl-row-discarded .wl-name { font-weight: 600; }
.wl-discard:disabled { opacity: 0.6; cursor: default; }
/* The way in, on the heartbeat's own line. Sized to the bar it sits in rather than to the
   panel's controls: the bar is 12px text and a taller button would push its row open. */
.hb-btn {
  font: inherit; font-size: 12px; padding: 1px 8px; margin-left: 4px;
  border: 1px solid var(--it-blue); border-radius: 4px; background: none;
  color: var(--it-blue); cursor: pointer;
}

/* ---------- Account view (LOCUS009G): People, Phones, Billing ----------
   Reuses the app-modal chrome (backdrop, card, panel-close) rather than inventing a second
   modal system; only the card's width and alignment differ, since this one holds tables and
   forms rather than a centred QR code. */
.acct-card { max-width: 640px; text-align: left; max-height: 82vh; overflow-y: auto; }
.acct-tabs { display: flex; gap: 6px; border-bottom: 1px solid var(--it-line); margin: 0 0 14px; }
.acct-tab {
  font: inherit; font-size: 14px; font-weight: 600; padding: 8px 4px; margin-bottom: -1px;
  border: none; border-bottom: 2px solid transparent; background: none; color: var(--it-slate);
  cursor: pointer;
}
.acct-tab.active { color: var(--it-deep); border-bottom-color: var(--it-blue); }
.acct-panel { padding-top: 2px; }
.acct-note { color: var(--it-slate); font-size: 14px; margin: 0 0 12px; line-height: 1.5; }
.acct-add-form { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-bottom: 8px; }
.acct-add-form input, .acct-add-form select {
  font: inherit; font-size: 14px; padding: 6px 8px; border: 1px solid var(--input-border);
  border-radius: 4px; background: var(--it-white); color: var(--it-ink);
}
.acct-add-form input[type="email"] { flex: 2; min-width: 160px; }
.acct-add-form input[type="text"] { flex: 1; min-width: 140px; }
.acct-rows { margin-top: 10px; }
.acct-row {
  display: flex; align-items: center; gap: 10px; padding: 8px 0;
  border-bottom: 1px solid var(--it-line); font-size: 14px;
}
.acct-row:last-child { border-bottom: none; }
.acct-row-main { flex: 1; min-width: 0; }
.acct-row-sub { color: var(--it-slate); font-size: var(--fs-caption); }
.acct-row select {
  font: inherit; font-size: 14px; padding: 4px 6px; border: 1px solid var(--input-border);
  border-radius: 4px; background: var(--it-white); color: var(--it-ink);
}
.acct-row-remove {
  font: inherit; font-size: 14px; padding: 3px 10px; min-height: 28px;
  border: 1px solid var(--input-border); border-radius: 4px; background: none;
  color: var(--it-slate); cursor: pointer;
}
.acct-row-remove:hover { border-color: var(--danger); color: var(--danger); }
/* LOCUS025D: "First Last" over the email; a blank name offers Add name; Edit name opens inline. */
.acct-row-email { color: var(--it-slate); font-size: 12px; }
.acct-row-addname {
  font: inherit; font-size: 12px; margin-left: 6px; padding: 0; border: 0; background: none;
  color: var(--it-blue); text-decoration: underline; cursor: pointer;
}
.acct-name-edit { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.acct-name-edit input {
  font: inherit; font-size: 14px; padding: 4px 6px; border: 1px solid var(--input-border);
  border-radius: 4px; background: var(--it-white); color: var(--it-ink); width: 9em; max-width: 100%;
}
.acct-you-badge {
  display: inline-block; margin-left: 6px; padding: 0 5px; border-radius: 3px;
  font-size: 12px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  background: rgba(11,86,189,0.12); color: var(--it-blue); vertical-align: middle;
}
/* LOCUS024U: passwords on the Account view. */
.acct-row-temp {
  font: inherit; font-size: 12px; padding: 3px 10px; min-height: 26px;
  border: 1px solid var(--it-line); border-radius: 4px; background: none;
  color: var(--it-slate); cursor: pointer;
}
.acct-row-temp:hover { border-color: var(--it-blue); color: var(--it-blue); }
.acct-temp-pw {
  background: rgba(11,86,189,0.08); border: 1px solid var(--it-blue); border-radius: 6px;
  padding: 8px 10px; margin-top: 8px; font-size: 12px; color: var(--it-ink);
}
.acct-temp-pw code { font-size: 15px; font-weight: 700; letter-spacing: 0.04em; user-select: all; }
.acct-pw-form { display: flex; flex-direction: column; gap: 8px; max-width: 320px; margin-bottom: 8px; }
.acct-pw-form input {
  font: inherit; font-size: 14px; padding: 6px 8px; border: 1px solid var(--it-line);
  border-radius: 4px; background: var(--it-white); color: var(--it-ink); width: 100%;
}
.acct-pw-form .ed-save { align-self: flex-start; }
.acct-next-month {
  background: rgba(11,86,189,0.06); border: 1px solid var(--it-line); border-radius: 6px;
  padding: 10px 12px; margin-bottom: 12px; font-size: 14px;
}
.acct-statement-row {
  display: flex; justify-content: space-between; gap: 10px; padding: 8px 0; width: 100%;
  background: none; border: none; border-bottom: 1px solid var(--it-line); font: inherit; font-size: 14px;
  color: var(--it-ink); text-align: left; cursor: pointer;
}
.acct-statement-row:hover { color: var(--it-blue); }
.acct-statement-detail {
  margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--it-line); font-size: 14px;
}
.acct-statement-line { display: flex; justify-content: space-between; padding: 3px 0; }

/* ================================================================================================
   SENSOR TRENDS (LOCUS012D) -- light and warmth as change over time, never as a number.

   THE RULE THIS STYLESHEET ENFORCES BY OMISSION: there is no class here for a Y AXIS, a tick, a
   gridline or a value label ON EITHER GRAPH, because sensor-trends.js draws none. The warmth
   graph has ONE baseline and nothing else, and its vertical position means nothing outside its
   own box -- so there is deliberately nothing to label it with.

   .tr-axis (LOCUS013B 3) IS NOT AN EXCEPTION TO THAT RULE: it is a plain label strip UNDER the
   graph, of TIMES read off the loaded points, with no line connecting it to anything drawn above
   it. There is still no class for a tick or a gridline on the graph itself.
   ================================================================================================ */

/* The sensor mark on a row and in the table: a dot with a ring, in the row's own ink at 55%.
   currentColor, so it inherits whatever the row is set in and can never fight the selected
   state's colour. Baseline-shifted a hair so it sits with the text rather than on top of it. */
.sensor-glyph {
  margin-left: 6px; vertical-align: -1px; color: var(--it-blue); flex: 0 0 auto;
}

.trends { margin-top: 4px; }
/* The 24 h | 7 d switch, on the section's own heading line. Small, quiet, and pushed to the end
   so the heading still reads as a heading. */
.tr-range { float: right; text-transform: none; letter-spacing: 0; }
.tr-range-btn {
  background: none; border: 1px solid var(--it-line); color: var(--it-slate);
  font: inherit; font-size: 12px; padding: 2px 8px; border-radius: 6px; cursor: pointer;
  margin-left: 4px;
}
.tr-range-btn.tr-on { border-color: var(--it-blue); color: var(--it-blue); font-weight: 600; }

.tr-label {
  font-size: 12px; color: var(--it-slate); margin: 10px 0 2px; font-weight: 600;
}
/* 100% wide, 72px tall, and the viewBox is stretched to fit -- the shape of the change is the
   subject, and a fixed aspect ratio would letterbox it in a narrow panel. */
.tr-svg { width: 100%; height: 72px; display: block; overflow: visible; }
.tr-svg-light { height: 26px; }

/* THE ONE HUE. The blue this dashboard already uses everywhere. One colour on the warmth graph is
   a deliberate constraint: two would imply a threshold between them, and there is no threshold on
   this channel and never will be. */
.tr-line {
  fill: none; stroke: var(--it-blue); stroke-width: 2;
  stroke-linejoin: round; stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
.tr-dot { fill: var(--it-blue); }
/* The baseline. Not an axis: it carries no tick and no number. */
.tr-base { stroke: var(--it-line); stroke-width: 1; vector-effect: non-scaling-stroke; }
/* Invisible hover columns. `fill: transparent` and not `fill: none`: none is not hit-testable,
   so the tooltip would only appear on the 2px line itself. `cursor: pointer` (LOCUS013B 4) is the
   only hint on the page that a tap here pins a readout below the graph. */
.tr-hit { fill: transparent; cursor: pointer; }
.tr-hit:hover { fill: rgba(0, 88, 208, 0.06); }

/* The light strip: ink at 70% for dark and 25% for dim (LOCUS013B, from /code-review evidence
   9/4: dark was the PALEST band on the strip, exactly backwards from what its own name says).
   Ink rather than a grey token so the three still read as one scale of the same thing. */
.tr-band, .tr-gap-hit { shape-rendering: crispEdges; cursor: pointer; }
.tr-dark { fill: rgba(10, 10, 10, 0.70); }
.tr-dim { fill: rgba(10, 10, 10, 0.25); }
.tr-bright { fill: var(--it-blue); }
/* The gaps are tappable too (hard stop 5), but nothing is DRAWN over them: the bare background
   between two blocks is what "nobody was listening" already looks like, and painting it would
   contradict that. */
.tr-gap-hit { fill: transparent; }
.tr-gap-hit:hover { fill: rgba(10, 10, 10, 0.04); }

/* THE LABEL STRIP UNDER EACH GRAPH (LOCUS013B 3) -- times, never a tick and never a gridline.
   Four spans, spread by flexbox, the same row shape under both graphs. */
.tr-axis {
  display: flex; justify-content: space-between; font-size: 12px; color: var(--muted-2);
  margin-top: 3px;
}

/* THE LEGEND (LOCUS013B 1/2): the four things a strip colour can mean, named. Swatches painted
   with the strip's OWN classes (tr-bright/tr-dim/tr-dark), so a legend colour can never drift
   from what the strip actually draws. */
.tr-legend {
  display: flex; align-items: center; flex-wrap: wrap; font-size: 12px; color: var(--it-slate);
  margin-top: 6px;
}
.tr-swatch {
  width: 10px; height: 10px; border-radius: 2px; display: inline-block; margin-left: 10px;
}
.tr-swatch:first-child { margin-left: 0; }
.tr-swatch.tr-bright { background: var(--it-blue); }
.tr-swatch.tr-dim { background: rgba(10, 10, 10, 0.25); }
.tr-swatch.tr-dark { background: rgba(10, 10, 10, 0.70); }
.tr-swatch.tr-none { background: transparent; border: 1px solid var(--it-line); }

/* THE GAP SENTENCE (LOCUS013B 2), fixed under the warmth graph -- what a break in the line means,
   separate from .tr-note's server-supplied cadence sentence. */
.tr-gap-note { font-size: 12px; color: var(--muted-2); margin-top: 4px; line-height: 1.4; }

/* THE DRILL-DOWN READOUT (LOCUS013B 4): empty and taking no visible room (:empty) until a tap
   sets it via app.js; persists until the next tap or a window toggle clears it. */
.tr-readout { font-size: 12px; font-weight: 600; color: var(--it-ink); margin-top: 6px; }
.tr-readout:empty { margin-top: 0; }

/* The server's own sentence about what the graph is made of, verbatim, under both. */
.tr-note { font-size: 12px; color: var(--muted-2); margin-top: 6px; line-height: 1.4; }
/* Millivolts. The one number with a real unit on this whole surface -- small and secondary,
   which is what "secondary" is supposed to look like. */
.tr-batt { font-size: 12px; color: var(--muted-2); margin-top: 4px; }

/* ---------- The pre-printed RFID plate (LOCUS012E) ----------
   A 24-character hex string somebody is going to read out loud or re-type into a reader, so it is
   monospace and it does not wrap mid-number if the panel is narrow -- a plate broken across two
   lines is a plate somebody transcribes wrong. Copy-on-click, so it carries the pointer a control
   carries and a visible focus ring for the keyboard path (role="button", tabindex="0" in app.js).
   Deliberately no colour of its own: it is an identifier row like the Locus number above it, not
   a status. */
.uhf-plate {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  white-space: nowrap;
}
.uhf-plate:focus-visible { outline: 2px solid var(--it-blue); outline-offset: 2px; }

/* LOCUS024ZE: the idle sign-out warning (idle-signout.js), on both consoles. Above every panel and
   the toast stack, at the top centre where the eye lands; the button takes focus when it appears. */
.idle-warning {
  /* LOCUS024ZR: below the assumed-session bar when there is one (app.js sets the variable). */
  position: fixed; top: calc(16px + var(--assumed-banner-h, 0px)); left: 50%; transform: translateX(-50%);
  z-index: 2000; max-width: calc(100vw - 32px);
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 12px 16px; border-radius: 10px;
  background: #fff; color: #1a1a1a; border: 2px solid var(--it-blue);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.25);
}
.idle-warning[hidden] { display: none; }
.idle-warning p { margin: 0; font-weight: 600; }

/* LOCUS024ZB: the bar across every page of an assumed session (RES staff inside a customer's
   account). Amber, not the brand blue, so it reads as "this is not the customer"; fixed, so no
   scroll hides it; app.js pushes the page down by its measured height so nothing sits under it. */
.assumed-banner {
  position: fixed; top: 0; left: 0; right: 0; z-index: 1500;
  display: flex; align-items: center; justify-content: center; gap: 12px; flex-wrap: wrap;
  min-height: 40px; padding: 6px 16px;
  background: #92400e; color: #fff; font-weight: 600; font-size: 14px; text-align: center;
}
.assumed-banner[hidden] { display: none; }
.assumed-leave {
  background: #fff; color: #92400e; border: 0; border-radius: 6px;
  padding: 6px 14px; font-weight: 700; cursor: pointer;
}
.assumed-leave:focus-visible { outline: 3px solid #fde68a; outline-offset: 2px; }
body.assumed { box-sizing: border-box; } /* padding-top set by app.js fitAssumedBanner */

/* LOCUS024ZB: the staff desk's "why are you opening this account" box. */
.assume-dialog { border: 0; border-radius: 12px; padding: 20px; width: min(460px, calc(100vw - 32px)); box-shadow: 0 12px 40px rgba(0,0,0,.3); }
.assume-dialog::backdrop { background: rgba(0, 0, 0, 0.4); }
.assume-dialog h3 { margin: 0 0 6px; }
.assume-dialog label { display: block; margin: 12px 0 6px; font-weight: 600; }
.assume-dialog input { width: 100%; box-sizing: border-box; padding: 8px 10px; font: inherit; }
.assume-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 14px; }

/* LOCUS024ZD: the one password box (password-box.js), on both consoles. */
.pw-box { margin: 10px 0; padding: 12px 14px; border: 1px solid var(--it-line, #d0d7e2); border-radius: 10px; background: #fffbea; }
.pw-box-says { font-weight: 600; margin-bottom: 8px; }
.pw-box-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.pw-box-value { font-size: 17px; font-weight: 700; letter-spacing: 0.06em; padding: 4px 8px; background: #fff; border-radius: 6px; min-width: 12ch; }
.pw-box-btn { font: inherit; padding: 6px 12px; border: 1px solid var(--it-blue, #0058D0); border-radius: 6px; background: #fff; color: var(--it-blue, #0058D0); cursor: pointer; }
.pw-box-btn:focus-visible { outline: 2px solid var(--it-blue, #0058D0); outline-offset: 2px; }
.pw-box-status, .pw-box-note { margin-top: 6px; font-size: 14px; color: #555; }
/* Inside the existing hosts (the staff desk's .otp-box, the dashboard's .acct-temp-pw) the box takes the host's own frame. */
.otp-box > .pw-box, .acct-temp-pw > .pw-box { margin: 0; padding: 0; border: 0; background: none; }

/* LOCUS024ZG: the one confirm (confirm-dialog.js), on both consoles. */
.confirm-dialog { border: 0; border-radius: 12px; padding: 20px 22px; width: min(480px, calc(100vw - 32px)); box-shadow: 0 12px 40px rgba(0,0,0,.3); }
.confirm-dialog::backdrop { background: rgba(0, 0, 0, 0.45); }
.confirm-title { margin: 0 0 8px; font-size: 17px; }
.confirm-message { margin: 0 0 12px; line-height: 1.45; }
.confirm-type-label { display: block; font-weight: 600; margin: 4px 0 6px; }
.confirm-type { width: 100%; box-sizing: border-box; padding: 8px 10px; font: inherit; margin-bottom: 4px; }
.confirm-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 14px; }
.confirm-actions button { font: inherit; padding: 8px 14px; border-radius: 8px; cursor: pointer; border: 1px solid #b8c2d0; background: #fff; }
.confirm-actions .confirm-go { background: var(--it-blue, #0058D0); border-color: var(--it-blue, #0058D0); color: #fff; font-weight: 700; }
.confirm-actions .confirm-go.danger { background: #b42318; border-color: #b42318; }
.confirm-actions .confirm-go:disabled { opacity: 0.45; cursor: not-allowed; }
.confirm-actions button:focus-visible, .confirm-type:focus-visible { outline: 3px solid var(--it-blue, #0058D0); outline-offset: 2px; }
.acct-row-fixed { font-size: 14px; color: var(--it-slate, #555); padding: 4px 8px; }

/* LOCUS024ZI: what the last billing run did, kept on screen until the next run. */
.billing-result { margin: 10px 0; padding: 10px 12px; border-radius: 8px; background: var(--it-surface, #f3f6fb); font-size: 14px; }
.billing-result ul { margin: 6px 0 0 18px; padding: 0; }

/* LOCUS024ZJ: shared form controls for both consoles (024ZA M9/M10/M11). Every text box and picker
   has an edge you can see (3:1 or more); a tick box is a 24 px target; the one red is --danger. */
input[type="text"], input[type="email"], input[type="password"], input[type="date"], input[type="search"],
input[type="number"], select, textarea {
  /* Review 2: no !important -- a field's own :focus colour (one class more specific) still wins. */
  border-color: var(--input-border);
}
select { font-size: var(--fs-body); min-height: 32px; padding: 4px 8px; border: 1px solid var(--input-border); border-radius: 6px; background: var(--it-white); color: var(--it-ink); }
input[type="checkbox"] { width: 24px; height: 24px; margin: 0; accent-color: var(--it-blue); cursor: pointer; }
.panel-close { min-width: 32px; min-height: 32px; }

/* LOCUS024ZM: an account with no site yet -- one calm line, never an error colour. */
.first-site {
  margin: 8px 16px 0; padding: 10px 14px; border-radius: 8px;
  background: #eef4fc; color: #1a1a1a; border: 1px solid #c9daf3;
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
}
.first-site[hidden] { display: none; }
.first-site-btn {
  border: 1px solid var(--it-blue); background: #fff; color: var(--it-blue);
  border-radius: 6px; padding: 4px 12px; font-weight: 600; cursor: pointer;
}
.first-site-btn:focus-visible { outline: 2px solid var(--it-blue); outline-offset: 2px; }
/* The "Not in a site" card opens to its tags, by name. */
.s-nosite { cursor: pointer; }
.s-nosite-list { margin: 8px 0 0; padding-left: 18px; text-align: left; max-height: 220px; overflow-y: auto; }
.s-nosite-list[hidden] { display: none; }
