/* Beekon console — shell + component vocabulary.
 *
 * Rules that carry the brand and must not drift:
 *   - Elevation is surface tone + 1px hairlines. There is no drop shadow in the
 *     brand except one amber-tinted glow; overlays are the only exception here.
 *   - Tables/panels use the 1px-gap grid motif: gap:1px over a --line
 *     background with --bg-elev cells, so borders never double up.
 *   - Mono is a semantic layer (ids, keys, coordinates, micro-labels), not
 *     decoration.
 *   - Empty and error states are VISUALLY DISTINCT. Conflating them was the
 *     worst information-design fault of the console this replaces.
 */

/* ---------- base ---------- */

* { box-sizing: border-box; }

html, body { margin: 0; padding: 0; height: 100%; }

body {
  font-family: var(--f-body);
  font-size: var(--t-body);
  line-height: 1.5;
  letter-spacing: var(--tr-body);
  background: var(--bg);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 { margin: 0; font-family: var(--f-display); font-weight: 500; }
p { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }

a { color: var(--accent-text); text-decoration: none; }
a:hover { text-decoration: underline; }
/* A link inside a sentence is underlined: its colour alone is 1.4:1 against the
   text around it (M4-0407, audit G5-12; WCAG 1.4.1). Buttons and tags that
   happen to be links keep their own look. */
:is(p, .page-sub, .footnote, .field-hint, .empty-body) a:not(.btn):not(.config-tag) {
  text-decoration: underline; text-underline-offset: 2px; text-decoration-thickness: 1px;
}

:focus { outline: none; }
:focus-visible {
  outline: 2px solid var(--accent-text);
  outline-offset: 1px;
  border-radius: var(--r-sm);
}

/* The router moves focus to each view's heading so keyboard and screen-reader
   users land in the new page. That is a programmatic focus on a non-interactive
   element — it must not paint a focus ring, which would read as a selected
   control. Interactive elements keep theirs. */
[tabindex="-1"]:focus,
[tabindex="-1"]:focus-visible { outline: none; }

/* Screen-reader-only, but focusable (skip link). */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip:focus-visible {
  position: fixed; top: var(--s-5); left: var(--s-5); z-index: 100;
  width: auto; height: auto; margin: 0; clip: auto;
  padding: var(--s-3) var(--s-6); background: var(--bg-elev);
  border: 1px solid var(--line-strong); border-radius: var(--r-md);
}

/* ---------- typography helpers ---------- */

.eyebrow {
  font-family: var(--f-mono);
  font-size: var(--t-eyebrow);
  font-weight: 500;
  letter-spacing: var(--tr-eyebrow);
  text-transform: uppercase;
  color: var(--accent-text);
}
.eyebrow-muted { color: var(--ink-3); }

.page-title {
  font-size: var(--t-display);
  letter-spacing: var(--tr-display);
  line-height: 1.15;
}
.h2 { font-size: var(--t-h2); letter-spacing: var(--tr-h2); }
.h3 { font-size: var(--t-h3); letter-spacing: var(--tr-h3); }

.muted { color: var(--ink-3); }
.faint { color: var(--ink-4); }
.small { font-size: var(--t-sm); }
.micro { font-size: var(--t-micro); }
.mono { font-family: var(--f-mono); }
.num { font-variant-numeric: tabular-nums; }

.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* An id/ULID rendered inline: mono, dimmed, never wrapping mid-token. */
.id {
  font-family: var(--f-mono);
  font-size: var(--t-micro);
  color: var(--ink-3);
  letter-spacing: 0;
}

/* ---------- app shell ---------- */

.shell { display: grid; grid-template-columns: var(--sidebar-w) minmax(0, 1fr); min-height: 100vh; }

.sidebar {
  background: var(--bg-sunk);
  border-right: 1px solid var(--line);
  display: flex; flex-direction: column;
  position: sticky; top: 0; height: 100vh;
  /* Console v3's grouped nav (M4-0377) can be taller than a short window. */
  overflow-y: auto; overscroll-behavior: contain;
  /* On a laptop the nav is taller than the window, and a classic always-on
     scrollbar then sits mid-screen beside the page's own (M4-0445): keep the
     gutter thin and show the thumb only while the pointer or focus is here. */
  scrollbar-width: thin; scrollbar-color: transparent transparent;
}
.sidebar:hover, .sidebar:focus-within { scrollbar-color: var(--line-strong) transparent; }

.brand {
  display: flex; align-items: center; gap: var(--s-4);
  padding: var(--s-6) var(--s-6) var(--s-7);
}
.brand svg { width: 26px; height: 26px; flex: none; }
.brand-name {
  font-family: var(--f-display); font-size: 17px; font-weight: 600;
  letter-spacing: -0.022em; line-height: 1;
}
.brand-sub {
  font-family: var(--f-mono); font-size: var(--t-eyebrow); font-weight: 500;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--accent-text); margin-top: 3px;
}

.nav { display: flex; flex-direction: column; gap: var(--s-6); padding: 0 var(--s-4) var(--s-5); flex: 1; }
/* Console v3's groups (Sidebar.dc.html, M4-0377): a mono eyebrow over each. */
.nav-group { display: flex; flex-direction: column; gap: 2px; }
.nav-group-label {
  display: block; padding: 0 var(--s-4) var(--s-2);
  font-family: var(--f-mono); font-size: var(--t-eyebrow); font-weight: 500;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-3);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.nav-item {
  display: flex; align-items: center; gap: var(--s-4);
  min-height: 36px;
  padding: var(--s-2) var(--s-4);
  border-radius: var(--r-md);
  color: var(--ink-2);
  font-size: var(--t-sm);
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.nav-item:hover { background: color-mix(in oklch, var(--ink) 5%, transparent); text-decoration: none; color: var(--ink); }
.nav-item[aria-current="page"] {
  background: color-mix(in oklch, var(--honey) 14%, transparent);
  color: var(--accent-text);
  font-weight: 500;
}
.nav-item svg { width: 15px; height: 15px; flex: none; opacity: 0.85; }

.sidebar-foot { padding: var(--s-5) var(--s-6); border-top: 1px solid var(--line); }
.sidebar-docs {
  display: flex; align-items: center; gap: var(--s-3);
  margin-top: var(--s-4); font-size: var(--t-sm); color: var(--ink-2);
}
.sidebar-docs svg { width: 15px; height: 15px; flex: none; }

/* A laptop window (M4-0445): the same nav, tighter, so it fits without
   scrolling. 940 px tall covers a 1080 p screen at 100 % less the browser's own
   chrome; desktop only, the phone drawer keeps its 44 px targets. */
@media (min-width: 768px) and (max-height: 940px) {
  .brand { padding: var(--s-5) var(--s-6) var(--s-5); }
  .nav { gap: var(--s-4); padding-bottom: var(--s-3); }
  .nav-group { gap: 0; }
  .nav-group-label { padding-bottom: var(--s-1); }
  .nav-item { min-height: 30px; padding: var(--s-1) var(--s-4); }
  .sidebar-foot { padding: var(--s-3) var(--s-6); }
  .sidebar-docs { margin-top: var(--s-2); }
}
/* 1280 × 720 (a 1920 px screen at 150 %) is the shortest common laptop window. */
@media (min-width: 768px) and (max-height: 760px) {
  .brand { padding-block: var(--s-4); }
  .nav-item { min-height: 28px; }
}

.topbar {
  height: var(--topbar-h);
  border-bottom: 1px solid var(--line);
  background: var(--bg-elev);
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-5);
  padding: 0 var(--s-8);
  position: sticky; top: 0; z-index: 20;
}
.topbar-right { display: flex; align-items: center; gap: var(--s-5); }

/* The top bar's breadcrumb (Topbar.dc.html, M4-0377): scope / page. It
   shrinks before anything else in the bar. */
.crumbs { display: flex; align-items: center; gap: var(--s-3); min-width: 0; flex: 1 1 200px; font-size: var(--t-sm); }
.crumb-scope { color: var(--ink-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; flex: 0 1 auto; }
.crumb-sep { color: var(--ink-4, var(--ink-3)); }
.crumb-page { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.crumbs [hidden] { display: none; }

/* The account menu (Topbar.dc.html): initials, then a menu under them. */
.account { position: relative; }
.account-btn {
  display: inline-flex; align-items: center; gap: var(--s-3);
  height: 38px; padding: 0 10px 0 4px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--bg-elev); color: var(--ink);
  font: inherit; cursor: pointer;
}
.account-avatar {
  width: 30px; height: 30px; border-radius: 999px;
  background: color-mix(in oklch, var(--honey) 22%, transparent); color: var(--accent-text);
  display: inline-flex; align-items: center; justify-content: center;
  font-weight: 600; font-size: 12px;
}
.account-menu {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 30;
  width: 280px; max-width: calc(100vw - 32px); box-sizing: border-box; padding: var(--s-3);
  border-radius: var(--r-lg, 14px); border: 1px solid var(--line); background: var(--bg-elev);
  box-shadow: 0 12px 32px color-mix(in oklch, oklch(20% 0.03 60) 16%, transparent);
  display: flex; flex-direction: column; gap: 2px;
}
.account-menu[hidden] { display: none; }
.account-menu-head { padding: var(--s-3) var(--s-4) var(--s-4); border-bottom: 1px solid var(--line); margin-bottom: var(--s-2); }
.account-menu-email { font-weight: 600; }
.account-menu-item, .account-menu-foot .btn {
  display: flex; align-items: center; width: 100%; min-height: 38px; box-sizing: border-box;
  padding: 0 var(--s-4); border-radius: var(--r-md); border: 0; background: transparent;
  color: var(--ink); font: inherit; font-size: var(--t-sm); text-align: left; justify-content: flex-start; cursor: pointer;
}
.account-menu-item:hover, .account-menu-item:focus-visible,
.account-menu-foot .btn:hover, .account-menu-foot .btn:focus-visible {
  background: color-mix(in oklch, var(--ink) 5%, transparent); text-decoration: none;
}
.account-menu-foot { border-top: 1px solid var(--line); margin-top: var(--s-2); padding-top: var(--s-2); }

/* The project switcher (M4-0180), in the sidebar since Console v3 (M4-0377):
   a "Project" eyebrow over a full-width button naming the project and its
   region; since M4-0410 (G1-02, G1-03) the button opens a popover listbox. */
.switcher { position: relative; display: flex; flex-direction: column; gap: var(--s-2); min-width: 0; }
.switcher-btn {
  display: flex; align-items: center; gap: var(--s-3); width: 100%; min-width: 0; min-height: 38px;
  padding: 8px 10px; box-sizing: border-box;
  border: 1px solid var(--line); border-radius: var(--r-md); background: var(--bg-elev); color: var(--ink);
  font: inherit; font-size: var(--t-sm); font-weight: 600; text-align: left; cursor: pointer;
}
.switcher-btn:hover { border-color: color-mix(in oklch, var(--ink) 25%, var(--line)); }
.switcher-btn > .switcher-name { flex: 1 1 auto; min-width: 0; }
.switcher-btn > svg { flex: none; color: var(--ink-3); }
.switcher-btn[aria-expanded="true"] > svg { transform: rotate(180deg); }
.region-chip { flex: none; }
.switcher-btn > .region-chip[hidden] { display: none; }
.switcher-pop {
  position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 30;
  display: flex; flex-direction: column; gap: var(--s-2); padding: var(--s-2);
  border: 1px solid var(--line); border-radius: var(--r-lg, 14px); background: var(--bg-elev);
  box-shadow: 0 12px 32px color-mix(in oklch, oklch(20% 0.03 60) 16%, transparent);
}
.switcher-pop[hidden], .switcher-filter[hidden], .switcher-new[hidden] { display: none; }
.switcher-filter { width: 100%; min-width: 0; box-sizing: border-box; }
.switcher-list { list-style: none; margin: 0; padding: 0; max-height: min(320px, 50vh); overflow-y: auto; outline: none; }
.switcher-option {
  display: flex; align-items: center; gap: var(--s-3); min-height: 36px; padding: 0 var(--s-3);
  border-radius: var(--r-md); font-size: var(--t-sm); color: var(--ink); cursor: pointer;
}
.switcher-option[hidden] { display: none; }
.switcher-option > .switcher-option-name { flex: 1 1 auto; min-width: 0; }
.switcher-option:hover { background: color-mix(in oklch, var(--ink) 5%, transparent); }
.switcher-option.is-active { background: color-mix(in oklch, var(--honey) 16%, transparent); outline: 2px solid var(--focus, var(--honey)); outline-offset: -2px; }
.switcher-option[aria-selected="true"] { font-weight: 600; }
.switcher-new { justify-content: flex-start; gap: var(--s-2); border-top: 1px solid var(--line); border-radius: 0 0 var(--r-md) var(--r-md); }

/* The sidebar's head: the brand, and — where the sidebar is the phone drawer —
   its close button (M4-0410, G1-32), shown only below 768 px. */
.sidebar-head { display: flex; align-items: center; min-width: 0; }
.sidebar-head > .brand { flex: 1 1 auto; min-width: 0; }
.sidebar-head > .drawer-close { display: none; }
.shell-main { min-width: 0; }

/* The account menu's Theme group (M4-0410, G1-37): three menuitemradio, the
   current one checked. */
.account-menu-group { display: flex; flex-direction: column; gap: 2px; border-top: 1px solid var(--line); margin-top: var(--s-2); padding-top: var(--s-2); }
.account-menu-label { padding: var(--s-2) var(--s-4) 0; }
.account-menu-radio { justify-content: space-between; }
.account-menu-radio[aria-checked="true"] { font-weight: 600; }
.account-menu-radio[aria-checked="true"]::after { content: "✓"; color: var(--accent-text); }

/* The frame boot paints while /me is in flight (M4-0410, G5-16): the sidebar
   and the top bar as empty chrome, and a skeleton where the page goes. */
.shell-boot .boot-side { background: var(--bg-sunk); border-right: 1px solid var(--line); height: 100vh; position: sticky; top: 0; }
.shell-boot .boot-top { height: var(--topbar-h); border-bottom: 1px solid var(--line); background: var(--bg-elev); }

/* Lists of projects as links: the project not-found page and Choose a project
   (M4-0410, G1-28, G1-31). */
.link-list { list-style: none; margin: var(--s-3) 0 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.link-list a { display: flex; align-items: center; gap: var(--s-3); min-height: 40px; padding: 0 var(--s-4); border-radius: var(--r-md); color: var(--ink); }
.link-list a:hover { background: color-mix(in oklch, var(--ink) 5%, transparent); text-decoration: none; }
.link-list-name { font-weight: 500; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.not-found-list { margin-top: var(--s-7); }
.choose-list { margin-top: var(--s-6); }

/* The Organisation page's tabs (Organisation.dc.html, M4-0377): links. */
.org-tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--line); margin-bottom: var(--s-7); overflow-x: auto; }
.org-tab {
  display: inline-flex; align-items: center; min-height: 40px;
  padding: 10px 14px; color: var(--ink-3); font-weight: 500; white-space: nowrap;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.org-tab:hover { color: var(--ink); text-decoration: none; }
.org-tab[aria-current="page"] { color: var(--ink); border-bottom-color: var(--honey); }

/* Every page fills the main column; long prose keeps its own ch-based measure. */
.content { padding: var(--s-9) var(--s-8) var(--s-10); }
.content-wide { padding-left: var(--s-7); padding-right: var(--s-7); }
.page-head { margin-bottom: var(--s-8); }
.page-head .page-title { margin-top: var(--s-2); }
.page-head-row { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--s-6); flex-wrap: wrap; }
/* The view's one-sentence subtitle (Design v2's .v2-sub). */
.page-sub { color: var(--ink-3); font-size: var(--t-sm); margin-top: var(--s-2); max-width: 80ch; }
/* The project and its region, under the subtitle (M4-0281). */
.page-meta { display: flex; align-items: center; gap: var(--s-4); flex-wrap: wrap; margin-top: var(--s-3); font-size: var(--t-xs); color: var(--ink-3); }
/* A note under a table (Design v2's Devices footnote). */
.footnote { margin-top: var(--s-5); max-width: 80ch; }

section + section { margin-top: var(--s-9); }
.section-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-5); margin-bottom: var(--s-5); }

/* ---------- buttons ---------- */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s-3);
  font-family: var(--f-body); font-size: var(--t-sm); font-weight: 500;
  letter-spacing: var(--tr-body);
  padding: 8px 14px;
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  background: transparent; color: var(--ink);
  cursor: pointer; white-space: nowrap;
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
/* The base :focus-visible outline (2 px accent ink, 7.45:1 / 8.12:1) is the
   ring; a second box-shadow ring under it would only thicken it. */
.btn:focus-visible { outline-offset: 2px; }
.btn:disabled { opacity: 0.55; cursor: default; }
/* A hidden button is gone (M4-0412: "Clear filters" shows only while a filter
   is applied); .btn's inline-flex would otherwise outrank [hidden]. */
.btn[hidden] { display: none; }
.btn:not(:disabled):active { transform: translateY(0.5px); }

.btn-primary { background: var(--accent-solid); color: var(--accent-solid-ink); }
.btn-primary:not(:disabled):hover { background: color-mix(in oklch, var(--accent-solid) 88%, var(--honey)); transform: translateY(-1px); }
/* A disabled primary button has its own colours, not the enabled one faded: at
   55% opacity the dark theme's honey still read as a live Save (M4-0407, G4-10). */
.btn-primary:disabled { opacity: 1; background: var(--bg-sunk); color: var(--ink-3); border-color: var(--line-strong); }

.btn-ghost { border-color: var(--line-strong); color: var(--ink); }
.btn-ghost:not(:disabled):hover { background: var(--bg-elev); border-color: var(--ink-4); }

.btn-danger { border-color: var(--line-strong); color: var(--red); }
.btn-danger:not(:disabled):hover { background: color-mix(in oklch, var(--red) 10%, transparent); border-color: var(--red); }

.btn-sm { padding: 5px 10px; font-size: var(--t-xs); }
/* Icon buttons are at least 32 px square, 40 px on a phone (M4-0407, G5-17). */
.btn-icon { padding: 6px; min-width: 32px; min-height: 32px; border-radius: var(--r-md); border-color: transparent; color: var(--ink-3); }
.btn-icon:hover { background: color-mix(in oklch, var(--ink) 6%, transparent); color: var(--ink); }
.btn-icon svg { width: 16px; height: 16px; display: block; }

.btn-row { display: flex; gap: var(--s-3); flex-wrap: wrap; }

/* ---------- fields ---------- */

.field { display: block; margin-bottom: var(--s-5); }
.field-label {
  display: block; font-size: var(--t-xs); font-weight: 500;
  color: var(--ink-2); margin-bottom: var(--s-2);
}
.field-req { color: var(--ink-4); font-weight: 400; }

.input, .select, textarea.input {
  width: 100%;
  font-family: var(--f-body); font-size: var(--t-body); color: var(--ink);
  background: var(--bg-elev);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  padding: 8px 11px;
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
.input::placeholder { color: var(--ink-4); }
.input:focus-visible, .select:focus-visible, textarea.input:focus-visible {
  outline: none; border-color: var(--accent-text); box-shadow: var(--focus-ring);
}
.input:disabled, .select:disabled { background: var(--bg-sunk); color: var(--ink-3); }
.input.mono { font-family: var(--f-mono); font-size: var(--t-sm); }

.select {
  appearance: none;
  padding-right: 30px;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 16px) 52%, calc(100% - 11px) 52%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}

.field-hint { font-size: var(--t-micro); color: var(--ink-3); margin-top: var(--s-2); }
.field-error { font-size: var(--t-micro); color: var(--red); margin-top: var(--s-2); }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-4); }
/* M4-0418 (G1-08): a password field's Show password toggle, an eye button
   inside the input's right edge (field({reveal: true})). */
.field-reveal-row { position: relative; }
.field-reveal-row > .input { padding-right: 42px; }
.field-reveal {
  position: absolute; top: 50%; right: 4px; transform: translateY(-50%);
  display: inline-grid; place-items: center; width: 32px; height: 32px; padding: 0;
  border: 0; border-radius: var(--r-sm); background: transparent; color: var(--ink-3); cursor: pointer;
}
.field-reveal svg { width: 16px; height: 16px; display: block; }
.field-reveal:hover, .field-reveal[aria-pressed="true"] { color: var(--ink); }
.field-reveal:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.field-reveal:disabled { cursor: default; color: var(--ink-4); }

/* ---------- region picker (M4-0173) ----------
   A permanent, unrepairable choice, so it reads as a decision rather than a
   form control: full-width rows, the human name leading, the cell id and its
   region as secondary metadata in mono because the id is what the operator
   will recognise inside their SDK key. */

/* The region picker is a fieldset (M4-0272): no browser frame around it. */
fieldset.region-field { border: 0; padding: 0; margin-inline: 0; min-width: 0; }
fieldset.region-field > legend { padding: 0; }
.region-options, .role-options { display: grid; gap: var(--s-2); margin-top: var(--s-3); }
.region-option, .role-option {
  display: flex; align-items: flex-start; gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  background: var(--bg-elev);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.region-option:hover, .role-option:hover { border-color: var(--accent-text); }
.region-option:has(input:checked), .role-option:has(input:checked) { border-color: var(--accent-text); background: var(--bg-sunk); }
.region-option:has(input:focus-visible), .role-option:has(input:focus-visible) { box-shadow: var(--focus-ring); }
.region-option input, .role-option input { margin-top: 3px; accent-color: var(--accent-text); flex: none; }
.region-text, .role-text { display: grid; gap: 2px; }
.region-name, .role-name { font-size: var(--t-body); color: var(--ink); }
.region-meta { font-size: var(--t-micro); color: var(--ink-3); }
/* The Change role dialog's roles (M4-0406) borrow the region picker's cards. */
fieldset.role-field { border: 0; padding: 0; margin: 0; min-width: 0; }
.role-options { margin-top: 0; }
.role-meta { font-size: var(--t-sm); color: var(--ink-2); }
.role-option:has(input:disabled) { cursor: default; opacity: .7; }
.role-warn { margin: 0; color: var(--ink); }

/* ---------- cards + the 1px-gap grid motif ---------- */

.card {
  background: var(--bg-elev);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: var(--s-7);
}
.card-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-5); margin-bottom: var(--s-5); }
/* M4-0420 (G1-36): a card's one line under its title (Account.dc.html). */
.card-head:has(> .card-sub) { flex-direction: column; align-items: flex-start; gap: 4px; }
.card-sub { margin: 0; }
.card-foot {
  margin-top: var(--s-6); padding-top: var(--s-5);
  border-top: 1px dashed var(--line);
  display: flex; justify-content: space-between; gap: var(--s-5);
  font-family: var(--f-mono); font-size: var(--t-micro); color: var(--ink-3);
}

/* Seamless bordered grid: one hairline between cells, none doubled. */
.grid-panel {
  display: grid; gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  overflow: hidden;
}
.grid-panel > * { background: var(--bg-elev); padding: var(--s-6); }
.cols-2 { grid-template-columns: 1fr 1fr; }
.cols-3 { grid-template-columns: repeat(3, 1fr); }
.cols-4 { grid-template-columns: repeat(4, 1fr); }

/* ---------- table ---------- */

.table { width: 100%; border-collapse: collapse; font-size: var(--t-sm); }
:is(.table, .skel-table) th {
  text-align: left; font-weight: 500;
  font-family: var(--f-mono); font-size: var(--t-eyebrow);
  letter-spacing: var(--tr-eyebrow); text-transform: uppercase;
  color: var(--ink-3);
  padding: var(--s-4) var(--s-6);
  border-bottom: 1px solid var(--line);
  background: var(--bg-sunk);
  white-space: nowrap;
}
:is(.table, .skel-table) td { padding: var(--s-5) var(--s-6); border-bottom: 1px solid var(--line); vertical-align: middle; }
:is(.table, .skel-table) tr:last-child td { border-bottom: none; }
.table tbody tr { transition: background var(--dur-fast) var(--ease); }
.table tbody tr:hover { background: color-mix(in oklch, var(--ink) 3%, transparent); }
.table .cell-actions { text-align: right; white-space: nowrap; }
.table .cell-num { text-align: right; font-variant-numeric: tabular-nums; }

/* A table wider than its wrap scrolls inside it; clipping hid Devices' last
   column at 1024 px (M4-0407, G5-10). Ids never break mid-token. */
.table-wrap {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  overflow-x: auto;
  background: var(--bg-elev);
}
.table td .id, .table td .mono, .table td .device-id-short { white-space: nowrap; }

/* The Activity table (M4-0181): fourteen columns, so it scrolls inside its own
   wrap rather than clipping (.table-wrap is overflow:hidden) or pushing the page. */
.table-scroll {
  overflow-x: auto;
  max-width: 100%;
  /* Its columns never size the page: on a phone the shell's single column is a
     plain 1fr, which a wide table's min-content would otherwise stretch. */
  contain: inline-size;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--bg-elev);
}
/* v2's compact table (Activity.dc.html, M4-0275): 9px cells and xs text, so
   all fourteen columns fit a 1440 px screen without the wrap scrolling. v2 drew
   13 columns at 9px 14px; the Flags column (0216) leaves 8px a side. */
.table-scroll :is(.table, .skel-table) td, .table-scroll :is(.table, .skel-table) th { white-space: nowrap; padding: 9px 8px; }
.table-scroll .table td { font-size: var(--t-xs); line-height: 1.4; }
.table-scroll :is(.table, .skel-table) th { letter-spacing: 0.02em; }
.table-scroll .table .mono, .table-scroll .table .num, .table-scroll .table .id { font-size: var(--t-micro); }
.table-scroll .table .pos { font-size: 11px; }
/* Pills in the compact table read as words, not eyebrows: the same tones,
   without the uppercase and the tracking that cost a column its width. */
.table-scroll .table .pill { font-size: 12px; padding: 1px 5px; }
.table-scroll .table .wrap-gap { gap: 4px; }
.table-foot { display: flex; justify-content: space-between; gap: var(--s-6); padding: var(--s-5) var(--s-2) 0; font-size: var(--t-xs); color: var(--ink-3); }
.table tr.gated td { background: color-mix(in oklch, var(--red) 4%, transparent); }
/* A session boundary (M4-0338): one cell across the table, not a point. */
.table tr.session-break td {
  padding: 5px 8px; white-space: normal;
  font-size: var(--t-xs); color: var(--ink-2);
  background: var(--bg-sunk);
  border-top: 1px dashed var(--line-strong); border-bottom: 1px dashed var(--line-strong);
}
.table tbody tr.session-break:hover { background: none; }
.table tr.session-break .mono { font-size: var(--t-micro); }

/* A segmented choice (0216's v2 kit, ported without the prefix): one pressed. */
.seg {
  display: inline-flex; gap: 2px;
  background: var(--bg-sunk);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 3px;
}
.seg-btn {
  font-family: var(--f-body); font-size: var(--t-xs);
  min-height: 32px; padding: 5px 10px; border-radius: var(--r-sm);
  border: 0; background: transparent; color: var(--ink-2); cursor: pointer;
  white-space: nowrap;
}
.seg-btn[aria-pressed="true"] { background: var(--accent-solid); color: var(--accent-solid-ink); }

/* A merged sidebar entry's view switch under the page title (D150, M4-0437):
   Locations = Table · Map, Users & devices = Users · Devices. */
.view-switch-row { margin-top: var(--s-4); }
.view-switch .seg-btn { min-width: 72px; }

.activity-controls {
  display: flex; flex-wrap: wrap; align-items: flex-end;
  gap: var(--s-4) var(--s-5);
  margin-bottom: var(--s-6);
}
.activity-ctl { display: flex; flex-direction: column; gap: var(--s-2); min-width: 0; max-width: 100%; }
.activity-ctl .field-label { margin-bottom: 0; }
.activity-custom { display: flex; flex-wrap: wrap; gap: var(--s-4); min-width: 0; max-width: 100%; }
.activity-custom[hidden] { display: none; }
/* M4-0418: the zone beside the inputs, and the pair's one refusal under them. */
.activity-zone { align-self: flex-end; padding-bottom: 10px; }
.activity-custom > .field-error { flex-basis: 100%; margin-top: 0; }
.activity-apply { flex-direction: row; gap: var(--s-3); }
.activity-controls[hidden], .page-head-row > .row[hidden], .page-sub[hidden] { display: none; }
/* The filter toolbar (v2's toolbar card) and its note. */
.activity-controls {
  padding: var(--s-5) var(--s-6);
  border: 1px solid var(--line); border-radius: var(--r-lg);
  background: var(--bg-elev);
}
.activity-hint { flex-basis: 100%; margin: 0; font-size: var(--t-micro); color: var(--ink-3); }
.page-sub { color: var(--ink-3); font-size: var(--t-sm); margin: var(--s-2) 0 0; }
.activity-footnote { text-align: center; margin-top: var(--s-5); }
.activity-cap { margin-bottom: var(--s-5); }
.activity-cap[hidden] { display: none; }

/* ---------- status ---------- */

/* A pill reads as a word: 12 px sentence case in the body face, not a mono
   eyebrow (M4-0407, G1-44). Each tone is its text colour on its -soft tint. */
.pill {
  display: inline-flex; align-items: center; gap: var(--s-2);
  font-family: var(--f-body); font-size: 12px; font-weight: 500;
  letter-spacing: 0; text-transform: none;
  padding: 2px 8px; border-radius: 4px;
  background: color-mix(in oklch, var(--ink) 8%, transparent);
  color: var(--ink-2);
}
.pill-ok { background: var(--green-soft); color: var(--green); }
.pill-warn { background: color-mix(in oklch, var(--amber) 18%, transparent); color: var(--honey-ink); }
.pill-bad { background: var(--red-soft); color: var(--red); }
:root[data-theme="dark"] .pill-warn { color: var(--honey); }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .pill-warn { color: var(--honey); } }

.dot { width: 6px; height: 6px; border-radius: 50%; background: var(--green); flex: none; }
.dot-live { animation: pulse 1.8s infinite; }
@keyframes pulse {
  0% { box-shadow: 0 0 0 0 color-mix(in oklch, var(--green) 55%, transparent); }
  70% { box-shadow: 0 0 0 6px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}
@media (prefers-reduced-motion: reduce) { .dot-live { animation: none; } }

/* A status: a coloured dot and its words, never colour alone (Design v2's
   v2-status; M4-0277 ports the same piece for the Map's "Data as of"). */
.status { display: inline-flex; align-items: center; gap: 7px; font-size: var(--t-xs); color: var(--ink-2); white-space: nowrap; }
.status::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--ink-4); flex: none; }
.status.ok::before { background: var(--green); }
.status.warn::before { background: var(--amber); }
.status.bad::before { background: var(--red); }
.status.quiet::before { background: transparent; border: 1.5px solid var(--ink-4); width: 6px; height: 6px; }

/* Health chips (Design v2's v2-chip): one fact each. */
.chips { display: flex; gap: 6px; flex-wrap: wrap; }
.chip { font-family: var(--f-mono); font-size: 11px; padding: 2px 7px; border-radius: var(--r-sm); background: var(--bg-sunk); border: 1px solid var(--line); color: var(--ink-2); white-space: nowrap; }
.chip.bad { color: var(--red); border-color: color-mix(in oklch, var(--red) 35%, var(--line)); }

/* ---------- stat tile ---------- */

/* 12 px sentence case, like the Activity table's labels (M4-0407, G1-44). */
.stat-label {
  font-family: var(--f-body); font-size: 12px; font-weight: 500;
  letter-spacing: 0; text-transform: none; color: var(--ink-3);
}
.stat-value {
  font-family: var(--f-display); font-size: 28px; font-weight: 500;
  letter-spacing: -0.025em; line-height: 1.1; margin-top: var(--s-3);
  font-variant-numeric: tabular-nums;
}
.stat-note { font-size: var(--t-micro); color: var(--ink-3); margin-top: var(--s-2); }
/* M4-0420 (G1-24): a day-counting tile says whose day beside its label. */
.stat-head { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
.stat-zone { font-size: 11px; color: var(--ink-3); }

/* ---------- empty vs error: deliberately different ---------- */

.empty {
  text-align: center;
  padding: var(--s-10) var(--s-7);
  color: var(--ink-3);
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-lg);
  background: var(--bg-sunk);
}
.empty-title { font-family: var(--f-display); font-size: var(--t-h2); letter-spacing: var(--tr-h2); color: var(--ink); }
.empty-body { margin-top: var(--s-3); font-size: var(--t-sm); max-width: 46ch; margin-left: auto; margin-right: auto; }
.empty-action { margin-top: var(--s-6); }
/* The project-state rule's actions (M4-0415, lib/project-state.js), centred
   under the sentence and wrapping on a narrow box. */
.empty-actions { justify-content: center; flex-wrap: wrap; }

.errorbox {
  display: flex; gap: var(--s-4); align-items: flex-start;
  padding: var(--s-5) var(--s-6);
  border: 1px solid color-mix(in oklch, var(--red) 40%, var(--line));
  border-left: 3px solid var(--red);
  border-radius: var(--r-md);
  background: var(--red-soft);
  color: var(--ink);
  font-size: var(--t-sm);
}
.errorbox-title { font-weight: 500; color: var(--red); }
.errorbox-body { color: var(--ink-2); margin-top: 2px; }
.errorbox-actions { margin-left: auto; display: flex; gap: var(--s-3); flex-wrap: wrap; }

/* An inline refusal (M4-0408, G4-09, States §6): said next to what failed, in
   the dialog, form or panel, never in a toast. Copy error details carries the
   raw text. */
.inline-error {
  display: flex; flex-wrap: wrap; gap: var(--s-3) var(--s-4); align-items: center;
  margin-top: var(--s-5); padding: var(--s-4) var(--s-5);
  border: 1px solid color-mix(in oklch, var(--red) 40%, var(--line));
  border-left: 3px solid var(--red); border-radius: var(--r-md);
  background: var(--red-soft); color: var(--ink); font-size: var(--t-sm);
}
.inline-error[hidden] { display: none; }
.inline-error-msg { flex: 1 1 220px; min-width: 0; }
.inline-error-actions { display: flex; gap: var(--s-3); }
.copy-failed { display: inline-block; margin: 0 0 0 var(--s-3); }

/* The read-only banner (D129 States §5): one note at the top of a page a member
   may view but not change. A member's edit controls are hidden, so this is the
   single acknowledgement, not a row of disabled buttons. */
.readonly-banner { margin-bottom: var(--s-5); }

/* A shortened id with a Copy button (D129 States §8). */
.id-chip { display: inline-flex; align-items: center; gap: var(--s-3); min-width: 0; }
.id-chip .id { color: var(--ink-3); }

/* An "unavailable / not measured" state — neither empty nor error. This is the
   ingest-health honesty case: we could not ask, which is not the same as zero. */
.notice {
  padding: var(--s-5) var(--s-6);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--bg-sunk);
  color: var(--ink-3);
  font-size: var(--t-sm);
}

/* ---------- skeleton ---------- */

.skel {
  background: linear-gradient(90deg, var(--skel-a) 25%, var(--skel-b) 37%, var(--skel-a) 63%);
  background-size: 400% 100%;
  animation: shimmer 1.4s infinite linear;
  border-radius: var(--r-sm);
  height: 1em;
}
@keyframes shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }
@media (prefers-reduced-motion: reduce) { .skel { animation: none; } }
.skel-row { height: 44px; border-radius: var(--r-md); margin-bottom: var(--s-3); }

/* Page-shaped skeletons (M4-0443, lib/ui.js): each mirrors the loaded layout it
   stands in for, inside the same card, so nothing moves when the data lands. */
.skel-cell, .skel-line, .skel-dot, .skel-li-end, .skel-stat, .skel-tab, .skel-heading { display: inline-block; vertical-align: middle; }
.skel-cell { height: 0.8em; max-width: 100%; }
.skel-table { width: 100%; border-collapse: collapse; font-size: var(--t-sm); }
.skel-table td { height: 1.45em; }
.skel-list { display: flex; flex-direction: column; }
.skel-li {
  display: grid; grid-template-columns: 10px minmax(0, 1fr) auto; gap: 10px; align-items: center;
  padding: 11px 16px; border-bottom: 1px solid var(--line);
}
.skel-li-plain { grid-template-columns: minmax(0, 1fr) auto; padding-inline: 0; border-bottom: 0; }
.skel-list-boxed { gap: var(--s-3); }
.skel-list-boxed > .skel-li { grid-template-columns: minmax(0, 1fr) auto; padding: 12px 14px; border: 1px solid var(--line); border-radius: var(--r-md); background: var(--bg-elev); }
.skel-dot { width: 10px; height: 10px; border-radius: 50%; }
.skel-li-text { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.skel-line { display: block; height: 0.75em; max-width: 100%; }
.skel-line-sub { height: 0.6em; }
.skel-li-end { width: 44px; height: 0.7em; }
.skel-stat { width: 3.2ch; height: 0.8em; min-width: 18px; border-radius: var(--r-sm); }
.skel-stat-lg { display: block; height: 26px; width: 40%; max-width: 140px; margin-top: var(--s-3); }
.skel-chart { width: 100%; height: auto; border-radius: var(--r-md); }
.skel-tiles > .skel-tile { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.skel-fields { display: flex; flex-direction: column; gap: var(--s-4); }
.skel-heading { display: block; height: 1.1em; }
.skel-field-grid { row-gap: var(--s-6); margin-top: var(--s-4); }
.skel-field { display: flex; flex-direction: column; gap: var(--s-3); }
.skel-input { display: block; height: 36px; border-radius: var(--r-md); }
.skel-form { display: flex; align-items: flex-start; gap: 24px; }
.skel-form-index { flex: 0 0 200px; display: flex; flex-direction: column; gap: 2px; }
.skel-form-link { min-height: 36px; padding: 0 10px; display: flex; align-items: center; }
.skel-form-cards { flex: 1 1 auto; min-width: 0; }
.skel-tabs { display: flex; gap: var(--s-7); padding: 12px 4px; border-bottom: 1px solid var(--line); }
.skel-tab { width: 72px; height: 0.8em; }
@media (max-width: 760px) {
  .skel-form { flex-direction: column; align-items: stretch; }
  .skel-form-index { flex: none; flex-direction: row; flex-wrap: wrap; }
}

/* ---------- modal ---------- */

.overlay {
  position: fixed; inset: 0; z-index: 60;
  background: color-mix(in oklch, oklch(15% 0.02 60) 55%, transparent);
  display: flex; align-items: center; justify-content: center;
  padding: var(--s-7);
  backdrop-filter: blur(3px);
}
.modal {
  background: var(--bg-elev);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-lg);
  padding: var(--s-8);
  width: 100%; max-width: 460px;
  max-height: calc(100vh - 2 * var(--s-7));
  overflow-y: auto;
  box-shadow: 0 24px 60px -28px color-mix(in oklch, oklch(15% 0.02 60) 60%, transparent);
}
.modal-wide { max-width: 620px; }
.modal-title { font-size: var(--t-h2); letter-spacing: var(--tr-h2); }
.modal-body { margin-top: var(--s-4); color: var(--ink-2); font-size: var(--t-sm); }
.modal-actions { margin-top: var(--s-8); display: flex; justify-content: flex-end; gap: var(--s-3); }

/* ---------- side panels (M4-0413; audit §4.3, §5.4) ----------
   lib/ui.js sidePanel(): Activity's point, Trips' trip, Webhooks' endpoint and
   the Map's device card. Beside the table at 768 px and wider, where each view
   keeps its own layout; below that Activity's, Trips' and Webhooks' are a modal
   bottom sheet in the viewport (G2-03, G3-20, G4-06): the list stays visible
   above it, and the tap that opened it is never answered off-screen. */
.side-panel :is(h1, h2, h3)[tabindex="-1"]:focus { outline: none; }
.side-panel :is(h1, h2, h3)[tabindex="-1"]:focus-visible { outline: 2px solid var(--accent-text); outline-offset: 2px; border-radius: var(--r-sm); }
.overlay.overlay-sheet { align-items: flex-end; justify-content: stretch; padding: var(--s-10) 0 0; }
.overlay-sheet > .side-panel {
  width: 100%; max-width: none; margin: 0;
  max-height: 85vh; max-height: 85dvh; overflow-y: auto; overscroll-behavior: contain;
  border-width: 1px 0 0; border-radius: 18px 18px 0 0;
  padding-bottom: calc(var(--s-6) + env(safe-area-inset-bottom, 0px));
  box-shadow: 0 -6px 24px color-mix(in oklch, var(--ink) 12%, transparent);
}
.overlay-sheet > .side-panel::before { content: ""; display: block; flex: none; width: 36px; height: 4px; border-radius: 2px; background: var(--line-strong); margin: -8px auto 0; }

/* ---------- secret reveal ---------- */

.secret {
  display: flex; align-items: center; gap: var(--s-4);
  font-family: var(--f-mono); font-size: var(--t-sm);
  background: var(--bg-sunk);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  padding: var(--s-4) var(--s-5);
  word-break: break-all;
}
.secret-value { flex: 1; min-width: 0; }
.secret-masked { letter-spacing: 0.08em; color: var(--ink-3); }
.warn-band {
  display: flex; gap: var(--s-4); align-items: flex-start;
  padding: var(--s-5) var(--s-6);
  border: 1px solid color-mix(in oklch, var(--amber) 45%, var(--line));
  border-left: 3px solid var(--amber);
  border-radius: var(--r-md);
  background: color-mix(in oklch, var(--amber) 10%, var(--bg-elev));
  font-size: var(--t-sm);
}

/* ---------- first-data onboarding + SDK setup code (M4-0183) ---------- */

.onboard {
  border: 1px solid var(--line-strong);
  border-radius: var(--r-lg);
  background: var(--bg-elev);
  padding: var(--s-6) var(--s-7);
  margin-bottom: var(--s-7);
}
.onboard > * + * { margin-top: var(--s-4); }
.onboard-head { display: flex; align-items: center; flex-wrap: wrap; gap: var(--s-4); }
.onboard-ok { border-color: color-mix(in oklch, var(--green) 40%, var(--line)); }
.onboard-link { margin-left: auto; }
/* Design v2's onboarding row (Keys.dc.html): a mark, the words, the actions. */
.onboard-row { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: var(--s-6); align-items: center; }
.onboard-title { font-weight: 600; margin-top: 2px; }
.onboard-text { margin-top: 4px; max-width: 70ch; }
.onboard-pulse { width: 38px; height: 38px; border-radius: 50%; border: 2px solid var(--honey); display: grid; place-items: center; }
.onboard-pulse::after { content: ""; width: 12px; height: 12px; border-radius: 50%; background: var(--honey); }
.onboard-pulse-off { border-color: var(--ink-4); }
.onboard-pulse-off::after { background: var(--ink-4); }
.onboard-check { width: 38px; height: 38px; border-radius: 50%; border: 2px solid var(--green); position: relative; }
.onboard-check::after { content: ""; position: absolute; left: 13px; top: 8px; width: 8px; height: 14px; border: solid var(--green); border-width: 0 2px 2px 0; transform: rotate(45deg); }
.onboard-setup[hidden] { display: none; }
@media (max-width: 640px) { .onboard-row { grid-template-columns: minmax(0, 1fr); } .onboard-pulse, .onboard-check { display: none; } }

/* Keys on the v3 board (M4-0383, D129): each list is a card with a one-sentence
   head, its table flush inside, and its revoked keys collapsed under it. */
.keys-top { display: flex; justify-content: flex-end; margin-bottom: var(--s-4); }
.keys-card { padding: 0; overflow: hidden; }
.keys-card + .keys-card { margin-top: var(--s-7); }
.keys-card-head {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--s-5); padding: var(--s-6) var(--s-6) var(--s-5);
}
.keys-card-head p { margin-top: 2px; }
.keys-card-note { padding: 0 var(--s-6) var(--s-5); }
/* One suspended notice above both key cards (M4-0415, G4-15). */
.keys-suspended { margin-bottom: var(--s-5); }
.keys-card .table-wrap { border: 0; border-top: 1px solid var(--line); border-radius: 0; }
.keys-card > div > .empty, .keys-card > div > .errorbox { margin: 0 var(--s-6) var(--s-6); }
.keys-card > div > .notice { margin: 0 var(--s-6) var(--s-5); }
.keys-card .footnote { margin: 0; padding: var(--s-4) var(--s-6) var(--s-5); border-top: 1px solid var(--line); max-width: none; }
.key-prefix { display: inline-flex; align-items: center; gap: var(--s-2); }
.key-prefix .key-copy-btn { padding: 4px; min-width: 32px; min-height: 32px; }
.key-prefix .key-copy-btn svg { width: 13px; height: 13px; }
.table tbody.keys-revoked[hidden] { display: none; }
.keys-revoked-foot { border-top: 1px solid var(--line); padding: var(--s-4) var(--s-6); }
.keys-revoked-toggle {
  display: inline-flex; align-items: center; gap: var(--s-3);
  border: 0; background: none; padding: 2px 0; cursor: pointer;
  font: inherit; font-size: var(--t-sm); color: var(--ink-3);
}
.keys-revoked-toggle:hover { color: var(--ink); }
.keys-revoked-chev { width: 0; height: 0; border-left: 5px solid currentColor; border-top: 4px solid transparent; border-bottom: 4px solid transparent; transition: transform var(--dur-fast) var(--ease); }
.keys-revoked-toggle[aria-expanded="true"] .keys-revoked-chev { transform: rotate(90deg); }
.keys-revoked tr td { color: var(--ink-3); }
/* v3 Keys (D133): a revoked key's mark in its Key cell, and the row ⋯ menu. */
.key-cell { display: inline-flex; align-items: center; gap: var(--s-3); flex-wrap: wrap; }
.key-revoked-tag { color: var(--red); white-space: nowrap; }
.row-menu { display: inline-flex; justify-content: flex-end; }
.row-menu-btn { font-size: 16px; line-height: 1; padding: 4px 8px; min-width: 32px; min-height: 32px; }
.row-menu-btn[aria-expanded="true"] { background: color-mix(in oklch, var(--ink) 8%, transparent); color: var(--ink); }
.row-menu-list {
  position: fixed; z-index: 40; min-width: 168px;
  display: flex; flex-direction: column; padding: var(--s-1);
  background: var(--bg-elev); border: 1px solid var(--line-strong); border-radius: var(--r-md);
  box-shadow: 0 8px 24px color-mix(in oklch, var(--ink) 14%, transparent);
}
.row-menu-list[hidden] { display: none; }
.row-menu-item {
  text-align: left; background: none; border: 0; border-radius: var(--r-sm);
  min-height: 32px; padding: var(--s-2) var(--s-3); font: inherit; font-size: var(--t-sm); color: var(--ink); cursor: pointer;
}
.row-menu-item:hover, .row-menu-item:focus-visible { background: color-mix(in oklch, var(--ink) 6%, transparent); }
.row-menu-item:focus-visible { outline: 2px solid var(--accent-text); outline-offset: -2px; }
.row-menu-item.danger { color: var(--red); }
/* M4-0448 (D157): Issue key alone in the card head; the name is asked in its dialog. */
.keys-issue { display: flex; flex-direction: column; align-items: flex-end; }
/* M4-0420 (G4-20): Select all read / Clear above the scopes. */
.scope-presets { margin-bottom: var(--s-3); }
.key-name { overflow-wrap: anywhere; }
/* M4-0365: server keys with scopes; the picker is the region picker's pattern (M4-0272). */
fieldset.scope-field { border: 0; padding: 0; margin-inline: 0; min-width: 0; }
fieldset.scope-field > legend { padding: 0; }
.scope-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-2); }
.scope-option {
  display: flex; align-items: flex-start; gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  background: var(--bg-elev);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.scope-option:hover { border-color: var(--accent-text); }
.scope-option:has(input:checked) { border-color: var(--accent-text); background: var(--bg-sunk); }
.scope-option:has(input:focus-visible) { box-shadow: var(--focus-ring); }
.scope-option input { margin-top: 3px; accent-color: var(--accent-text); flex: none; }
.scope-option > span { display: grid; gap: 2px; min-width: 0; }
.scope-name { font-size: var(--t-sm); color: var(--ink); }
@media (max-width: 640px) { .scope-grid { grid-template-columns: minmax(0, 1fr); } }
/* M4-0257: platform, model and SDK under a device's install id. */
.device-meta { margin-top: 2px; overflow-wrap: anywhere; }

.code-switch { min-width: 0; }
.code-switch > * + * { margin-top: var(--s-3); }
.code-switch-head { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: var(--s-4); }
/* M4-0420 (G4-18): each language's versioned install line. M4-0448: the label,
   the line and its Copy on one row, Copy at the row's end (no wrap: the line
   itself wraps, overflow-wrap: anywhere). */
.code-install { display: flex; align-items: center; gap: var(--s-3); flex-wrap: nowrap; min-width: 0; }
.code-install > .btn { flex: none; margin-left: auto; }
.code-install-line { font-size: 12px; padding: 4px 8px; border-radius: var(--r-sm); background: var(--bg-sunk); border: 1px solid var(--line); overflow-wrap: anywhere; min-width: 0; flex: 1 1 auto; }
.code-install-label { flex: none; }
.lang-tabs {
  display: inline-flex; flex-wrap: wrap; gap: var(--s-2);
  padding: var(--s-2); border-radius: var(--r-md); background: var(--bg-sunk);
}
.lang-tab {
  font: inherit; font-size: var(--t-sm); color: var(--ink-3);
  background: transparent; border: 0; border-radius: var(--r-sm);
  min-height: 32px; padding: var(--s-2) var(--s-4); cursor: pointer;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.lang-tab:hover { color: var(--ink); }
.lang-tab:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.lang-tab-on { background: var(--bg-elev); color: var(--ink); box-shadow: 0 0 0 1px var(--line); }
/* M4-0448: the snippet wraps rather than scrolling sideways. */
.code-block {
  display: block; margin: 0;
  white-space: pre-wrap; overflow-wrap: anywhere; line-height: 1.6;
  font-family: var(--f-mono); font-size: var(--t-sm);
  background: var(--bg-sunk);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  padding: var(--s-4) var(--s-5);
}

/* ---------- toast ---------- */

/* M4-0408 (G5-08): bottom-left, as the States board draws it. Two regions,
   made at shell mount: the alert region (errors with no other home) over the
   status region (what worked). */
.toasts {
  position: fixed; bottom: var(--s-7); left: var(--s-7); z-index: 80;
  display: flex; flex-direction: column; gap: var(--s-3);
  max-width: min(380px, calc(100vw - 2 * var(--s-7)));
  pointer-events: none;
}
.toast-region { display: flex; flex-direction: column; gap: var(--s-3); }
.toast-region:empty { display: none; }
.toast { pointer-events: auto; }
.toast-msg { flex: 1; min-width: 0; padding-top: 2px; }
.toast .toast-close { flex: none; margin: -6px -10px -6px 0; color: var(--ink-3); }
.toast {
  display: flex; gap: var(--s-4); align-items: flex-start;
  background: var(--bg-elev);
  border: 1px solid var(--line-strong);
  border-left: 3px solid var(--ink-3);
  border-radius: var(--r-md);
  padding: var(--s-5) var(--s-6);
  font-size: var(--t-sm);
  box-shadow: 0 12px 32px -18px color-mix(in oklch, oklch(15% 0.02 60) 70%, transparent);
}
.toast-ok { border-left-color: var(--green); }
.toast-bad { border-left-color: var(--red); }

/* ---------- login ---------- */

.auth-wrap { min-height: 100vh; display: grid; place-items: center; padding: var(--s-7); }
.auth-card { width: 100%; max-width: 380px; }
.auth-brand { display: flex; align-items: center; gap: var(--s-4); margin-bottom: var(--s-8); }
.auth-brand svg { width: 30px; height: 30px; }
.auth-sep {
  display: flex; align-items: center; gap: var(--s-4);
  margin: var(--s-6) 0;
  color: var(--ink-4); font-size: var(--t-micro);
  font-family: var(--f-mono); letter-spacing: 0.08em; text-transform: uppercase;
}
.auth-sep::before, .auth-sep::after { content: ""; height: 1px; background: var(--line); flex: 1; }
/* Why Login is showing, when the console knows (views/login.js). */
.auth-message { margin-top: var(--s-6); }
/* M4-0418 (K11, G1-11): the refused sign-in band above the fields, and the
   forgotten-password line under the form. */
.auth-error { margin: 0 0 var(--s-5); }
.auth-forgot { margin-top: var(--s-6); }
/* Design v2's Login (M4-0272, Login.dc.html): the sign-in pane beside an
   illustrated one, which goes below 900 px. */
.auth-split { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); place-items: stretch; padding: 0; }
.auth-pane { display: flex; align-items: center; justify-content: center; padding: var(--s-8) var(--s-7); min-width: 0; }
.auth-body { margin-top: var(--s-8); }
.auth-google { justify-content: center; gap: 10px; }
.auth-google svg { flex: none; }
.auth-google-block .auth-sep { margin: var(--s-6) 0; text-transform: none; letter-spacing: 0; font-family: var(--f-body); }
.auth-art { position: relative; overflow: hidden; background: var(--bg-sunk); border-left: 1px solid var(--line); min-height: 100vh; }
.auth-art-map { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.auth-art-roads path { stroke: var(--bg-elev); stroke-width: 10; fill: none; }
.auth-art-water { fill: color-mix(in oklch, var(--map-track) 14%, var(--bg-sunk)); }
.auth-art-track { stroke: var(--map-fence); stroke-width: 4; fill: none; stroke-linecap: round; }
.auth-art-halo { fill: var(--map-fence); fill-opacity: 0.16; }
.auth-art-pin { fill: var(--map-fence); stroke: var(--bg-elev); stroke-width: 3; }
.auth-art-fence { fill: var(--map-track); fill-opacity: 0.08; stroke: var(--map-track); stroke-width: 2; stroke-dasharray: 6 5; }
.auth-art-copy { position: absolute; left: 40px; right: 40px; bottom: 40px; max-width: 420px; }
.auth-art-title { font-family: var(--f-display); font-size: 22px; letter-spacing: -0.02em; font-weight: 600; color: var(--ink); }
.auth-art-sub { color: var(--ink-2); margin-top: 6px; }
@media (max-width: 900px) {
  .auth-split { grid-template-columns: minmax(0, 1fr); }
  .auth-art { display: none; }
  .auth-pane { padding: var(--s-7) var(--s-5); }
}
.btn-block { width: 100%; }

/* ---------- map surface ---------- */

.map-wrap {
  position: relative;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--bg-sunk);
  min-height: 420px;
}
/* The basemap is still loading (M4-0443): the grid is drawn, under a slow,
   quiet shimmer, rather than an empty flat area or the no-basemap note. */
.map-wrap.is-basemap-loading::after {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: linear-gradient(100deg, transparent 35%, var(--skel-a) 50%, transparent 65%);
  background-size: 300% 100%;
  animation: shimmer 2.4s infinite linear;
}
@media (prefers-reduced-motion: reduce) { .map-wrap.is-basemap-loading::after { animation: none; background: var(--skel-a); opacity: 0.5; } }
/* The Map's page before the project is read (tab-map.js mapSkeleton). */
.map-bar .skel-tab { height: 30px; width: 140px; }
.map-panel .skel-heading { width: 60%; }
.map-panel-head > .skel + .skel { margin-top: 10px; }
.map-bar .skel-tabs { padding: 0; border: 0; }
/* The fence overlay only: an icon placed in the map keeps its own size (0208). */
.map-wrap svg.fence-canvas { display: block; width: 100%; height: 100%; position: absolute; inset: 0; }
.map-note {
  position: absolute; left: var(--s-4); bottom: var(--s-4);
  font-family: var(--f-mono); font-size: var(--t-eyebrow);
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--ink-3);
  background: color-mix(in oklch, var(--bg-elev) 85%, transparent);
  padding: 3px 8px; border-radius: 4px;
}
.map-attr {
  position: absolute; right: var(--s-4); bottom: var(--s-4);
  font-size: var(--t-eyebrow); color: var(--ink-3);
  background: color-mix(in oklch, var(--bg-elev) 85%, transparent);
  padding: 2px 7px; border-radius: 4px;
}
.map-toolbar {
  position: absolute; left: var(--s-4); top: var(--s-4);
  display: flex; gap: var(--s-2);
  background: var(--bg-elev);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 3px;
}
.map-tool {
  font-family: var(--f-body); font-size: var(--t-xs);
  min-height: 32px; padding: 5px 10px; border-radius: var(--r-sm);
  border: 0; background: transparent; color: var(--ink-2); cursor: pointer;
}
.map-tool[aria-pressed="true"] { background: var(--accent-solid); color: var(--accent-solid-ink); }
.editor-grid { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: var(--s-6); align-items: start; }
/* A fence list entry is a button styled as its card, so the keyboard reaches it. */
.fence-entry { display: block; width: 100%; padding: 12px 14px; font: inherit; color: inherit; text-align: left; cursor: pointer; }
.fence-entry[aria-current="true"] { border-color: var(--honey); }
/* Brought into view when a draft appears or a fence opens (0205): clear of the
   sticky topbar above and the viewport's edge below. */
.fence-form { scroll-margin-top: calc(var(--topbar-h) + var(--s-5)); scroll-margin-bottom: var(--s-5); }

/* The Geofences screen as Design v2 draws it (M4-0269): the list with its search
   on the left, the map beside it, and the tools, zoom, hint and edit card
   floating on the map. The map is sized to the screen, so nothing scrolls. */
.fence-editor {
  --fence-map-h: clamp(480px, calc(100dvh - 275px), 1100px);
  grid-template-columns: 340px minmax(0, 1fr); align-items: start;
}
.fence-top { display: grid; grid-template-columns: 340px minmax(0, 1fr); gap: var(--s-6); align-items: center; margin-bottom: var(--s-4); }
.fence-about { margin: 0; }
.fence-side { display: flex; flex-direction: column; gap: var(--s-4); min-width: 0; max-height: var(--fence-map-h); }
.fence-side .section-head { margin: 0; }
.fence-search {
  display: flex; align-items: center; gap: var(--s-3);
  border: 1px solid var(--line); border-radius: var(--r-md); background: var(--bg-elev);
  padding: 0 var(--s-4); color: var(--ink-3);
}
.fence-search:focus-within { border-color: var(--accent-text); }
.fence-search:has(.input:focus-visible) { box-shadow: var(--focus-ring); }
.fence-search .input { border: 0; background: transparent; padding-left: 0; box-shadow: none; outline: none; min-width: 0; flex: 1; }
.fence-search:has(.input[hidden]) { visibility: hidden; }
.fence-list { overflow: auto; min-height: 0; flex: 1; }
.fence-entries { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s-3); }
.fence-entry {
  border: 1px solid var(--line); border-radius: var(--r-md); background: var(--bg-elev);
}
.fence-entry:hover { border-color: color-mix(in oklch, var(--honey) 50%, var(--line)); }
.fence-stage { position: relative; min-width: 0; }
.fence-stage .map-wrap { height: var(--fence-map-h); min-height: 0; }
.fence-stage .map-note { bottom: calc(var(--s-4) + 30px); }
.fence-card-host[hidden] { display: none; }
/* D133: the edit card takes the list's place in the side panel, under a link
   back to the list; it scrolls inside the panel, its footer sticky. */
.fence-card-host { min-height: 0; display: flex; flex-direction: column; }
.fence-side .fence-form { margin: 0; overflow: auto; min-height: 0; max-height: calc(var(--fence-map-h) - 40px); }
.fence-back {
  align-self: flex-start; background: none; border: 0; padding: 2px 0; cursor: pointer;
  font: inherit; font-size: var(--t-sm); color: var(--ink-2);
}
.fence-back:hover { color: var(--ink); text-decoration: underline; }
.fence-back:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: var(--r-sm); }
/* M4-0383 (the v3 board): the card's actions in a sticky footer, full bleed,
   so Create / Save and Cancel stay in view while the card scrolls; Delete
   sits on the left. A new fence's Circle / Polygon switch above its fields. */
.fence-form-foot {
  position: sticky; bottom: 0; z-index: 1;
  display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--s-3);
  margin: var(--s-6) calc(-1 * var(--s-7)) 0;
  padding: var(--s-5) var(--s-7);
  border-top: 1px solid var(--line); background: var(--bg-elev);
  border-radius: 0 0 var(--r-lg) var(--r-lg);
}
.fence-form-foot .btn-danger { margin-right: auto; }
/* M4-0418 (G3-31): a sticky box stays inside its scroller's content box, so a
   footer reaching into the card's bottom padding (a negative margin) was lifted
   by that padding at the card's end and covered the Entry and Exit row. The
   card with a footer has no bottom padding: the footer is the card's bottom
   edge, and the room under the last fields is the footer's own height. */
.fence-form:has(> .fence-form-foot) { padding-bottom: 0; }
.fence-shape { align-self: flex-start; }
/* M4-0418 (G3-35): the radius's under-100 m warning line. */
.field-warn { font-size: var(--t-micro); color: var(--ink-2); margin-top: var(--s-2); }
.map-wrap .fence-zoom { top: auto; transform: none; bottom: 60px; }
.fence-hint {
  position: absolute; z-index: 2; left: var(--s-4); bottom: var(--s-4); max-width: calc(100% - 200px);
  font-size: var(--t-xs); color: var(--ink-2);
  background: color-mix(in oklch, var(--bg-elev) 90%, transparent);
  border: 1px solid var(--line); border-radius: var(--r-md); padding: 5px 10px;
}
.fence-hint[hidden] { display: none; }
/* On a basemap the hint sits above Mapbox's logo, which its terms require to
   stay visible (M4-0443). */
.map-wrap:has(.mapboxgl-ctrl-logo) .fence-hint { bottom: calc(var(--s-4) + 32px); }
.fence-top .fence-search:has(> .skel) { min-height: 38px; }
.fence-measure {
  position: absolute; z-index: 2; pointer-events: none;
  font-family: var(--f-mono); font-size: var(--t-xs); color: var(--ink);
  background: var(--bg-elev); border: 1px solid var(--honey-3); border-radius: var(--r-sm);
  padding: 2px 7px; white-space: nowrap;
}
.fence-measure[hidden] { display: none; }
.map-tool { display: inline-flex; align-items: center; gap: 6px; }
.map-wrap svg.fence-canvas[data-tool="circle"], .map-wrap svg.fence-canvas[data-tool="polygon"] { cursor: crosshair; }
.map-wrap svg.fence-canvas[data-tool="select"] { cursor: grab; }
.map-wrap svg.fence-canvas.panning { cursor: grabbing; }
/* On a basemap with Select armed, the empty map is Mapbox's; shapes stay live. */
.map-wrap svg.fence-canvas.passthrough { pointer-events: none; }
.map-wrap svg.fence-canvas.passthrough [data-fence], .map-wrap svg.fence-canvas.passthrough [data-handle] { pointer-events: auto; }

/* A fence's name drawn on the map (G3-29). --ink-2 or better for contrast
   (axe stays green, 0407's AxeContrast); a halo keeps it legible over the
   honey fills and the grid. The overlay is aria-hidden, so this is decoration. */
.fence-name {
  font-family: var(--f-body); font-size: var(--t-xs); font-weight: 500;
  fill: var(--ink); paint-order: stroke;
  stroke: var(--bg); stroke-width: 3px; stroke-linejoin: round;
}
.fence-name-lit { fill: var(--honey-ink, var(--ink)); font-weight: 600; }
/* The minimum-size mark for a fence too small to see at the current zoom. */
.fence-mark { filter: drop-shadow(0 1px 2px color-mix(in oklch, oklch(20% 0 0) 40%, transparent)); }
/* Card ↔ shape: the lit shape/entry pair (G3-29). Driven by hoverId, not CSS
   :hover, which cannot cross the list/map boundary. */
.fence-entry-lit { border-color: var(--honey); background: color-mix(in oklch, var(--honey) 10%, var(--bg-elev)); }

/* G3-30 (Q6): the opened fence's id under the editor-head title. */
.fence-head { display: flex; flex-direction: column; gap: var(--s-2); }
.fence-head-id { display: flex; }

/* G3-38: when the grid is centred on the project's region the note is a
   sentence, not the uppercase label the no-basemap note uses — it reads as
   guidance ("check coordinates before saving"), and its case must survive to
   the accessible text. A touch wider than the plain note to fit the sentence. */
.map-note.map-note-region {
  text-transform: none; letter-spacing: normal;
  font-family: var(--f-body); font-size: var(--t-xs);
  max-width: calc(100% - 2 * var(--s-4));
}

/* ---------- charts ---------- */

.chart { width: 100%; display: block; overflow: visible; }
.chart-baseline { stroke: var(--line-strong, var(--line)); stroke-width: 1; }
.chart-grid { stroke: var(--line); stroke-width: 1; stroke-dasharray: 2 3; }
/* Axis labels are read: 11 px at least, in --ink-3 (M4-0407, G5-28, G3-03). */
.chart-label { font-family: var(--f-mono); font-size: var(--t-eyebrow); fill: var(--ink-3); letter-spacing: 0.04em; }
/* 0373: Usage charts stretch a fixed viewBox, so their labels are counter-scaled to the micro size (views/tab-usage.js fitLabels). */
.usage .chart-label { font-size: calc(max(var(--t-micro), var(--t-eyebrow)) * var(--chart-k, 1)); }
.spark { width: 72px; height: 20px; display: block; }
.spark path { fill: none; stroke: var(--honey); stroke-width: 1.5; stroke-linejoin: round; stroke-linecap: round; }

/* The Usage dashboard (M4-0294). Its series slots, --series-1/--series-2, are
   tokens.css's (M4-0407). Failures use the reserved --red, always with a word
   beside it. */

.usage { display: grid; gap: var(--s-6); }
.usage-filters { display: flex; flex-wrap: wrap; gap: var(--s-4); align-items: center; }
.usage-grid { display: grid; gap: var(--s-6); }
.usage-cap-skel { display: flex; flex-direction: column; gap: var(--s-4); margin-top: var(--s-4); }
.usage-cap-skel > .skel-stat-lg { margin-top: 0; height: 32px; }
/* M4-0417: Table view swaps the chart grid for the one table; [hidden] must win over display. */
.usage-grid[hidden], .usage-table-card[hidden] { display: none; }
/* Wholly empty (M4-0415): the one empty state stands in for the chart cards. */
.usage-whole:empty { display: none; }
/* A member reads remote config as values (M4-0415, G4-26). */
.config-read .config-value { display: block; margin-top: 4px; font-variant-numeric: tabular-nums; color: var(--ink); }
.usage-view { display: grid; gap: var(--s-6); }
.usage-card-head { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--s-5); margin-bottom: var(--s-4); }
.usage-card-summary { color: var(--ink-2); margin-bottom: var(--s-3); }
.usage-card-summary:empty { display: none; }
.usage-cap-row { display: flex; align-items: baseline; gap: var(--s-3); margin: var(--s-4) 0 var(--s-3); }
.usage-cap-note { margin-top: var(--s-3); color: var(--ink-2); }
.usage-cap-drops { margin-top: var(--s-2); color: var(--ink-2); }
.usage-foot { margin: 0; }
.usage-table { margin-top: var(--s-2); }
/* M4-0417 (G3-05): one day × metric table; its header stays while the page scrolls. */
.usage-table thead th { position: sticky; top: 0; z-index: 1; }
.usage-table tbody th { font-weight: 400; text-align: left; white-space: nowrap; }
.usage-table-lead th { font-style: italic; }
.usage-cap-month { margin-top: 2px; color: var(--ink-2); }
.usage-cap-asof { margin: 0 0 var(--s-3); }
.usage-cap-provisional { font-weight: 600; color: var(--ink-2); }
.usage-cap-prev { margin-top: var(--s-3); color: var(--ink-2); }
.usage-cap-prev:empty { display: none; }
/* M4-0383 (the v3 Usage board): why points were filtered, beside the key table. */
.usage-pair { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr); gap: var(--s-6); align-items: start; }
.usage-why { display: flex; flex-direction: column; gap: var(--s-5); }
.why-list { display: flex; flex-direction: column; gap: var(--s-5); }
.why-row { display: flex; flex-direction: column; gap: 6px; }
.why-head { display: flex; justify-content: space-between; gap: var(--s-4); font-size: var(--t-sm); }
.why-value { color: var(--ink-2); }
.why-track { height: 10px; border-radius: 4px; background: var(--bg-sunk); overflow: hidden; }
.why-fill { height: 100%; background: var(--series-2); border-radius: 0 4px 4px 0; }
.usage-why-link { align-self: flex-start; }
/* 1240 = the 1000 px the pair needs plus the 232 px sidebar beside it (M4-0445:
   at a 1024 px window the key table stuck out 10 px and scrolled the page). */
@media (max-width: 1240px) { .usage-pair { grid-template-columns: minmax(0, 1fr); } }
.chart .mark.s1 { fill: var(--series-1); }
.chart .mark.s2 { fill: var(--series-2); }
.chart .mark.bad { fill: var(--red); }
.chart .chart-dot { stroke: var(--bg-elev); stroke-width: 2; }
.chart-line { fill: none; stroke: var(--series-1); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.chart-cap { stroke: var(--ink-3); stroke-width: 1; stroke-dasharray: 4 3; }
.chart-hit { fill: transparent; }
.chart g.day { outline: none; }
.chart g.day:hover .chart-hit, .chart g.day:focus .chart-hit { fill: color-mix(in oklch, var(--ink-3) 10%, transparent); }
/* A focused day has a visible stroke, not only the 10% wash (1.14:1, G5-03). */
.chart g.day:focus-visible .chart-hit { stroke: var(--accent-text); stroke-width: 2; }
/* M4-0417 (G3-02): the chart is one Tab stop; ←/→ move the highlighted day,
   which carries the same 2 px stroke. */
.chart:focus { outline: none; }
.chart:focus-visible g.day.active .chart-hit { fill: color-mix(in oklch, var(--ink-3) 10%, transparent); stroke: var(--accent-text); stroke-width: 2; }
.chart:focus-visible:not(:has(g.day)) { outline: 2px solid var(--accent-text); outline-offset: 2px; }
/* Today is partial (M4-0417, G3-11): hatched in its series colour, not faded. */
.chart .hatch-bg { opacity: 0.3; }
.chart .hatch-bg.s1, .chart .hatch-line.s1 { fill: var(--series-1); }
.chart .hatch-bg.s2, .chart .hatch-line.s2 { fill: var(--series-2); }
.chart .hatch-bg.bad, .chart .hatch-line.bad { fill: var(--red); }
.chart .chart-dot[style] { stroke: var(--series-1); stroke-width: 1.5; }
/* The readout is always there (G3-10): two lines reserved, so showing a day never moves the card. */
.chart-tip { min-height: 2lh; color: var(--ink-2); margin-top: var(--s-2); }
.chart-tip.rest { color: var(--ink-3); }
.chart-legend { display: flex; gap: var(--s-4); color: var(--ink-2); }
.chart-key { display: inline-flex; align-items: center; gap: 6px; }
.chart-swatch { width: 10px; height: 10px; border-radius: 2px; display: inline-block; }
.chart-swatch.s1 { background: var(--series-1); }
.chart-swatch.s2 { background: var(--series-2); }
.chart-swatch.today { background: repeating-linear-gradient(135deg, var(--series-1) 0 2px, color-mix(in oklch, var(--series-1) 30%, transparent) 2px 4px); }

.meter { height: 8px; border-radius: var(--r-pill); background: var(--bg-sunk); overflow: hidden; border: 1px solid var(--line); }
.meter-fill { height: 100%; background: var(--honey); border-radius: var(--r-pill); }
.meter-fill.warn { background: var(--amber); }
.meter-fill.over { background: var(--red); }

/* ---------- misc layout ---------- */

.stack > * + * { margin-top: var(--s-5); }
.stack-lg > * + * { margin-top: var(--s-7); }
.row { display: flex; align-items: center; gap: var(--s-4); }
.row-between { display: flex; align-items: center; justify-content: space-between; gap: var(--s-5); }
.spacer { flex: 1; }
.wrap-gap { display: flex; flex-wrap: wrap; gap: var(--s-3); }

.menu-btn { display: none; }
.nav-scrim { display: none; }

/* ---------- responsive ---------- */

/* Console v3 (M4-0377) has no icon rail: the sidebar carries the project
   switcher and the group names, so it keeps its width down to the phone
   breakpoint, where it becomes the drawer. */
@media (max-width: 1200px) {
  .cols-4 { grid-template-columns: 1fr 1fr; }
  .cols-3 { grid-template-columns: 1fr 1fr; }
  .editor-grid { grid-template-columns: 1fr; }
  .fence-side { max-height: none; }
  .fence-top { grid-template-columns: minmax(0, 1fr); }
  .fence-list { max-height: 260px; }
}

/* A phone: the map comes first, then the list and the card below it (G3-39,
   Q2/D116 — map first in both the list and the editing state), and the map's
   overlays sit in clear, non-overlapping bands. */
@media (max-width: 767.98px) {
  .fence-editor { --fence-map-h: 420px; }
  /* The map (.fence-stage) before the list and the edit card (.fence-side holds
     both the list and, open, the card): one rule, both states (Q2). */
  .fence-editor .fence-stage { order: -1; }
  .fence-editor .fence-side { order: 0; }
  .fence-side .fence-form { max-height: none; }
  /* The hint and the no-basemap note stack bottom-left, kept clear of the
     scale bar at the bottom-right: each is narrow enough never to reach it,
     and they sit in two bands that do not touch. */
  .fence-hint {
    left: var(--s-4); right: auto; bottom: calc(var(--s-4) + 64px);
    max-width: calc(100% - 160px);
  }
  .map-wrap:has(.mapboxgl-ctrl-logo) .fence-hint { bottom: calc(var(--s-4) + 64px); }
  .fence-stage .map-note { left: var(--s-4); right: auto; bottom: var(--s-4); max-width: calc(100% - 160px); }
  .map-wrap .fence-zoom { bottom: calc(var(--s-4) + 108px); }
}

@media (max-width: 767.98px) {
  /* minmax(0, 1fr), not a bare 1fr: a bare 1fr track is sized by its content's
     min-content, so a wide page body (the device page's Points table in its
     .table-scroll) would stretch the one column past the viewport and push the
     topbar sideways. minmax(0, …) lets the column shrink to the viewport and
     the body's own wrap/scroll take the overflow (M4-0381, Contract-change:
     D131 — the v3 device page is the first phone body wide enough to show it). */
  .shell { grid-template-columns: minmax(0, 1fr); }
  .sidebar {
    position: fixed; inset: 0 auto 0 0; z-index: 50;
    width: 260px; height: 100vh;
    transform: translateX(-100%);
    /* Closed, the drawer is hidden as well as off-screen, so its links leave the
       tab order; visibility flips only once the slide-out has finished. */
    visibility: hidden;
    transition: transform var(--dur) var(--ease), visibility 0s linear var(--dur);
    box-shadow: 0 0 60px -20px color-mix(in oklch, oklch(15% 0.02 60) 60%, transparent);
  }
  .shell[data-nav="open"] .sidebar {
    transform: translateX(0);
    visibility: visible;
    transition: transform var(--dur) var(--ease), visibility 0s;
  }
  :root { --sidebar-w: 0px; }
  .nav-item { padding: var(--s-3) var(--s-4); }
  .menu-btn { display: inline-flex; }
  .shell[data-nav="open"] .nav-scrim { display: block; position: fixed; inset: 0; z-index: 40; background: color-mix(in oklch, oklch(15% 0.02 60) 45%, transparent); }

  .topbar { padding: 0 var(--s-6); gap: var(--s-4); }
  .topbar > .spacer { display: none; }
  /* G1-34 (packet Q9): the project's name stays, as one truncated line above
     the page's name, so a phone always says which project it is on. */
  .crumbs { flex-direction: column; align-items: flex-start; justify-content: center; gap: 0; line-height: 1.25; }
  .crumb-scope { font-size: var(--t-micro); max-width: 100%; }
  .crumb-sep { display: none; }
  .crumb-page { max-width: 100%; }
  .account-btn { height: 44px; }
  /* The drawer's close button (G1-32): a 44 px target at its top. */
  .sidebar-head > .drawer-close.btn-icon { display: inline-flex; align-items: center; justify-content: center; min-width: 44px; min-height: 44px; margin-left: var(--s-3); }
  .shell-boot .boot-side { display: none; }
  .content { padding: var(--s-7) var(--s-6) var(--s-9); }
  .cols-2, .cols-3, .cols-4 { grid-template-columns: 1fr; }
  .field-row { grid-template-columns: 1fr; }
  .page-head-row { align-items: flex-start; }

  /* Tables reflow to stacked cards — the page body must never scroll sideways. */
  .table, .table tbody, .table tr, .table td { display: block; width: 100%; }
  .table thead { display: none; }
  .table tr { padding: var(--s-5) var(--s-6); border-bottom: 1px solid var(--line); }
  .table tr:last-child { border-bottom: none; }
  .table td { border: none; padding: var(--s-2) 0; text-align: left; }
  .table td::before {
    content: attr(data-label);
    display: block;
    font-family: var(--f-mono); font-size: var(--t-eyebrow);
    letter-spacing: var(--tr-eyebrow); text-transform: uppercase;
    color: var(--ink-3); margin-bottom: 2px;
  }
  .table td:empty { display: none; }
  .table .cell-actions, .table .cell-num { text-align: left; }
  .table .cell-actions { padding-top: var(--s-4); }
  /* The Activity table keeps its columns and scrolls inside its own wrap. */
  .table-scroll .table { display: table; width: 100%; }
  .table-scroll .table thead { display: table-header-group; }
  .table-scroll .table tbody { display: table-row-group; width: auto; }
  .table-scroll .table tr { display: table-row; width: auto; padding: 0; }
  .table-scroll .table td { display: table-cell; width: auto; padding: 9px 8px; border-bottom: 1px solid var(--line); }
  .table-scroll .table td::before { content: none; }
  .table-scroll .table .cell-num { text-align: right; }
  .activity-controls > .spacer { display: none; }

  .btn { min-height: 40px; }
  .btn-sm { min-height: 34px; }
  .toasts { left: var(--s-5); right: var(--s-5); bottom: var(--s-5); max-width: none; }
}

/* ---------- map view (M4-0182) ---------- */

/* The map's paint colours (--map-*) and the chart series slots are tokens.css's
   since M4-0407 (audit G5-33): sRGB there, because mapbox-gl cannot parse oklch(). */

/* Design v2's Map (M4-0277): the map is the page under the topbar, edge to
   edge, and its chrome floats over it (Map.dc.html; the kit's v2-float,
   v2-map-bar, v2-map-panel, v2-card, v2-zoom, v2-fresh, v2-legend). */
.content-bleed { padding: 0; }
.map-stage { position: relative; height: calc(100vh - var(--topbar-h)); height: calc(100dvh - var(--topbar-h)); min-height: 520px; overflow: hidden; }
.map-stage > .map-wrap { position: absolute; inset: 0; border: 0; border-radius: 0; min-height: 0; }
.map-float {
  position: absolute; z-index: 2;
  background: var(--bg-elev); border: 1px solid var(--line); border-radius: var(--r-lg);
  box-shadow: 0 6px 24px color-mix(in oklch, var(--ink) 10%, transparent);
}
.map-bar { top: 16px; left: 16px; right: 16px; display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-5); padding: 8px 10px; }
.map-bar .seg { flex-wrap: wrap; max-width: 100%; }
.map-bar .spacer { flex: 1; }
/* Custom…'s From / To pair, a row of its own under the Time window group (M4-0416, G2-16). */
.map-custom { flex-basis: 100%; display: flex; flex-wrap: wrap; gap: var(--s-3) var(--s-5); min-width: 0; }
.map-custom[hidden] { display: none; }
.map-custom-ctl { display: inline-flex; align-items: center; gap: var(--s-2); min-width: 0; max-width: 100%; }
.map-custom-ctl .field-label { margin-bottom: 0; }
/* --map-bar-b is the bar's real bottom edge plus the gap (tab-map.js
   syncBarEdge, M4-0443): the bar wraps to two rows when Custom… shows From /
   To or the window is narrow, and nothing under it may cover it. */
.map-panel { top: var(--map-bar-b, 76px); left: 16px; bottom: 60px; width: 288px; display: flex; flex-direction: column; overflow: hidden; }
.map-panel-head { padding: 14px 16px 10px; border-bottom: 1px solid var(--line); }
.map-panel-head .h3 { font-weight: 600; margin: 0; }
.map-count { margin-top: 3px; }
.map-search { margin-top: 10px; padding-top: 6px; padding-bottom: 6px; font-size: var(--t-sm); width: 100%; }
.map-panel-filter { padding: 8px 16px; border-bottom: 1px solid var(--line); }
.map-panel-filter[hidden] { display: none; }
.map-cap { display: flex; flex-direction: column; align-items: flex-start; gap: var(--s-3); padding: 10px 16px; border-bottom: 1px solid var(--line); }
.map-cap[hidden] { display: none; }
.map-dev-list { display: flex; flex-direction: column; flex: 1; min-height: 0; overflow-y: auto; }
.map-dev {
  display: grid; grid-template-columns: 10px minmax(0, 1fr) auto; gap: 10px; align-items: center;
  padding: 11px 16px; border: 0; border-bottom: 1px solid var(--line); background: transparent;
  color: var(--ink); font: inherit; text-align: left; cursor: pointer; min-width: 0;
}
.map-dev:hover { background: color-mix(in oklch, var(--ink) 3%, transparent); }
.map-dev[aria-current="true"] { background: color-mix(in oklch, var(--honey) 12%, transparent); }
.map-dev-id { display: flex; flex-direction: column; min-width: 0; overflow-wrap: anywhere; }
.map-swatch { width: 10px; height: 10px; border-radius: 50%; }
.map-none { padding: 11px 16px; margin: 0; overflow-wrap: anywhere; }
.map-panel-foot { padding: 12px 16px; border-top: 1px solid var(--line); }
.map-card { top: var(--map-bar-b, 76px); right: 16px; width: 300px; padding: 16px; max-height: calc(100% - var(--map-bar-b, 76px) - 168px); overflow-y: auto; min-width: 0; }
.map-card.is-empty { visibility: hidden; }
.map-card-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
.map-card-id { font-size: 12.5px; font-weight: 400; margin: 0; padding-top: 6px; min-width: 0; overflow-wrap: anywhere; }
.map-card-head .btn-icon { flex: none; margin: -4px -6px 0 0; }
.map-card-filtered { color: var(--red); margin-top: var(--s-2); }
.kv { display: grid; grid-template-columns: auto 1fr; gap: 6px 14px; font-size: var(--t-xs); margin-top: 12px; }
.kv dt { color: var(--ink-3); }
.kv dd { margin: 0; text-align: right; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
/* The no-basemap note and the empty state sit between the panel and the card. */
.map-basemap-note { top: var(--map-bar-b, 76px); left: 332px; right: 332px; margin-inline: auto; width: fit-content; max-width: 560px; padding: 12px 16px; display: flex; gap: 12px; align-items: flex-start; }
.map-basemap-note > svg { color: var(--ink-3); flex: none; margin-top: 1px; }
/* Its Dismiss (M4-0416, G2-42): icon-only, so the note's words are unchanged. */
.map-basemap-note .map-note-close { flex: none; margin: -6px -8px -6px 0; min-width: 28px; min-height: 28px; padding: 6px; }
.map-basemap-note .map-note-close svg { width: 14px; height: 14px; }
/* M4-0415 (G2-20): the empty state is centred in the part of the map the chrome
   leaves clear (tab-map.js placeEmpty sets left/top), never under the freshness
   chip; it carries the project-state rule's box, drawn compact over the map. */
.map-empty { left: 50%; top: 50%; transform: translate(-50%, -50%); width: max-content; max-width: min(420px, calc(100% - 32px)); padding: 0; font-size: var(--t-sm); }
.map-empty[hidden] { display: none; }
.map-empty .empty { border: 0; background: transparent; padding: var(--s-5) var(--s-6); }
.map-empty .empty-title { font-size: var(--t-h3); }
.map-empty .empty-action { margin-top: var(--s-4); }
.map-zoom { right: 16px; bottom: 84px; display: flex; flex-direction: column; overflow: hidden; }
.map-zoom button { width: 36px; height: 36px; border: 0; background: transparent; color: var(--ink-2); font: inherit; font-size: 18px; cursor: pointer; }
.map-zoom button + button { border-top: 1px solid var(--line); }
.map-zoom button svg { display: block; margin: auto; }
/* The grid drags to pan (M4-0416, G2-14 / G3-17). */
.map-view .map-wrap svg.fence-canvas, .trip-map svg.fence-canvas { cursor: grab; }
.map-view .map-wrap svg.fence-canvas.panning, .trip-map svg.fence-canvas.panning { cursor: grabbing; }
.map-fresh { left: 16px; bottom: 16px; padding: 7px 12px; font-size: var(--t-micro); color: var(--ink-2); display: flex; gap: 10px; align-items: center; border-radius: var(--r-pill); max-width: calc(100% - 32px - 400px); }
.map-fresh:has(.map-tiles-off) { flex-wrap: wrap; row-gap: 6px; border-radius: var(--r-lg); }
.map-fresh .map-tiles-off { flex-basis: 100%; }
/* The two-line chip is taller: the panel above it gives way. */
.map-stage:has(.map-tiles-off) > .map-panel { bottom: 84px; }
.map-legend { right: 16px; bottom: 16px; margin: 0; padding: 9px 14px; list-style: none; font-size: var(--t-micro); color: var(--ink-2); display: flex; gap: 14px; align-items: center; border-radius: var(--r-pill); }
.map-legend li { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
/* On a basemap Mapbox's own logo and attribution stay visible above the
   floating chips; the view's duplicate attribution line is not needed here. */
.map-stage .map-attr { display: none; }
.map-stage .mapboxgl-ctrl-bottom-left, .map-stage .mapboxgl-ctrl-bottom-right { bottom: 50px; }
.map-stage .mapboxgl-ctrl-bottom-left { left: 312px; }
/* A phone stacks the chrome under the map instead of over it. */
@media (max-width: 900px) {
  .map-stage { height: auto; min-height: 0; overflow: visible; display: flex; flex-direction: column; gap: var(--s-4); padding: var(--s-5); }
  .map-stage > .map-wrap { position: relative; inset: auto; height: 420px; border: 1px solid var(--line); border-radius: var(--r-lg); order: 2; }
  .map-stage > .map-float { position: static; width: auto; max-width: none; max-height: none; box-shadow: none; }
  .map-stage > .map-bar { order: 1; }
  .map-stage > .map-fresh { order: 3; border-radius: var(--r-lg); flex-wrap: wrap; }
  .map-stage > .map-legend { order: 4; flex-wrap: wrap; border-radius: var(--r-lg); }
  .map-stage > .map-card { order: 5; }
  .map-stage > .map-card.is-empty { display: none; }
  .map-stage > .map-panel { order: 6; }
  .map-basemap-note { left: var(--s-4); right: var(--s-4); }
  .map-basemap-note { top: var(--s-4); }
  .map-zoom { bottom: var(--s-4); }
  .map-stage .mapboxgl-ctrl-bottom-left, .map-stage .mapboxgl-ctrl-bottom-right { bottom: 0; }
  .map-stage .mapboxgl-ctrl-bottom-left { left: 0; }
}

/* ---------- one device's page (M4-0293) ---------- */

/* The install id is the title: device-made and up to 128 bytes, so it breaks
   anywhere rather than widening the page. */
.device-page .device-title { min-width: 0; flex: 1 1 320px; }
.device-page .device-id { font-size: clamp(18px, 2.4vw, 26px); overflow-wrap: anywhere; word-break: break-word; }
.device-page .device-links { flex-wrap: wrap; }
/* The head's actions (Open on map · Upload queued points now · Request logs)
   wrap rather than running past a narrow viewport (M4-0381; the three buttons
   are ~450 px in a row, wider than a 390 px phone). */
.device-page .device-actions { flex-wrap: wrap; }
.device-tiles { margin-bottom: var(--s-8); }
.device-tiles .stat-value { overflow-wrap: anywhere; }
.device-card .set-aside { margin-bottom: var(--s-4); }
.device-grid { display: grid; grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); gap: var(--s-7); margin-bottom: var(--s-7); }
.device-grid > section.device-card + section.device-card { margin-top: 0; }
.device-page > div > section.device-card { margin-top: var(--s-7); }
.device-card { min-width: 0; }
.device-card .card-head { flex-wrap: wrap; }

/* Last 24 hours: the track drawn from the points (no basemap). */
.minimap-wrap { background: var(--bg-sunk); border: 1px solid var(--line); border-radius: var(--r-md); overflow: hidden; }
.minimap { display: block; width: 100%; height: auto; max-height: 300px; }
.minimap .mm-track { fill: none; stroke: var(--ink-3); stroke-width: 1.5; stroke-linejoin: round; stroke-linecap: round; }
.minimap .mm-point { fill: var(--ink-2); }
.minimap .mm-gated { fill: none; stroke: var(--red); stroke-width: 1.5; }
.minimap .mm-halo { fill: color-mix(in oklch, var(--honey) 22%, transparent); stroke: color-mix(in oklch, var(--honey) 60%, transparent); stroke-width: 1; }
.minimap .mm-newest { fill: var(--honey); stroke: var(--bg-elev); stroke-width: 2; }
.minimap-legend { display: flex; flex-wrap: wrap; gap: var(--s-3) var(--s-6); margin-top: var(--s-4); }
.minimap-legend > span { display: inline-flex; align-items: center; gap: 6px; }
.mm-key { display: inline-block; width: 10px; height: 10px; border-radius: 50%; }
.mm-key-track { height: 2px; width: 14px; border-radius: 1px; background: var(--ink-3); }
.mm-key-gated { border: 1.5px solid var(--red); }
.mm-key-newest { background: var(--honey); }
.minimap-foot { margin-top: var(--s-3); }

/* Device's own report: one fact a line, the value right. */
.report-rows { display: grid; gap: var(--s-3); }
.report-row { display: flex; justify-content: space-between; align-items: baseline; gap: var(--s-5); font-size: var(--t-sm); border-bottom: 1px dashed var(--line); padding-bottom: var(--s-3); }
.report-row:last-child { border-bottom: none; }
.report-value { text-align: right; min-width: 0; overflow-wrap: anywhere; }
.report-value.bad, .log-summary .bad { color: var(--red); }
.report-error { font-size: var(--t-xs); }
.report-foot { margin-top: var(--s-5); }

/* Commands and their uploaded lines. */
.log-summary { flex-wrap: wrap; gap: var(--s-3); }
.device-logs:not(:empty) { margin-top: var(--s-6); }
.log-lines {
  font-family: var(--f-mono); font-size: var(--t-micro); line-height: 1.6;
  background: var(--bg-sunk); border: 1px solid var(--line); border-radius: var(--r-md);
  padding: var(--s-4) var(--s-5); max-height: 420px; overflow: auto;
}
.log-line { white-space: pre-wrap; overflow-wrap: anywhere; }
.log-line .log-ts, .log-line .log-cat { color: var(--ink-3); }
.log-line .log-level { text-transform: uppercase; }
.log-error .log-level, .log-error .log-msg { color: var(--red); }

@media (max-width: 1000px) {
  .device-grid { grid-template-columns: minmax(0, 1fr); }
}

/* ---------- Devices v3 and the device page (M4-0405; Devices.dc.html, Device.dc.html) ----------
   0381 shipped these classes with no rules (2026-10-15 audit G2-01). Values are
   the boards'; colours are the console's tokens, so dark mode follows them. */
.devices-v3 { display: flex; flex-direction: column; gap: var(--s-6); }
/* A narrow laptop window (M4-0445): beside the sidebar the seven columns need
   ~712 px and got 687 at 1024, so cells wrapped word by word and Points today
   was cut off. Config goes; the "Config behind" chip and the device page keep it.
   The cells' side padding tightens too, for the long ids and models real fleets have. */
@media (min-width: 768px) and (max-width: 1180px) {
  .devices-v3 .table :is(th, td):nth-child(6) { display: none; }
  .devices-v3 .table :is(th, td) { padding-inline: var(--s-4); }
}

/* The status chips: a 36 px pill each, solid when pressed. */
.device-chips { display: flex; flex-wrap: wrap; gap: var(--s-3); }
.filt {
  display: inline-flex; align-items: center; gap: var(--s-3);
  min-height: 36px; padding: 0 14px; border-radius: var(--r-pill);
  border: 1px solid var(--line); background: var(--bg-elev); color: var(--ink-2);
  font-family: var(--f-body); font-size: var(--t-sm); cursor: pointer; white-space: nowrap;
}
.filt:hover { border-color: var(--line-strong); color: var(--ink); }
.filt:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.filt[aria-pressed="true"] { background: var(--accent-solid); color: var(--accent-solid-ink); border-color: var(--accent-solid); }
.filt .chip-count { font-weight: 600; font-variant-numeric: tabular-nums; }

/* A dot is a real 7 px disc in its chip's colour (the base .dot is a green
   6 px block that only sizes inside a flex row). */
.filt .dot, .chip .dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: currentColor; flex: none; }
.filt .dot.ok { background: var(--green); }
.filt .dot.warn { background: var(--amber); }
.filt .dot.quiet { background: var(--ink-4); }

/* v3 chips: sentence case in the body face, in a tone (ok, warn, bad, info,
   quiet). Text mixes the tone with --ink, which darkens it on light and
   lightens it on dark. Scoped to these two pages; other screens keep .chip. */
.devices-v3 .chip, .device-page .chip {
  display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--f-body); font-size: 12px; font-weight: 500;
  padding: 2px 9px; border: 0; border-radius: var(--r-pill);
  background: var(--bg-sunk); color: var(--ink-2);
}
.devices-v3 .chip.ok, .device-page .chip.ok { background: var(--green-soft); color: var(--green); }
.devices-v3 .chip.warn, .device-page .chip.warn { background: color-mix(in oklch, var(--amber) 18%, transparent); color: var(--honey-ink); }
.devices-v3 .chip.bad, .device-page .chip.bad { background: var(--red-soft); color: var(--red); }
.devices-v3 .chip.info, .device-page .chip.info { background: var(--blue-soft); color: var(--blue); }
:root[data-theme="dark"] :is(.devices-v3, .device-page) .chip.warn { color: var(--honey); }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) :is(.devices-v3, .device-page) .chip.warn { color: var(--honey); } }
.devices-v3 .chip.quiet .dot, .device-page .chip.quiet .dot { background: var(--ink-4); }

/* The list: search bar, the device cell (name over its shortened id), health
   word over its reason, the foot. */
.device-list-bar { display: flex; flex-wrap: wrap; gap: var(--s-4); margin-bottom: var(--s-5); }
.device-list-bar .input { flex: 1 1 260px; }
/* Nothing to filter (M4-0415): Devices hides its chips and search. */
.device-chips[hidden], .device-list-bar[hidden] { display: none; }
.device-list .device-name { color: var(--ink); font-weight: 600; }
.device-list .device-name:hover { color: var(--accent-text); }
.device-id-short { display: block; font-size: var(--t-micro); color: var(--ink-3); }
.health-cell { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; }
.device-list-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--s-4); margin-top: var(--s-4); }
.device-list-foot:empty { display: none; }
.device-list-note { margin: 0; }
/* The link-only ?show= filter's note (no_user, from the Users tab footer). */
.device-link-note { margin: 0; }
.device-link-note[hidden] { display: none; }

/* The device page head: title row with its chips, the id line, the tabs. */
.device-title-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-4); }
.device-title-row .page-title { margin: 0; }
.device-title .health-why { margin: var(--s-2) 0 0; }
.device-title .health-see-why { margin: 2px 0 0; }
.device-ids {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3);
  margin-top: var(--s-3); font-size: var(--t-sm); color: var(--ink-3);
}
.device-ids .device-user, .device-ids .device-install { display: inline-flex; align-items: center; gap: var(--s-2); min-width: 0; }
.device-ids .id-value { color: var(--ink); overflow-wrap: anywhere; }
.device-ids .btn-copy { padding: 4px; min-width: 32px; min-height: 32px; }
.device-ids .btn-copy svg { width: 13px; height: 13px; }
.device-head .page-head-row { align-items: flex-start; }
.device-tabs { margin: var(--s-6) 0 var(--s-6); }
.device-tab { flex: none; }
/* A tab's sections are hidden by attribute; a display rule (the KPI grid) would
   otherwise outrank the browser's [hidden]. */
.device-panels { min-width: 0; }
.device-panels [hidden] { display: none; }
.device-tabs .tab-note { font-weight: 400; }

/* The KPI row: four cards in one row, two below 768 px. */
.device-kpis { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--s-5); }
.device-kpis .kpi { padding: 14px 16px; min-width: 0; }
.device-kpis .stat-value { font-size: 22px; font-weight: 600; letter-spacing: -0.02em; margin-top: var(--s-2); }

/* Health: a left-aligned key/value list (.kv.kv-left); a fact in trouble is red. */
.device-health .kv dd.bad { color: var(--red); }

@media (max-width: 767.98px) {
  .device-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .filt { min-height: 44px; }
}

/* ---------- Overview: setup checklist and foot (M4-0265, Overview.dc.html) ----------
   Ported from the canvas's v2-onboard / v2-steps, without the prefix (0216 §Styles). */
.setup { margin-bottom: var(--s-8); }
.setup-item {
  border: 1px solid color-mix(in oklch, var(--honey) 45%, var(--line));
  background: color-mix(in oklch, var(--honey) 7%, var(--bg-elev));
  border-radius: var(--r-lg);
  padding: 20px 22px;
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 18px; align-items: center;
}
.setup-item + .setup-item { margin-top: var(--s-4); }
.setup-steps { display: flex; gap: 6px; }
.setup-steps i { width: 26px; height: 4px; border-radius: 2px; background: var(--line-strong); display: block; }
.setup-steps i.done { background: var(--honey); }
.setup-title { font-weight: 600; }
.setup-text { margin-top: 3px; max-width: 70ch; }
.setup-more { margin-top: var(--s-4); }
.overview-dashboard[hidden] { display: none; }
/* M4-0340: when the rollup figures were computed, above the tiles. */
.overview-asof { margin: 0 0 var(--s-4); max-width: 80ch; }
.overview-asof[hidden] { display: none; }

/* ---------- Overview: org tiles and Open problems (M4-0295, design pass §0295) ---------- */
.overview-dashboard { display: grid; gap: var(--s-7); margin-bottom: var(--s-8); }
.overview-tiles .tile { display: flex; flex-direction: column; min-width: 0; }
.tile-partial { color: var(--ink-2); }
.problems .section-head { margin-bottom: var(--s-4); }
.problems-list { list-style: none; margin: 0; padding: 0; border: 1px solid var(--line); border-radius: var(--r-lg); background: var(--bg-elev); }
.problem { display: grid; grid-template-columns: 84px minmax(0, 1fr) auto; gap: var(--s-4); align-items: center; padding: 12px 16px; }
.problem + .problem { border-top: 1px solid var(--line); }
.problems-more { margin-top: var(--s-3); }
.problems-more-error { margin-left: var(--s-2); }
@media (max-width: 640px) { .problem { grid-template-columns: minmax(0, 1fr); } }
/* M4-0382 (Main.dc.html): Needs attention as one card, its rows divided by
   lines, and Recent changes beside it; they stack when the row is narrow. */
.overview-split { display: flex; flex-wrap: wrap; gap: var(--s-6); align-items: flex-start; }
.overview-split > .problems { flex: 999 1 520px; min-width: 0; padding: 18px 18px 8px; }
.overview-split > .overview-recent { flex: 1 1 300px; min-width: 0; padding: 18px; }
.overview-split > section + section { margin-top: 0; }
.overview-split .section-head { margin-bottom: var(--s-3); }
.overview-split .problems-list { border: 0; border-radius: 0; background: none; }
.overview-split .problem { grid-template-columns: 110px minmax(0, 1fr) auto; padding: 12px 0; border-top: 1px solid var(--line); }
.overview-split .problems-none { margin: 0 0 var(--s-4); }
.problem-sub { margin-top: 2px; }
/* M4-0382 (D135): the KPI row's "of N", a failure count in red, the cap bar,
   and the region chip and count in the projects card. */
.stat-of { font-size: 16px; font-weight: 500; color: var(--ink-3); }
.stat-bad { color: var(--red); }
.tile-cap { margin: 4px 0 2px; }
.project-cell { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.region-chip { font-size: 11px; }
.overview-projects .section-head { align-items: baseline; }
.recent-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 14px; }
.recent-item { display: flex; flex-direction: column; gap: 2px; min-width: 0; overflow-wrap: anywhere; }
@media (max-width: 640px) { .overview-split .problem { grid-template-columns: minmax(0, 1fr); } }
.overview-foot {
  display: flex; flex-wrap: wrap; gap: var(--s-4) 32px;
  margin-top: 28px; padding-top: 18px; border-top: 1px solid var(--line);
  font-size: var(--t-xs); color: var(--ink-3);
}
@media (max-width: 640px) { .setup-item { grid-template-columns: minmax(0, 1fr); } }
.strong { font-weight: 500; }

/* Settings (M4-0271, Settings.dc.html): cards with an h2 head, left-aligned
   key/value rows (v2's .v2-kv as Settings sets it), the danger row at a card's
   foot, and Organisation beside Appearance. */
.settings-card { padding: 20px; }
.kv.kv-left { grid-template-columns: 160px 1fr; margin-top: 0; font-size: var(--t-sm); }
.kv.kv-left dd { text-align: left; color: var(--ink); }
.settings-note { margin: 10px 0 0; max-width: 72ch; }
.settings-foot {
  display: flex; justify-content: space-between; align-items: center; gap: 20px;
  margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--line);
}
.settings-foot > p { margin: 0; max-width: 60ch; }
.settings-pair { display: grid; grid-template-columns: 1.5fr 1fr; gap: 18px; }
/* M4-0367: the Account link under Settings, the Account form, the cap control. */
.account-form { margin-top: 18px; max-width: 480px; }
.usage-cap-control:empty { display: none; }
@media (max-width: 760px) {
  .settings-pair { grid-template-columns: 1fr; }
  .settings-foot { flex-direction: column; align-items: flex-start; }
  .kv.kv-left { grid-template-columns: 1fr; }
}

/* ---------- phone layouts (M4-0268; Design v2's PhoneNav, PhoneActivity, PhoneMap) ----------
   The shell's phone breakpoint (768 px) and nothing else: below it the drawer is
   already the navigation (0180); this block makes the v2 phone frame's touch
   targets, Activity's cards and the full-bleed Map. CSS only, no new control:
   every action stays where the desktop has it. */
@media (max-width: 767.98px) {
  /* v2's phone frame: 44 px targets for the menu, the drawer's links and the
     buttons; a small button keeps 36 px (v2's "Layers", "Filters"). */
  .menu-btn { min-width: 44px; min-height: 44px; align-items: center; justify-content: center; }
  .nav-item { min-height: 44px; }
  .btn { min-height: 44px; }
  .btn-sm { min-height: 36px; }
  /* Icon buttons, copy buttons, row menus, segmented chips and tabs are at
     least 40 px on a phone (M4-0407, G5-17, G4-11, G1-33). */
  /* They are all .btn, so already 44 px tall here; the menu button keeps its 44 px width. */
  :is(.btn-icon, .row-menu-btn, .key-copy-btn, .btn-copy):not(.menu-btn) { min-width: 40px; }
  .id-chip .btn, .seg-btn, .map-tool, .lang-tab, .row-menu-item,
  .wh-tabs [role="tab"], .device-tabs [role="tab"], .org-tab { min-height: 40px; }
  /* v2's phone body keeps a 16 px gutter; Login's pane (0272) too. */
  .auth-pane { padding-left: var(--s-6); padding-right: var(--s-6); }

  /* Activity, and a device's recent points: one card per point (v2-pcard), its
     cells a two-column grid of captioned values; the time and the row's link
     take the whole width. Nothing scrolls sideways, not even inside the wrap. */
  .table-scroll { border: 0; border-radius: 0; background: transparent; }
  .table-scroll .table, .table-scroll .table tbody { display: block; width: 100%; }
  .table-scroll .table thead { display: none; }
  .table-scroll .table tr {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px 14px;
    width: 100%; padding: 12px 14px; margin-bottom: 10px;
    border: 1px solid var(--line); border-radius: var(--r-md); background: var(--bg-elev);
  }
  .table-scroll .table tr:last-child { margin-bottom: 0; border-bottom: 1px solid var(--line); }
  .table-scroll .table tr.gated { border-color: color-mix(in oklch, var(--red) 30%, var(--line)); }
  .table-scroll .table tr.gated td { background: transparent; }
  .table-scroll .table td {
    display: block; width: auto; min-width: 0; padding: 0; border: 0;
    white-space: normal; overflow-wrap: anywhere; text-align: left;
  }
  .table-scroll .table td::before {
    content: attr(data-label); display: block;
    font-family: var(--f-mono); font-size: var(--t-eyebrow);
    letter-spacing: var(--tr-eyebrow); text-transform: uppercase;
    color: var(--ink-3); margin-bottom: 2px;
  }
  .table-scroll .table td.cell-actions::before { content: none; }
  .table-scroll .table td:first-child, .table-scroll .table td.cell-actions { grid-column: 1 / -1; }
  .table-scroll .table td:empty { display: none; }
  .table-scroll .table .cell-num { text-align: left; }
  /* A session divider between the cards (M4-0338): a captionless line, not a card. */
  .table-scroll .table tr.session-break { display: block; padding: 0; border: 0; background: transparent; }
  .table-scroll .table tr.session-break td { display: block; padding: 6px 4px; border-left: 0; border-right: 0; border-radius: 0; overflow-wrap: anywhere; }
  .table-scroll .table tr.session-break td::before { content: none; }

  /* Map (PhoneMap): the map is the screen under the topbar, edge to edge; the
     window and layers bar floats on its top, the chosen device's card is a
     bottom sheet on its bottom edge, and the freshness line, the legend and the
     device list follow below it. One grid cell holds the map and its floats. */
  .map-stage { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0; padding: 0; height: auto; min-height: 0; overflow: visible; }
  .map-stage > .map-wrap {
    grid-area: 1 / 1; position: relative; inset: auto;
    height: calc(100vh - var(--topbar-h)); height: calc(100dvh - var(--topbar-h)); min-height: 420px;
    border: 0; border-radius: 0;
  }
  .map-stage > .map-bar {
    grid-area: 1 / 1; align-self: start; position: relative; inset: auto; z-index: 2; margin: 12px;
    box-shadow: 0 6px 24px color-mix(in oklch, var(--ink) 10%, transparent);
  }
  .map-stage > .map-card {
    grid-area: 1 / 1; align-self: end; position: relative; inset: auto; z-index: 3;
    width: auto; max-height: 45vh; max-height: 45dvh; overflow-y: auto; margin: 0;
    border-width: 1px 0 0; border-radius: 18px 18px 0 0; padding: 10px 18px 22px;
    box-shadow: 0 -6px 24px color-mix(in oklch, var(--ink) 12%, transparent);
  }
  .map-stage > .map-card::before { content: ""; display: block; width: 36px; height: 4px; border-radius: 2px; background: var(--line-strong); margin: 0 auto 12px; }
  .map-stage > .map-fresh, .map-stage > .map-legend, .map-stage > .map-panel { margin: 12px 12px 0; }
  .map-stage > .map-panel { margin-bottom: 12px; }
  /* The zoom and the no-basemap note sit at the map's middle, clear of the bar
     above and the sheet below. */
  .map-stage .map-zoom { top: 50%; bottom: auto; right: 12px; transform: translateY(-50%); }
  .map-stage .map-basemap-note { top: 50%; bottom: auto; left: 12px; right: 60px; margin: 0; width: auto; max-width: none; transform: translateY(-100%); }
}

/* ---------- phone rows (M4-0414; audit 2026-10-15 §5.4, G2-25, G1-35, G3-08, G3-19) ----------
   Below the phone breakpoint, Devices, Activity's points, the Audit log and
   Overview's projects are list rows of two or three lines (Phone.dc.html), not
   label-per-field cards. Same table, same row hooks (data-install, data-pos),
   laid out as a grid of lines: the cells say what they are by where they sit,
   so the reflow's td::before captions are off for these four tables (Q1).
   A spanning item always spans the row's 1fr track, so a long name never
   widens the figures' auto tracks under it. Separators are ::after, with an
   empty alt text so a screen reader does not read them. */
.device-today-phone { display: none; }
.map-note-chip { display: none; }
@media (max-width: 767.98px) {
  :is(.devices-v3, .overview-projects, .audit-body) .table tbody tr,
  .table-scroll .table.activity-table tbody tr.ac-row {
    display: grid; grid-template-columns: auto auto minmax(0, 1fr) auto;
    align-items: center; gap: 2px 0; min-height: 44px;
  }
  :is(.devices-v3, .overview-projects, .audit-body) .table tbody td,
  .table-scroll .table.activity-table tbody tr.ac-row td {
    display: block; width: auto; min-width: 0; padding: 0; border: 0; text-align: left;
  }
  :is(.devices-v3, .overview-projects, .audit-body) .table tbody td::before,
  .table-scroll .table.activity-table tbody tr.ac-row td::before { display: none; }
  .devices-v3 .table tbody td:is(:nth-child(2), :nth-child(3))::after,
  .table-scroll .table.activity-table tbody tr.ac-row td:is(:nth-child(2), :nth-child(3))::after,
  .audit-body .table tbody td:nth-child(2)::after {
    content: "·" / ""; margin: 0 6px; color: var(--ink-3);
  }

  /* Devices (Phone.dc.html): name and health chip, then "user · last point ·
     N points today", then the health reason when there is one. The name's
     link covers the row, so the whole row opens the device. Platform and
     Config are on the device page. */
  .devices-v3 .table tbody tr { position: relative; padding: 8px 14px; }
  .devices-v3 .table tbody td:nth-child(1) { grid-area: 1 / 1 / 2 / 4; }
  .devices-v3 .table tbody td:nth-child(1) .device-id-short { display: none; }
  .devices-v3 .table tbody td:nth-child(1) a.device-name { font-weight: 600; }
  .devices-v3 .table tbody td:nth-child(1) a.device-name::after { content: ""; position: absolute; inset: 0; }
  .devices-v3 .table tbody td:nth-child(4), .devices-v3 .health-cell { display: contents; }
  .devices-v3 .table tbody .health-chip { grid-area: 1 / 4; justify-self: end; margin-left: 8px; }
  .devices-v3 .table tbody .health-why {
    grid-area: 3 / 1 / 4 / -1; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
  }
  .devices-v3 .table tbody .health-why:empty { display: none; }
  .devices-v3 .table tbody td:nth-child(2) { grid-area: 2 / 1; }
  .devices-v3 .table tbody td:nth-child(3) { grid-area: 2 / 2; white-space: nowrap; }
  .devices-v3 .table tbody td:nth-child(7) { grid-area: 2 / 3 / 3 / -1; }
  .devices-v3 .table tbody td:is(:nth-child(2), :nth-child(3), :nth-child(7)) { font-size: 13px; color: var(--ink-3); }
  .devices-v3 .table tbody td:is(:nth-child(5), :nth-child(6)) { display: none; }
  .devices-v3 .device-today-total { display: none; }
  .devices-v3 .device-today-phone { display: inline; }

  /* Activity: the time and the status pill, then device · user · accuracy.
     Speed is in the point's panel. Session dividers stay as they are. */
  .table-scroll .table.activity-table tbody tr.ac-row { padding: 8px 14px; margin-bottom: 8px; }
  .table-scroll .table.activity-table tbody tr.ac-row td:nth-child(1) { grid-area: 1 / 1 / 2 / 4; font-weight: 500; }
  .table-scroll .table.activity-table tbody tr.ac-row td:nth-child(6) { grid-area: 1 / 4; justify-self: end; margin-left: 8px; }
  .table-scroll .table.activity-table tbody tr.ac-row td:nth-child(2) { grid-area: 2 / 1; }
  .table-scroll .table.activity-table tbody tr.ac-row td:nth-child(3) { grid-area: 2 / 2; }
  .table-scroll .table.activity-table tbody tr.ac-row td:nth-child(4) { grid-area: 2 / 3 / 3 / -1; }
  .table-scroll .table.activity-table tbody tr.ac-row td:is(:nth-child(2), :nth-child(3), :nth-child(4)) {
    font-size: 13px; color: var(--ink-3); white-space: nowrap;
  }
  .table-scroll .table.activity-table tbody tr.ac-row td:nth-child(5) { display: none; }

  /* The Audit log: the sentence, then who · when. */
  .audit-body .table tbody tr { padding: 8px 14px; }
  .audit-body .table tbody td:nth-child(3) { grid-area: 1 / 1 / 2 / -1; overflow-wrap: anywhere; }
  .audit-body .table tbody td:nth-child(2) { grid-area: 2 / 1; overflow-wrap: anywhere; }
  .audit-body .table tbody td:nth-child(1) { grid-area: 2 / 2 / 3 / -1; white-space: nowrap; }
  .audit-body .table tbody td:is(:nth-child(1), :nth-child(2)) { font-size: 13px; color: var(--ink-3); }

  /* Overview's projects: the name, its region and status, then devices
     sending · points today · last point; Open (or Settings) at the right of
     both lines. */
  .overview-projects .table tbody tr { grid-template-columns: auto auto auto minmax(0, 1fr) auto auto; padding: 8px 14px; }
  .overview-projects .table tbody td:nth-child(1) { grid-area: 1 / 1 / 2 / 5; }
  .overview-projects .table tbody td:nth-child(2) { grid-area: 1 / 5; justify-self: end; margin-left: 8px; }
  .overview-projects .table tbody td:nth-child(3) { grid-area: 2 / 1; }
  .overview-projects .table tbody td:nth-child(4) { grid-area: 2 / 2; }
  .overview-projects .table tbody td:nth-child(5) { grid-area: 2 / 3 / 3 / 6; overflow: hidden; text-overflow: ellipsis; }
  .overview-projects .table tbody td:nth-child(6) { grid-area: 1 / 6 / 3 / 7; padding: 0 0 0 10px; }
  .overview-projects .table tbody td:is(:nth-child(3), :nth-child(4), :nth-child(5)) { font-size: 13px; color: var(--ink-3); white-space: nowrap; }
  .overview-projects .table tbody td:nth-child(3)::after { content: "\00a0sending\00a0\00a0·\00a0\00a0" / " sending,"; }
  .overview-projects .table tbody td:nth-child(4)::after { content: "\00a0points today\00a0\00a0·\00a0\00a0" / " points today,"; }

  /* The Trip page: the four tiles two by two, and the no-basemap note a
     one-line chip at the map's top-left (.map-wrap.trip-map: above the
     Trips section's own .trip-map rule further down). */
  .trip-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .map-wrap.trip-map .map-basemap-note {
    right: auto; width: max-content; max-width: calc(100% - 72px); padding: 6px 10px; gap: 6px; align-items: center;
    border-radius: var(--r-pill);
  }
  .map-wrap.trip-map .map-basemap-note > svg { margin-top: 0; }
  .trip-map .map-note-chip { display: inline; white-space: nowrap; font-weight: 500; }
  .trip-map .map-note-full {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden;
    clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  }
}

/* Settings' server reads (M4-0320/0321/0322, views/settings-data.js). */
.settings-retention { margin-top: 14px; }
.settings-retention > p.small { margin: 0; }
/* ---------- Project settings (M4-0383; ProjectSettings.dc.html) ----------
   A sticky section index beside the cards (Remote config's, 0379), and each
   card a list of rows: what the row is on the left, its one control on the
   right, a hairline between rows. */
.settings-layout { display: flex; align-items: flex-start; gap: 24px; }
.settings-index {
  flex: 0 0 200px; position: sticky; top: calc(var(--topbar-h) + 16px);
  display: flex; flex-direction: column; gap: 2px;
}
.settings-index a {
  display: flex; align-items: center; min-height: 36px; padding: 0 10px; border-radius: 10px;
  color: var(--ink-2); text-decoration: none; font-size: var(--t-sm);
}
.settings-index a:hover { background: var(--bg-sunk); }
.settings-index a[aria-current="true"] { background: var(--bg-sunk); color: var(--ink); font-weight: 600; }
.settings-form { flex: 1 1 auto; min-width: 0; max-width: 820px; padding-bottom: 24px; }
.settings-form .readonly-banner { margin-bottom: 0; }
.ps-card { padding: 20px 0 4px; scroll-margin-top: calc(var(--topbar-h) + 16px); }
.ps-card > .card-head { padding: 0 20px 6px; }
.ps-card > .settings-retention { margin: 8px 20px 16px; }
.settings-retention-line { margin: 0; font-weight: 500; }
.ps-row {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px 24px;
  padding: 16px 20px; border-top: 1px solid var(--line);
}
.ps-card > .card-head + .ps-row { border-top: 0; }
.ps-row-main { display: flex; flex-direction: column; gap: 4px; flex: 1 1 320px; min-width: 0; }
.ps-row-aside { flex: none; }
.ps-label { font-weight: 500; font-size: var(--t-sm); }
.ps-value { display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ps-help { margin: 0; max-width: 60ch; }
.ps-id { word-break: break-all; }
.ps-chip { font-family: var(--f-body); font-size: 12px; border-radius: var(--r-pill); padding: 2px 9px; }
.ps-row-name .field { margin: 0; max-width: 480px; }
.ps-row-name { align-items: flex-end; }
/* M4-0418 (G4-34): Save name on the input's line, beside it. */
.ps-name-line { display: flex; align-items: center; gap: var(--s-3); }
.ps-name-line .input { flex: 1 1 auto; min-width: 0; }
.ps-name-line .btn { flex: none; }
.dialog-message > p { margin: 0 0 10px; }
.erase-list { margin: 0 0 12px; padding-left: 20px; font-size: var(--t-sm); }
.erase-list li { margin: 4px 0; }
/* M4-0420 (G4-36): what the project holds now; Data kept's way out (G4-35). */
.erase-counts { margin: 0 0 12px; }
.ps-card > .ps-data-link { margin: 0 20px 16px; }
@media (max-width: 760px) {
  .settings-layout { flex-direction: column; align-items: stretch; }
  .settings-index { position: static; flex: none; flex-direction: row; flex-wrap: wrap; }
  .ps-row-name { align-items: stretch; }
}
@media (max-width: 767.98px) {
  .ps-name-line { flex-wrap: wrap; }
}
.settings-hook-url { word-break: break-all; }

/* The Members tab (M4-0267, M4-0323; v3 board M4-0389, D129): the inline
   invite form, a compact role select per row, the member cell's initials and
   chips, the three-role legend, and the invite link shown once in its dialog. */
.select.select-sm { width: auto; min-width: 0; padding-top: 4px; padding-bottom: 4px; font-size: var(--t-sm); }
.members-status { margin-top: 12px; }
.invite-form { padding: var(--s-5); }
.invite-fields { display: flex; flex-wrap: wrap; gap: var(--s-4); align-items: flex-end; }
.invite-fields > .field { margin-bottom: 0; flex: 1 1 260px; }
.invite-fields > .field + .field { flex: 0 1 220px; }
.invite-note { margin: var(--s-3) 0 0; }
.invite-error { margin-top: var(--s-3); }
.member-who { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.member-av {
  width: 30px; height: 30px; border-radius: 999px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  background: color-mix(in oklch, var(--honey) 22%, transparent); color: var(--accent-text);
  font-weight: 600; font-size: 12px;
}
.member-chip {
  display: inline-flex; align-items: center; padding: 2px 9px; border-radius: 999px;
  font-size: 12px; font-weight: 500; white-space: nowrap;
  background: var(--bg-sunk); color: var(--ink-2);
}
.member-chip.warn { background: color-mix(in oklch, var(--amber) 18%, transparent); color: var(--honey-ink); }
:root[data-theme="dark"] .member-chip.warn { color: var(--honey); }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .member-chip.warn { color: var(--honey); } }
.members-legend {
  display: flex; flex-wrap: wrap; gap: 6px 20px;
  margin-top: var(--s-4); padding-top: var(--s-4); border-top: 1px solid var(--line);
}
.members-legend strong { color: var(--ink); font-weight: 600; }
.invite-link {
  display: block; padding: 10px 12px; border: 1px solid var(--line); border-radius: 6px;
  word-break: break-all; user-select: all; font-size: var(--t-sm);
}

/* Phone: Sign out in the drawer's foot, and Activity's "Filters · N" collapse
   (M4-0329; Design v2's PhoneNav and PhoneActivity). Above 768 px the filters'
   group lays out as if it were not there and the button is not shown. */
.activity-filters { display: contents; }
.activity-filters-btn { display: none; }
@media (max-width: 767.98px) {
  .sidebar-foot > .btn { margin-top: var(--s-3); }
  .activity-filters-btn { display: inline-flex; align-self: flex-end; }
  .activity-controls:not([data-filters="open"]) .activity-filters { display: none; }
  .activity-controls[data-filters="open"] .activity-filters {
    display: flex; flex-wrap: wrap; align-items: flex-end;
    gap: var(--s-4) var(--s-5); flex-basis: 100%; min-width: 0;
  }
}

/* ---------- Sidebar foot: the org switcher (M4-0336) ---------- */
.sidebar-foot .org-switcher { width: 100%; max-width: 100%; margin-top: 2px; }

/* ---------- The Webhooks page (M4-0364) ---------- */
.wh-types { border: 0; padding: 0; margin: 0; min-width: 0; display: flex; flex-wrap: wrap; gap: 8px 18px; }
.wh-types > legend { padding: 0; margin-bottom: 6px; width: 100%; }
.wh-check { display: inline-flex; align-items: center; gap: 6px; font-size: var(--t-sm); cursor: pointer; }
.wh-check input { margin: 0; }
/* M4-0420: an event type's wire name after its label (G4-28); an event's logged
   time under when it happened (G4-29). */
.wh-wire { margin-left: 2px; }
.wh-when2 { display: inline-flex; flex-direction: column; gap: 2px; }
/* Webhooks & events as one screen (M4-0378, D129; Webhooks.dc.html): tabs, the
   endpoints table with its side panel BESIDE it (it wraps under the table only
   when the two cannot share the row), and the deliveries and events tables. */
.wh-screen .readonly-banner { margin-bottom: 0; }
/* The board's state chips are sentence case in the body face, not the kit's
   uppercase mono pill. */
.wh-screen .chip { font-family: var(--f-body); font-size: 12px; border-radius: var(--r-pill); padding: 2px 9px; }
.wh-screen .pill {
  font-family: var(--f-body); font-size: var(--t-xs); letter-spacing: 0;
  text-transform: none; border-radius: var(--r-pill); padding: 2px 9px;
}
/* The device page's Overview · Points · Trips · Commands tabs share these
   (M4-0405; Device.dc.html's .tabs). */
.wh-tabs, .device-tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--line); overflow-x: auto; }
.wh-tabs [role="tab"], .device-tabs [role="tab"] {
  display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; cursor: pointer;
  min-height: 40px; padding: 10px 14px; border: 0; border-bottom: 2px solid transparent; background: none;
  font: inherit; font-weight: 500; color: var(--ink-3);
}
.wh-tabs [role="tab"][aria-selected="true"], .device-tabs [role="tab"][aria-selected="true"] { color: var(--ink); border-bottom-color: var(--honey); }
/* Inset: the tab strip scrolls sideways, which would clip an outer ring. */
.wh-tabs [role="tab"]:focus-visible, .device-tabs [role="tab"]:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--accent-text); border-radius: var(--r-sm); }
.wh-screen > .wh-tabs + [role="tabpanel"] { margin-top: var(--s-6); }
.wh-screen [role="tabpanel"][hidden] { display: none; }
.wh-split { display: flex; flex-wrap: wrap; gap: var(--s-6); align-items: flex-start; }
.wh-split > .wh-table-card { flex: 999 1 480px; min-width: 0; }
.wh-panel-host { flex: 1 1 360px; min-width: 0; }
.wh-panel-host:empty { display: none; }
.wh-table-card { padding: 0; overflow: hidden; }
.wh-table-card > .empty, .wh-table-card > .errorbox, .wh-table-card > .skel,
.wh-table-card > div > .skel { margin: var(--s-6); }
.wh-table-card .table-wrap { border: 0; border-radius: 0; }
.wh-row { cursor: pointer; }
.table tbody tr.sel td { background: color-mix(in oklch, var(--honey) 14%, transparent); }
.wh-host { display: block; font-weight: 600; }
.wh-path, .wh-line { display: block; font-size: var(--t-xs); }
.wh-line { margin-top: 3px; }
.wh-note { margin: 0; padding: var(--s-4) var(--s-6); border-top: 1px solid var(--line); }
.wh-bar {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px;
  padding: 12px 14px; border-bottom: 1px solid var(--line);
}
.wh-scope { display: inline-flex; align-items: center; gap: 6px; font-size: var(--t-sm); }
.wh-uid { font-size: var(--t-xs); }
.wh-panel { display: flex; flex-direction: column; gap: var(--s-6); padding: var(--s-6); min-width: 0; }
.wh-panel-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
.wh-panel-title { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; min-width: 0; }
.wh-panel-title .h2 { overflow-wrap: anywhere; }
.wh-url-row {
  display: flex; align-items: center; gap: 8px; min-width: 0;
  padding: 8px 10px; border-radius: var(--r-md); background: var(--bg-sunk);
}
.wh-url-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--t-xs); }
.wh-kv.kv-left { grid-template-columns: 120px 1fr; }
.wh-kv dd { text-align: left; }
.wh-eyebrow { margin-bottom: calc(-1 * var(--s-3)); text-transform: none; }
.wh-queue { display: flex; flex-direction: column; gap: var(--s-5); align-items: flex-start; }
.wh-tiles { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; width: 100%; }
.wh-tile { display: flex; flex-direction: column; gap: 2px; padding: 12px; border-radius: var(--r-md); background: var(--bg-sunk); }
.wh-tile-label { font-size: var(--t-xs); color: var(--ink-3); }
.wh-tile strong { font-family: var(--f-display); font-size: 22px; font-weight: 600; font-variant-numeric: tabular-nums; }
.wh-tile-bad { background: var(--red-soft); }
.wh-tile-bad .wh-tile-label, .wh-tile-bad strong { color: var(--red); }
.wh-latest { list-style: none; margin: 0; padding: 0; width: 100%; }
.wh-latest li { display: flex; gap: 10px; align-items: center; padding: 8px 0; border-top: 1px solid var(--line); font-size: var(--t-sm); }
.wh-latest-what { flex: 1; min-width: 0; }
.wh-latest-when { font-size: var(--t-xs); white-space: nowrap; }
.wh-panel-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap;
  padding-top: var(--s-5); border-top: 1px solid var(--line);
}
/* ---------- Trips (M4-0366) ---------- */
/* A trip's map is the Map's surface in a card: the note at the top and the
   zoom at the right, inside the map, at every width. */
.trip-map { min-height: 0; height: clamp(300px, 52vh, 520px); }
.trip-map .map-basemap-note { top: 12px; left: 12px; right: 60px; margin: 0; width: auto; max-width: 560px; transform: none; }
.trip-map .map-zoom { top: 12px; bottom: auto; right: 12px; transform: none; }
/* A route point's card (M4-0416, G3-17), bottom-left over the trip's map. */
.trip-point-card { left: 12px; bottom: 12px; width: min(260px, calc(100% - 24px)); padding: 10px 14px 12px; }
.trip-point-card[hidden] { display: none; }
.trip-point-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s-3); }
.trip-point-head .btn-icon { margin: -4px -8px -4px 0; }
.trip-point-card .kv { margin-top: 6px; }
.trip-key-start { background: var(--map-track-2); }
.trip-key-end { background: var(--map-fence); }
.trip-key-track { height: 3px; width: 14px; border-radius: 1px; background: var(--map-track); }
/* ---------- The remote config editor (M4-0363; views/config.js) ----------
   Settings' cards, one per section of the document, each a two-column grid of
   v2 fields (one column on a phone); checkboxes as a label row with the hint
   under its text; the below-900 s note in the accent ink; and the save bar,
   which stays in view at the bottom of the page. */
.settings-config { margin: 14px 0 0; }
.config-sub { margin: -6px 0 16px; max-width: 72ch; }
.config-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 24px; align-items: start; }
.config-grid > .field { min-width: 0; }
.config-check { padding-top: 22px; }
.config-check-label { display: flex; align-items: flex-start; gap: 10px; font-size: var(--t-sm); color: var(--ink); cursor: pointer; }
.config-check-label input { margin-top: 2px; accent-color: var(--accent-text); flex: none; width: 16px; height: 16px; }
.config-check-label input:disabled + span { color: var(--ink-3); cursor: default; }
.config-check > .field-hint, .config-check > .field-error { margin-left: 26px; }
/* Console v3 (M4-0379, RemoteConfig.dc.html): the sticky section index beside
   the cards, the tags (the head's revision and devices, the index's badges, a
   field's "Changed · was …"), each unit after its input, the upload-floor
   warning as an amber block under the interval's input, and the bar's count
   and blocker line. */
/* M4-0418 (G4-24): the neutral note tone, not honey/amber — a fact about
   Android, not a fault in the value. */
.config-floor {
  display: block; margin-top: var(--s-2); padding: 10px 12px; border-radius: var(--r-md, 10px);
  background: var(--bg-sunk); font-size: var(--t-xs); color: var(--ink-2);
}
.config-floor strong { font-weight: 600; color: var(--ink-2); }
.config-floor[hidden] { display: none; }
.config-kmh { font-size: var(--t-sm); white-space: nowrap; font-variant-numeric: tabular-nums; }
.config-rev { font-variant-numeric: tabular-nums; }
.config-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.config-tag {
  display: inline-flex; align-items: center; gap: 4px; padding: 2px 9px; border-radius: 999px;
  font-size: var(--t-xs); font-weight: 500; line-height: 1.5; white-space: nowrap;
  background: var(--bg-sunk); color: var(--ink-2); text-decoration: none;
}
a.config-tag:hover { text-decoration: underline; }
.config-tag[hidden] { display: none; }
/* The head's tags wrap at 320 px: the revision tag's note ran the page 59 px
   wide (M4-0407, G5-31; WCAG 1.4.10). */
.config-meta .config-tag { white-space: normal; max-width: 100%; }
.config-tag-edit { background: color-mix(in oklch, var(--honey) 18%, transparent); color: var(--honey-ink); }
.config-tag-warn { background: color-mix(in oklch, var(--amber) 18%, transparent); color: var(--honey-ink); }
.config-tag-ok { background: var(--green-soft); color: var(--green); }
.config-tag-bad { background: var(--red-soft); color: var(--red); }
:root[data-theme="dark"] .config-tag-edit, :root[data-theme="dark"] .config-tag-warn { color: var(--honey); }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .config-tag-edit, :root:not([data-theme="light"]) .config-tag-warn { color: var(--honey); } }
.config-rev-note { color: var(--ink-3); }
.config-layout { display: flex; align-items: flex-start; gap: 24px; }
.config-index {
  flex: 0 0 200px; position: sticky; top: calc(var(--topbar-h) + 16px);
  display: flex; flex-direction: column; gap: 2px;
}
.config-index a {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  min-height: 36px; padding: 0 10px; border-radius: 10px;
  color: var(--ink-2); text-decoration: none; font-size: var(--t-sm);
}
.config-index a:hover { background: var(--bg-sunk); }
.config-index a[aria-current="true"] { background: var(--bg-sunk); color: var(--ink); font-weight: 600; }
.config-index .config-badge { font-weight: 500; }
.config-form { flex: 1 1 auto; min-width: 0; padding-bottom: 24px; }
.config-card { scroll-margin-top: calc(var(--topbar-h) + 16px); }
.config-input-row { display: flex; align-items: center; gap: 8px; }
.config-input-row .input { flex: 1 1 auto; min-width: 0; }
.config-unit { font-size: var(--t-sm); white-space: nowrap; }
.config-was { font-weight: 500; }
.input.config-changed, .select.config-changed { border-color: var(--honey); }
.config-count { display: block; font-weight: 600; }
.config-blocker { margin: 2px 0 0; font-size: var(--t-xs); color: var(--red); }
.config-blocker[hidden], .config-status[hidden] { display: none; }
.config-blocker a { color: var(--red); text-decoration: underline; }
.config-bar {
  position: sticky; bottom: 0; z-index: 2;
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  margin-top: 18px; padding: 14px 20px;
  background: var(--bg-elev); border: 1px solid var(--line); border-radius: var(--r-lg);
  box-shadow: 0 -6px 24px color-mix(in oklch, var(--ink) 6%, transparent);
}
.config-bar-text { min-width: 0; }
.config-status { margin: 0; }
.config-problem { margin-top: 6px; font-size: var(--t-xs); color: var(--red); display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.config-problem[hidden] { display: none; }
@media (max-width: 760px) {
  .config-grid { grid-template-columns: minmax(0, 1fr); }
  .config-check { padding-top: 0; }
  .config-layout { flex-direction: column; align-items: stretch; }
  .config-index { position: static; flex: none; flex-direction: row; flex-wrap: wrap; }
  .config-bar { flex-direction: column; align-items: stretch; padding: 12px 16px; }
  .config-bar .btn-row { justify-content: flex-end; }
}
/* ---------- The audit log (M4-0376; views/audit.js) ----------
   Console v3's Organisation › Audit log: one card, the filter row on top, the
   When / Who / What table flush to the card's edges, and a footer with the
   order note and "Load older". */
.audit-card { padding: 0; overflow: hidden; }
.audit-filters { display: flex; flex-wrap: wrap; gap: 10px; padding: 12px 14px; border-bottom: 1px solid var(--line); }
.audit-filters .select { flex: 0 1 220px; width: auto; min-width: 0; }
.audit-card .table-wrap { border: 0; border-radius: 0; }
.audit-body > .skel-row, .audit-body > div > .skel-row { margin: 12px 14px; }
.audit-body > .empty, .audit-body > .errorbox, .audit-body > .notice { margin: 16px 14px; }
.audit-foot { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 12px 14px; border-top: 1px solid var(--line); }
.audit-foot:empty { display: none; }
@media (max-width: 760px) {
  .audit-filters .select { flex: 1 1 100%; }
}

/* ---------- Activity and Trips side panels (M4-0380; D129, Activity.dc.html,
   Trips.dc.html) ----------
   Selecting a row opens its panel BESIDE the table; the two share the row while
   they fit (the table first, the panel up to 400 px) and the panel wraps under
   the table only when they cannot. The reason chips sit above the table. */
.ac-split { display: flex; flex-wrap: wrap; gap: var(--s-6); align-items: flex-start; }
.ac-main { flex: 999 1 520px; min-width: 0; }
.ac-panel-host { flex: 1 1 320px; min-width: 0; max-width: 400px; }
.ac-panel-host:empty { display: none; }
.ac-row, .tp-row { cursor: pointer; }
.ac-row:focus-visible, .tp-row:focus-visible { outline: 2px solid var(--accent-text); outline-offset: -2px; }
.ac-reasons { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; margin-bottom: var(--s-5); }
.ac-reasons[hidden] { display: none; }
.ac-reason {
  display: inline-flex; align-items: center; padding: 2px 9px; border-radius: var(--r-pill);
  font-size: var(--t-xs); font-weight: 500; white-space: nowrap; font-variant-numeric: tabular-nums;
  background: var(--bg-sunk); color: var(--ink-2);
}
.ac-reason-implausible_speed { background: var(--red-soft); color: var(--red); }
.ac-reason-low_accuracy { background: color-mix(in oklch, var(--amber) 14%, transparent); color: var(--honey-ink); }
:root[data-theme="dark"] .ac-reason-low_accuracy { color: var(--honey); }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .ac-reason-low_accuracy { color: var(--honey); } }
.ac-reason-mock { background: color-mix(in oklch, var(--map-track) 12%, transparent); color: var(--map-track); }
.ac-panel, .tp-panel { display: flex; flex-direction: column; gap: var(--s-5); padding: var(--s-6); min-width: 0; }
.ac-panel-head, .tp-panel-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
.tp-panel-title { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; min-width: 0; }
.tp-panel-title .h3, .ac-panel-head .h3 { margin: 0; overflow-wrap: anywhere; }
.ac-held {
  display: flex; flex-direction: column; gap: 4px; padding: 12px; border-radius: var(--r-md);
  background: var(--red-soft); font-size: var(--t-sm);
}
.ac-held strong { color: var(--red); font-weight: 600; }
.ac-mini-wrap { display: flex; flex-direction: column; gap: 6px; }
.ac-mini, .tp-route {
  display: block; width: 100%; height: auto; border-radius: var(--r-md);
  border: 1px solid var(--line); background: var(--bg-sunk);
}
.ac-mini-note { margin: 0; }
.ac-kv.kv-left { grid-template-columns: 96px minmax(0, 1fr); }
.ac-kv .id-chip { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.ac-panel-links, .tp-panel-links { flex-wrap: wrap; gap: 8px; }
.tp-route-wrap { display: flex; flex-direction: column; gap: 6px; }
.tp-route-wrap > p { margin: 0; }
.tp-stats { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.tp-stat { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.tp-stat-value { font-family: var(--f-display); font-size: 18px; font-weight: 600; font-variant-numeric: tabular-nums; }

/* D133: an opened fence's name heads the side panel, under the link back. */
.fence-editor-title { margin: 0; min-width: 0; }
/* v3's line above the Activity table (the zone, the chips) and its foot (how
   far the shown rows reach, Load older), D137. */
.ac-bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 16px; margin-bottom: var(--s-5); }
.ac-bar[hidden] { display: none; }
.ac-bar .ac-reasons { margin-bottom: 0; }
.ac-zone { margin: 0; }
.ac-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px; margin-top: var(--s-4); }
.ac-foot .ac-cap { margin: 0; }
.ac-foot .ac-cap p { margin: 0; }
.ac-more { margin-left: auto; }
.ac-more:empty { display: none; }
.tp-who { display: block; font-size: var(--t-xs); }
/* Activity's Device cell (M4-0411, G2-21): the device's name, its short id beneath. */
.ac-dev { display: inline-flex; flex-direction: column; min-width: 0; }
/* The Map card's heading: the device's name (G2-13). */
.map-card-title { margin: 0 0 2px; overflow-wrap: anywhere; }
.tp-chip { display: inline-flex; align-items: center; gap: 5px; padding: 2px 9px; border-radius: var(--r-pill); font-size: var(--t-xs); font-weight: 500; white-space: nowrap; background: var(--bg-sunk); color: var(--ink-2); }
.tp-chip-live { background: var(--green-soft); color: var(--green); }
.tp-chip-live::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.ac-actions { flex-wrap: wrap; justify-content: flex-end; }

/* ---------- Users tab and the user page (M4-0424; D139, D142) ---------- */

/* The list reuses the devices-v3 chips, search bar and footer; the view renders
   the table or the phone list (never both), so the phone list is a plain block. */
.user-phone-list { list-style: none; margin: 0; padding: 0; }
.users-anon-note { display: block; margin-top: var(--s-3); }

/* The user page. */
.user-head { display: flex; flex-direction: column; gap: var(--s-2); }
.user-head-row { display: flex; align-items: center; gap: var(--s-4); flex-wrap: wrap; }
.user-content { display: flex; flex-direction: column; gap: var(--s-6); }
.user-seen { display: flex; gap: var(--s-3); flex-wrap: wrap; color: var(--ink-3); }
.user-actions { flex-wrap: wrap; gap: var(--s-3); margin-top: var(--s-4); }
.user-extras { margin-top: var(--s-3); }
.extra-chips { display: flex; flex-wrap: wrap; gap: var(--s-2); margin-top: var(--s-2); }
.extra-chip { display: inline-flex; gap: 6px; align-items: baseline; }
.extra-chip .extra-key { font-family: var(--f-mono); color: var(--ink-3); }
.extra-chip .extra-val { font-weight: 500; overflow-wrap: anywhere; }

/* The four KPI tiles: four in a row, two below 768 px (device-kpis' shape). */
.user-kpis { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--s-5); }
.user-kpis .kpi { padding: 14px 16px; min-width: 0; border: 1px solid var(--line); border-radius: var(--r-card); background: var(--bg-card); }
.user-kpis .stat-value { font-size: 20px; font-weight: 600; letter-spacing: -0.02em; margin-top: var(--s-2); overflow-wrap: anywhere; }

.user-section { }
.user-subhead { font-size: var(--t-h3); margin: var(--s-5) 0 var(--s-3); }
.user-device-list, .user-also-list { display: flex; flex-direction: column; gap: var(--s-3); }
.user-device-list li, .user-also-list li { display: flex; align-items: baseline; gap: var(--s-3); flex-wrap: wrap; }

/* Where they've been (M4-0436): the window's name over the minimap, the
   window's trips under it. */
.user-map-window { margin: var(--s-4) 0 var(--s-3); font-weight: 500; }
.user-map-trips .link-list a { justify-content: space-between; }
.user-map-note { margin-top: var(--s-4); }

.user-place-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s-3); }
.user-place { display: flex; align-items: baseline; gap: var(--s-3); flex-wrap: wrap; }
.user-place .place-kind { font-weight: 600; }
.user-place .place-fence { color: var(--ink-2); overflow-wrap: anywhere; }

.user-month-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s-2); }
.user-month-list li { display: flex; justify-content: space-between; gap: var(--s-4); padding: var(--s-2) 0; border-bottom: 1px solid var(--line); }
.user-month-list li:last-child { border-bottom: none; }

@media (max-width: 767.98px) {
  /* The list is phone rows, not a table (audit §5.4; the view renders one). */
  .user-phone-row { border-bottom: 1px solid var(--line); }
  .user-phone-row:last-child { border-bottom: none; }
  .user-phone-link { display: flex; flex-direction: column; gap: 4px; padding: var(--s-4) var(--s-2); min-height: 56px; text-decoration: none; color: inherit; }
  .user-phone-top { display: flex; align-items: center; gap: var(--s-3); justify-content: space-between; }
  .user-phone-id { font-weight: 500; }
  .user-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Device detail loading (M4-0443, views/device.js): the KPI cards' value bar
   at the value's size; the tab bars on the strip's line. */
.device-kpis .skel-tile > .skel + .skel { height: 22px; margin-top: var(--s-2); }
.device-card > .skel:first-child { height: 1.1em; margin-bottom: var(--s-5); }
.device-tabs > .skel-tabs { border-bottom: 0; }
