/*
 * FleetROS platform admin console.
 *
 * DIRECTION: operator instrument panel — deliberately not the tenant dashboard.
 *
 * The console shares the FleetROS brand tokens so it reads as the same product
 * family, but commits to a different treatment on purpose. From Milestone 5 the
 * operator can open a live session as a tenant or a customer with every side
 * effect real, no undo and no audit trail. If this surface looked like the
 * tenant dashboard, the operator could not tell at a glance which context they
 * are acting in — and the cost of that confusion is acting on a tenant's
 * production data believing you are in the read-only platform view.
 *
 * So: dark ground, dense layout, monospace for anything that gets read and
 * compared, and ORANGE RESERVED FOR "live / real / acting as someone else".
 * Orange is never decorative here. Milestone 5 inherits it as the impersonation
 * signal, which only works if nothing else has spent it.
 */

/* Plus Jakarta Sans — brand face, variable weight axis in a single file.
 * Declarations mirror fleetros-capacitor/src/css/style.css so the two surfaces
 * render the same face with the same subsetting. */
@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('/static/fonts/plus-jakarta-sans-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
    U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('/static/fonts/plus-jakarta-sans-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
    U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
    U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  /* ── Brand, carried over from fleetros-backoffice-v2/app/globals.css ── */
  --brand-blue: 217 91% 24%;
  --brand-blue-lift: 217 91% 60%;
  --brand-orange: 24 95% 53%;

  --success: 142 71% 45%;
  --warning: 38 92% 50%;
  --danger: 0 72% 51%;

  /* ── Ground and surfaces ────────────────────────────────────────────────
   * Blue-tinted charcoal rather than neutral grey, so the dark treatment
   * still reads as FleetROS rather than as a generic dark theme. */
  --ground: 222 47% 7%;
  --surface: 222 40% 11%;
  --surface-lift: 222 36% 15%;
  --line: 222 30% 20%;
  --line-strong: 222 26% 30%;

  --text: 210 40% 96%;
  --text-muted: 215 20% 62%;
  --text-dim: 215 16% 45%;

  /* ── Type ───────────────────────────────────────────────────────────────
   * ONE bundled family, self-hosted, no external request.
   *
   * Plus Jakarta Sans is the FleetROS brand face and is already vendored in
   * fleetros-capacitor/src/fonts/ for exactly this reason — see that
   * directory's README: an unbundled stack "silently fell back to system
   * sans" and rendering stopped being identical across devices.
   *
   * That is not only a brand problem here. This console ships visual
   * regression at four breakpoints, and a font stack that resolves to
   * whatever the machine happens to have makes every baseline diff noise
   * rather than signal. A bundled face is what makes those baselines mean
   * something on a different machine.
   *
   * A second monospace family was considered for identifiers and dropped:
   * font-variant-numeric: tabular-nums already gives the property that
   * actually matters — digits that line up in a column when compared — and a
   * system mono stack would reintroduce exactly the non-determinism the
   * bundled face exists to remove. The instrument feel is carried by tabular
   * figures, letter-spacing and colour instead. */
  --font-ui: "Plus Jakarta Sans", ui-sans-serif, system-ui, -apple-system,
             "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-mono: var(--font-ui);

  --step--1: 0.8125rem;
  --step-0: 0.9375rem;
  --step-1: 1.125rem;
  --step-2: 1.5rem;
  --step-3: clamp(1.75rem, 1.4rem + 1.6vw, 2.5rem);

  /* Rhythm is deliberately uneven: tight inside a component, generous between
   * them. Uniform padding everywhere is what makes a layout read as a
   * template. */
  --space-2xs: 0.25rem;
  --space-xs: 0.5rem;
  --space-sm: 0.75rem;
  --space-md: 1.25rem;
  --space-lg: 2rem;
  --space-xl: 3.5rem;

  --radius: 0.5rem;         /* matches the dashboard's --radius */
  --radius-sm: 0.3125rem;

  --duration: 160ms;
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

*, *::before, *::after { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 100vh;
  background: hsl(var(--ground));
  color: hsl(var(--text));
  font-family: var(--font-ui);
  font-size: var(--step-0);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

/* Atmosphere: a single soft brand-blue wash from the top-left, so the ground
 * has depth without a decorative gradient blob. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(60rem 40rem at 12% -10%, hsl(var(--brand-blue) / 0.28), transparent 60%),
    radial-gradient(40rem 30rem at 100% 0%, hsl(var(--brand-blue-lift) / 0.06), transparent 55%);
  z-index: 0;
}

a { color: hsl(var(--brand-blue-lift)); }

:focus-visible {
  outline: 2px solid hsl(var(--brand-blue-lift));
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* .mono marks values that get read and compared — identifiers, counts,
 * timestamps. Tabular figures line the digits up in a column; the slight
 * letter-spacing keeps long opaque strings legible. */
.mono {
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.01em;
}

/* ── Skip link ─────────────────────────────────────────────────────────────*/
.skip-link {
  position: absolute;
  left: var(--space-sm);
  top: -3rem;
  z-index: 10;
  padding: var(--space-xs) var(--space-sm);
  background: hsl(var(--surface-lift));
  border: 1px solid hsl(var(--line-strong));
  border-radius: var(--radius-sm);
  color: hsl(var(--text));
  text-decoration: none;
  transition: top var(--duration) var(--ease);
}
.skip-link:focus { top: var(--space-sm); }

/* ── Brand mark ────────────────────────────────────────────────────────────*/
.mark {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  font-weight: 600;
  letter-spacing: -0.015em;
}
.mark__glyph {
  display: grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--radius-sm);
  background: linear-gradient(140deg, hsl(var(--brand-blue-lift)), hsl(var(--brand-blue)));
  color: hsl(var(--text));
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: 700;
  box-shadow: 0 1px 0 hsl(0 0% 100% / 0.14) inset;
}
.mark__text { font-size: var(--step-0); }
.mark__scope {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: hsl(var(--brand-orange));
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* ── Sign-in ───────────────────────────────────────────────────────────────*/
.signin {
  position: relative;
  z-index: 1;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: var(--space-lg) var(--space-md);
}

.signin__card {
  width: min(26rem, 100%);
  background: hsl(var(--surface) / 0.86);
  backdrop-filter: blur(12px);
  border: 1px solid hsl(var(--line));
  border-radius: var(--radius);
  padding: var(--space-lg);
  /* Layered rather than a single flat drop shadow: a hairline highlight on
   * top, a tight contact shadow, and a wide ambient one. */
  box-shadow:
    0 1px 0 hsl(0 0% 100% / 0.06) inset,
    0 1px 2px hsl(222 60% 2% / 0.6),
    0 24px 48px -12px hsl(222 60% 2% / 0.7);
}

.signin__head { margin-bottom: var(--space-lg); }
.signin__title {
  margin: var(--space-md) 0 var(--space-2xs);
  font-size: var(--step-2);
  font-weight: 620;
  letter-spacing: -0.02em;
}
.signin__sub {
  margin: 0;
  color: hsl(var(--text-muted));
  font-size: var(--step--1);
}

/* The scope note is the point of this page: it says plainly that a tenant or
 * customer account cannot be used here, so an operator who lands on it with
 * the wrong credentials stops rather than retrying. */
.scope-note {
  margin: var(--space-md) 0 0;
  padding: var(--space-sm) var(--space-md);
  border-left: 2px solid hsl(var(--brand-blue-lift));
  background: hsl(var(--brand-blue) / 0.18);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  color: hsl(var(--text-muted));
  font-size: var(--step--1);
}
.scope-note strong { color: hsl(var(--text)); font-weight: 600; }

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  width: 100%;
  padding: 0.8125rem var(--space-md);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: linear-gradient(180deg, hsl(var(--brand-blue-lift)), hsl(217 91% 46%));
  color: hsl(0 0% 100%);
  font: inherit;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  box-shadow: 0 1px 0 hsl(0 0% 100% / 0.2) inset, 0 6px 16px -6px hsl(var(--brand-blue-lift) / 0.6);
  transition: transform var(--duration) var(--ease),
              box-shadow var(--duration) var(--ease),
              filter var(--duration) var(--ease);
}
.btn:hover { filter: brightness(1.08); box-shadow: 0 1px 0 hsl(0 0% 100% / 0.24) inset, 0 10px 24px -8px hsl(var(--brand-blue-lift) / 0.7); }
.btn:active { transform: translateY(1px); box-shadow: 0 1px 0 hsl(0 0% 100% / 0.12) inset; }

.btn__hint {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: 500;
  opacity: 0.72;
}

.signin__foot {
  margin: var(--space-md) 0 0;
  color: hsl(var(--text-dim));
  font-size: var(--step--1);
  text-align: center;
}

/* ── Shell ─────────────────────────────────────────────────────────────────*/
.shell {
  position: relative;
  z-index: 1;
  min-height: 100vh;
  display: grid;
  grid-template-rows: auto 1fr auto;
}

.topbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-sm) var(--space-md);
  border-bottom: 1px solid hsl(var(--line));
  background: hsl(var(--surface) / 0.72);
  backdrop-filter: blur(10px);
  position: sticky;
  top: 0;
  z-index: 5;
}
.topbar__spacer { flex: 1 1 auto; }

.identity {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  padding: var(--space-2xs) var(--space-sm);
  border: 1px solid hsl(var(--line));
  border-radius: 999px;
  background: hsl(var(--surface-lift));
  font-size: var(--step--1);
}
.identity__dot {
  width: 0.4375rem;
  height: 0.4375rem;
  border-radius: 50%;
  background: hsl(var(--success));
  box-shadow: 0 0 0 3px hsl(var(--success) / 0.18);
}
.identity__name { font-family: var(--font-mono); }

.linkish {
  padding: var(--space-2xs) var(--space-sm);
  border: 1px solid hsl(var(--line));
  border-radius: var(--radius-sm);
  background: transparent;
  color: hsl(var(--text-muted));
  font: inherit;
  font-size: var(--step--1);
  text-decoration: none;
  cursor: pointer;
  transition: color var(--duration) var(--ease),
              border-color var(--duration) var(--ease),
              background var(--duration) var(--ease);
}
.linkish:hover {
  color: hsl(var(--text));
  border-color: hsl(var(--line-strong));
  background: hsl(var(--surface-lift));
}

.main {
  padding: var(--space-xl) var(--space-md);
  max-width: 72rem;
  width: 100%;
  margin-inline: auto;
}

.page-head { margin-bottom: var(--space-lg); }
.eyebrow {
  margin: 0 0 var(--space-xs);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: hsl(var(--brand-orange));
}
.page-title {
  margin: 0 0 var(--space-xs);
  font-size: var(--step-3);
  font-weight: 620;
  letter-spacing: -0.025em;
  line-height: 1.1;
}
.page-sub { margin: 0; color: hsl(var(--text-muted)); max-width: 52ch; }

/* Bento rather than a uniform card grid: the milestone panel is the subject of
 * this page, so it takes the wide cell instead of being one of N equals. */
.bento {
  display: grid;
  gap: var(--space-md);
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
}
.panel {
  background: hsl(var(--surface) / 0.7);
  border: 1px solid hsl(var(--line));
  border-radius: var(--radius);
  padding: var(--space-md);
  box-shadow: 0 1px 0 hsl(0 0% 100% / 0.04) inset;
}
.panel--wide { grid-column: 1 / -1; }
.panel__label {
  margin: 0 0 var(--space-xs);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: hsl(var(--text-dim));
}
.panel__value {
  margin: 0;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--step-2);
  font-weight: 600;
  letter-spacing: -0.02em;
}
.panel__note { margin: var(--space-2xs) 0 0; color: hsl(var(--text-muted)); font-size: var(--step--1); }

.roadmap { margin: 0; padding: 0; list-style: none; display: grid; gap: var(--space-2xs); }
.roadmap li {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
  padding: var(--space-xs) 0;
  border-bottom: 1px solid hsl(var(--line) / 0.6);
  font-size: var(--step--1);
}
.roadmap li:last-child { border-bottom: 0; }
.roadmap__n {
  font-family: var(--font-mono);
  color: hsl(var(--text-dim));
  min-width: 1.5rem;
}
.roadmap__name { flex: 1 1 auto; }
.state {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 0 var(--space-xs);
  border-radius: var(--radius-sm);
}
.state--live { color: hsl(var(--success)); background: hsl(var(--success) / 0.12); }
.state--pending { color: hsl(var(--text-dim)); background: hsl(var(--line) / 0.5); }

.footer {
  padding: var(--space-md);
  border-top: 1px solid hsl(var(--line));
  color: hsl(var(--text-dim));
  font-size: var(--step--1);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md);
  justify-content: space-between;
}

/* ── Error ─────────────────────────────────────────────────────────────────*/
.notice {
  margin: 0 0 var(--space-md);
  padding: var(--space-sm) var(--space-md);
  border-left: 2px solid hsl(var(--danger));
  background: hsl(var(--danger) / 0.12);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  font-size: var(--step--1);
}
.notice__title { display: block; font-weight: 600; margin-bottom: var(--space-2xs); }

/* ── Narrow viewports ──────────────────────────────────────────────────────*/
@media (max-width: 30rem) {
  .main { padding: var(--space-lg) var(--space-sm); }
  .signin__card { padding: var(--space-md); }
  .topbar { gap: var(--space-xs); }
  .footer { flex-direction: column; gap: var(--space-2xs); }
}

/* ── Fact list ─────────────────────────────────────────────────────────────
 * A definition list rather than a table: these are attributes of one thing,
 * and a table would imply rows that can be compared against each other. */
.facts { margin: 0; display: grid; gap: var(--space-sm); }
.fact {
  display: grid;
  grid-template-columns: minmax(8rem, 12rem) 1fr;
  gap: var(--space-sm);
  align-items: baseline;
  padding-bottom: var(--space-sm);
  border-bottom: 1px solid hsl(var(--line) / 0.6);
}
.fact:last-child { border-bottom: 0; padding-bottom: 0; }
.fact dt {
  font-size: var(--step--1);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: hsl(var(--text-dim));
}
.fact dd {
  margin: 0;
  font-size: var(--step--1);
  overflow-wrap: anywhere;
}
.fact__rejected { display: flex; flex-wrap: wrap; gap: var(--space-xs); }
.fact__rejected span {
  padding: 0 var(--space-xs);
  border-radius: var(--radius-sm);
  background: hsl(var(--danger) / 0.14);
  color: hsl(var(--danger));
  text-decoration: line-through;
  text-decoration-thickness: 1px;
}

@media (max-width: 40rem) {
  .fact { grid-template-columns: 1fr; gap: var(--space-2xs); }
}

/* ═══════════════════════════════════════════════════════════════════════════
 * MILESTONE 2 — the tenant directory
 *
 * The console's first data surface, and the first place its visual direction
 * has to carry information rather than only identity.
 *
 * Two rules govern everything below.
 *
 * 1. COLOUR IS SEMANTIC, NEVER DECORATIVE. Four activity states are the whole
 *    point of this page, and an operator scanning fifty rows reads the colour
 *    before the word. Green/amber/blue/red map to active/dormant/new/never —
 *    and each is paired with a text label, because colour alone is not an
 *    accessible signal and the founder may well be reading this on a phone in
 *    daylight.
 *
 * 2. ORANGE IS STILL NOT SPENT. The brand orange remains reserved for
 *    "live / acting as someone else" (Milestone 5's impersonation signal).
 *    Nothing on this read-only page uses it. Amber for "dormant" is the
 *    --warning token, which is a different hue and a different meaning.
 * ═══════════════════════════════════════════════════════════════════════════ */

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ── Top navigation ──────────────────────────────────────────────────────── */
.topnav {
  display: flex;
  gap: var(--space-xs);
  margin-left: var(--space-md);
}
.topnav__link {
  color: hsl(var(--text-muted));
  text-decoration: none;
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0.01em;
  padding: var(--space-2xs) var(--space-xs);
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  transition: color var(--duration) var(--ease),
              background-color var(--duration) var(--ease),
              border-color var(--duration) var(--ease);
}
.topnav__link:hover { color: hsl(var(--text)); background: hsl(var(--surface-lift)); }
/* The current section is marked with a left rule rather than a fill: at a
 * glance it reads as position, not as a selected button. */
.topnav__link--current {
  color: hsl(var(--text));
  background: hsl(var(--surface));
  border-color: hsl(var(--line));
  box-shadow: inset 2px 0 0 hsl(var(--brand-blue-lift));
}
a.mark { text-decoration: none; color: inherit; }

/* ── Count strip ─────────────────────────────────────────────────────────────
 * Scale contrast does the hierarchy: the numbers are large and tabular, the
 * labels small and dim. Every figure covers the whole matched set, not the
 * page — a filter that hides rows without saying how many is how a console
 * teaches somebody a wrong number. */
.countstrip {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs) var(--space-lg);
  margin: 0 0 var(--space-md);
  padding: var(--space-sm) var(--space-md);
  background: hsl(var(--surface) / 0.6);
  border: 1px solid hsl(var(--line));
  border-radius: var(--radius);
}
.count { display: flex; flex-direction: column; gap: 0; }
.count dt {
  font-size: var(--step--1);
  color: hsl(var(--text-dim));
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.count dd {
  margin: 0;
  font-size: var(--step-2);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.count--good { color: hsl(var(--success)); }
.count--warn { color: hsl(var(--warning)); }
.count--info { color: hsl(var(--brand-blue-lift)); }
.count--bad  { color: hsl(var(--danger)); }

/* ── Toolbar and search ──────────────────────────────────────────────────── */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-sm);
  margin-bottom: var(--space-sm);
}
.field { display: flex; flex-direction: column; gap: var(--space-2xs); flex: 1 1 18rem; }
.field__label {
  font-size: var(--step--1);
  color: hsl(var(--text-dim));
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.field__input {
  background: hsl(var(--ground));
  border: 1px solid hsl(var(--line-strong));
  border-radius: var(--radius-sm);
  color: hsl(var(--text));
  font: inherit;
  padding: var(--space-xs) var(--space-sm);
  transition: border-color var(--duration) var(--ease), box-shadow var(--duration) var(--ease);
}
.field__input::placeholder { color: hsl(var(--text-dim)); }
.field__input:hover { border-color: hsl(var(--line-strong)); }
.field__input:focus {
  outline: none;
  border-color: hsl(var(--brand-blue-lift));
  box-shadow: 0 0 0 3px hsl(var(--brand-blue-lift) / 0.2);
}
.button {
  background: hsl(var(--brand-blue));
  border: 1px solid hsl(var(--brand-blue-lift) / 0.5);
  border-radius: var(--radius-sm);
  color: hsl(var(--text));
  font: inherit;
  font-weight: 600;
  padding: var(--space-xs) var(--space-md);
  cursor: pointer;
  text-decoration: none;
  display: inline-block;
  transition: background-color var(--duration) var(--ease), transform var(--duration) var(--ease);
}
.button:hover { background: hsl(var(--brand-blue-lift) / 0.35); }
.button:active { transform: translateY(1px); }

/* ── Filter chips ────────────────────────────────────────────────────────────
 * Links, not buttons, because every one of them is a URL. Search, filters,
 * sort and page all live in the query string, so any view can be bookmarked or
 * pasted into a support note — and the console still ships no JavaScript. */
.chips { display: flex; flex-wrap: wrap; gap: var(--space-xs); margin-bottom: var(--space-sm); }
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  padding: var(--space-2xs) var(--space-sm);
  border: 1px solid hsl(var(--line));
  border-radius: 999px;
  background: hsl(var(--surface) / 0.7);
  color: hsl(var(--text-muted));
  font-size: var(--step--1);
  font-weight: 600;
  text-decoration: none;
  transition: border-color var(--duration) var(--ease),
              color var(--duration) var(--ease),
              background-color var(--duration) var(--ease);
}
.chip:hover { color: hsl(var(--text)); border-color: hsl(var(--line-strong)); }
.chip__count {
  font-variant-numeric: tabular-nums;
  color: hsl(var(--text-dim));
  font-weight: 700;
}
.chip--on { color: hsl(var(--text)); background: hsl(var(--surface-lift)); }
.chip--good.chip--on { border-color: hsl(var(--success) / 0.7); box-shadow: inset 0 0 0 1px hsl(var(--success) / 0.35); }
.chip--warn.chip--on { border-color: hsl(var(--warning) / 0.7); box-shadow: inset 0 0 0 1px hsl(var(--warning) / 0.35); }
.chip--info.chip--on { border-color: hsl(var(--brand-blue-lift) / 0.7); box-shadow: inset 0 0 0 1px hsl(var(--brand-blue-lift) / 0.35); }
.chip--bad.chip--on  { border-color: hsl(var(--danger) / 0.7);  box-shadow: inset 0 0 0 1px hsl(var(--danger) / 0.35); }
.chip--on .chip__count { color: hsl(var(--text)); }

/* ── Notices ─────────────────────────────────────────────────────────────── */
.notice--warn {
  border-left: 3px solid hsl(var(--warning));
  background: hsl(var(--warning) / 0.08);
}
.notice--bad {
  border-left: 3px solid hsl(var(--danger));
  background: hsl(var(--danger) / 0.1);
}

/* ── The table ───────────────────────────────────────────────────────────────
 * One DOM for both layouts. At 768 and up it is a table; below that the same
 * rows become stacked cards through display changes alone, with each cell
 * carrying its heading in data-label. Two DOMs would mean two sets of
 * assertions and one of them silently rotting. */
.tablewrap {
  border: 1px solid hsl(var(--line));
  border-radius: var(--radius);
  background: hsl(var(--surface) / 0.5);
  overflow-x: auto;
}
.dirtable { width: 100%; border-collapse: collapse; font-size: var(--step--1); }
.dirtable thead th {
  text-align: left;
  padding: var(--space-sm) var(--space-md);
  font-size: var(--step--1);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: hsl(var(--text-dim));
  border-bottom: 1px solid hsl(var(--line-strong));
  white-space: nowrap;
}
.dirtable thead th a { color: inherit; text-decoration: none; }
.dirtable thead th a:hover { color: hsl(var(--text)); }
.dirtable tbody tr { border-bottom: 1px solid hsl(var(--line) / 0.6); }
.dirtable tbody tr:last-child { border-bottom: 0; }
.dirtable tbody tr:hover { background: hsl(var(--surface-lift) / 0.55); }
.dirtable td { padding: var(--space-sm) var(--space-md); vertical-align: top; }

.rowname { display: block; font-size: var(--step-0); font-weight: 700; color: hsl(var(--text)); }
.rowmeta { display: block; font-size: var(--step--1); color: hsl(var(--text-dim)); }
/* The "not enforced by the product" note under every status. Dim enough not to
 * compete with the value, present enough that it is never a footnote somebody
 * has to remember. */
.rowmeta--quiet { font-style: italic; opacity: 0.85; }
.statusline { font-weight: 600; letter-spacing: 0.03em; }
.cell--name { min-width: 14rem; }

.people { display: inline-flex; align-items: baseline; gap: var(--space-2xs); margin-right: var(--space-sm); font-variant-numeric: tabular-nums; font-weight: 700; }
.people .rowmeta { display: inline; font-weight: 400; }
.people--warn { color: hsl(var(--warning)); }

/* ── Badges ──────────────────────────────────────────────────────────────── */
.badge {
  display: inline-block;
  margin-top: var(--space-2xs);
  padding: 0.05rem var(--space-xs);
  border-radius: var(--radius-sm);
  font-size: var(--step--1);
  font-weight: 700;
  letter-spacing: 0.02em;
  border: 1px solid transparent;
  white-space: nowrap;
}
.badge--good    { color: hsl(var(--success)); background: hsl(var(--success) / 0.12); border-color: hsl(var(--success) / 0.35); }
.badge--warn    { color: hsl(var(--warning)); background: hsl(var(--warning) / 0.12); border-color: hsl(var(--warning) / 0.35); }
.badge--info    { color: hsl(var(--brand-blue-lift)); background: hsl(var(--brand-blue-lift) / 0.12); border-color: hsl(var(--brand-blue-lift) / 0.35); }
.badge--bad     { color: hsl(var(--danger)); background: hsl(var(--danger) / 0.12); border-color: hsl(var(--danger) / 0.4); }
.badge--neutral { color: hsl(var(--text-muted)); background: hsl(var(--surface-lift)); border-color: hsl(var(--line)); }
.badge--muted   { color: hsl(var(--text-dim)); background: transparent; border-color: hsl(var(--line)); }

/* ── Pager and footnote ──────────────────────────────────────────────────── */
.pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  margin-top: var(--space-sm);
}
.pager__count { font-size: var(--step--1); color: hsl(var(--text-dim)); font-variant-numeric: tabular-nums; }
.pager__links { display: flex; gap: var(--space-md); }
.footnote { font-size: var(--step--1); color: hsl(var(--text-dim)); margin-top: var(--space-sm); }

/* ── Empty states ────────────────────────────────────────────────────────────
 * Three of them, because they need three different actions. A single "no
 * results" would send the operator hunting for a tenant that a filter is
 * hiding. Designed rather than blank: an empty table with no explanation is
 * indistinguishable from a broken query. */
.empty {
  border: 1px dashed hsl(var(--line-strong));
  border-radius: var(--radius);
  padding: var(--space-xl) var(--space-lg);
  text-align: center;
  background: hsl(var(--surface) / 0.35);
}
.empty__title { margin: 0 0 var(--space-xs); font-size: var(--step-1); font-weight: 700; }
.empty__body { margin: 0 auto var(--space-md); max-width: 44ch; color: hsl(var(--text-muted)); }

/* ── Stacked cards below 768 ─────────────────────────────────────────────────
 * A directory table is the single most likely thing to overflow a 320px
 * viewport. Rather than let it scroll sideways — which hides the columns that
 * carry the classification — each row becomes a card and each cell grows its
 * own heading from data-label. */
@media (max-width: 767px) {
  .dirtable thead { display: none; }
  .dirtable, .dirtable tbody, .dirtable tr, .dirtable td { display: block; width: 100%; }
  .tablewrap { border: 0; background: transparent; overflow-x: visible; }
  .dirtable tbody tr {
    border: 1px solid hsl(var(--line));
    border-radius: var(--radius);
    background: hsl(var(--surface) / 0.6);
    margin-bottom: var(--space-sm);
    padding: var(--space-xs) 0;
  }
  .dirtable td {
    display: grid;
    grid-template-columns: minmax(6.5rem, 40%) 1fr;
    gap: var(--space-sm);
    padding: var(--space-2xs) var(--space-sm);
  }
  .dirtable td::before {
    content: attr(data-label);
    font-size: var(--step--1);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: hsl(var(--text-dim));
  }
  .dirtable td.cell--name { grid-template-columns: 1fr; }
  .dirtable td.cell--name::before { display: none; }
  .countstrip { gap: var(--space-xs) var(--space-md); }
  .count dd { font-size: var(--step-1); }
  .topnav { margin-left: 0; }
}

/* ── Milestone 2 layout corrections, made against a real render ──────────────
 * The first 1440px screenshot pushed the People column outside the viewport.
 * Three causes, all of them "the honest thing said at full length in every
 * row": the status disclaimer, the plan detail, and generous cell padding.
 * A column the operator cannot see is worse than a quieter note, so the
 * disclaimer moved into the column header and the rest is tightened here. */
.th__note {
  display: block;
  font-size: var(--step--1);
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  font-style: italic;
  color: hsl(var(--text-dim));
  opacity: 0.9;
}
.dirtable td { padding: var(--space-sm) var(--space-sm); }
.dirtable thead th { padding: var(--space-sm); }
.cell--name { min-width: 12rem; }
/* Long plan detail is clipped rather than wrapped over four lines. The full
 * value stays reachable through the title attribute. */
.rowmeta--clip {
  max-width: 12rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The timestamp, isolated so visual regression can mask the volatile value
 * without masking the activity badge beside it. */
.stamp { display: inline-block; white-space: nowrap; }

@media (max-width: 767px) {
  .th__note { display: none; }
  .rowmeta--clip { max-width: none; white-space: normal; overflow: visible; }
}

/* People: three small figures on one line rather than three stacked phrases.
 * Spelled out, this column overflowed 1440px and the wrapper clipped it -- the
 * counts were rendered and unreadable, which is the worst of both. */
.people { margin-right: var(--space-sm); white-space: nowrap; }
.people .rowmeta { margin-left: 0.15rem; font-size: var(--step--1); }
.dirtable td:last-child { white-space: nowrap; }
/* The enabled note wraps rather than forcing the status column wider. */
.badge--warn { white-space: normal; }

/* ── Overflow containment ────────────────────────────────────────────────────
 * `.main` is a grid item of `.shell`, and a grid item's default
 * `min-width: auto` refuses to shrink below its content's min-content width.
 * The directory table is ~1150px at its narrowest, so at 768 and 1024 the grid
 * item grew to fit it and the PAGE scrolled sideways -- 384px of it -- even
 * though `.tablewrap` already had `overflow-x: auto`. The wrapper never got the
 * chance to scroll because nothing ever constrained it.
 *
 * `min-width: 0` on both is the fix: the grid item may now be narrower than its
 * content, and the wrapper's own overflow takes over. */
.main { min-width: 0; }
.tablewrap { min-width: 0; max-width: 100%; }

/* The status disclaimer, carried by whichever element is on screen.
 * At >= 768 the column header has it and the per-row copy would repeat it once
 * per row; below 768 there is no header, so the row-level note is all there is.
 * It is never absent from both. */
@media (min-width: 768px) {
  .statusnote { display: none; }
}

/* ── Milestone 3: platform totals ────────────────────────────────────────────
 *
 * Deliberately NOT the `.bento`/`.panel` grid the overview uses. That grid is
 * uniform by design — four equal cards of session facts. This page has one
 * job the overview does not: every number here carries a definition and a list
 * of what it excludes, and those qualifiers are as load-bearing as the digits.
 * A uniform card would let the number dominate and the qualifier disappear,
 * which is exactly the failure Milestone 2 shipped twice.
 *
 * So the hierarchy is carried by SCALE CONTRAST inside each tile — a display
 * figure, then a definition at body size, then a dense qualifier list at the
 * smallest step — rather than by giving each tile equal weight in a grid.
 */

.stampline {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-xs);
  margin: calc(var(--space-md) * -1) 0 var(--space-lg);
  font-size: var(--step--1);
  color: hsl(var(--text-dim));
}
.stampline__time,
.stampline__took {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: hsl(var(--text-muted));
  letter-spacing: 0.02em;
}
.stampline__sep { color: hsl(var(--line-strong)); }
.stampline__note { flex: 1 1 18rem; }

.tiles {
  display: grid;
  gap: var(--space-md);
  /* 20rem, not 15rem: a tile has to hold a definition sentence and a
   * qualifier list without either wrapping into confetti. Below that the
   * single column is the correct answer, not a narrower tile.
   *
   * `min(20rem, 100%)`, NOT a bare 20rem. 20rem IS 320px, which is the
   * narrowest breakpoint this project tests -- so a bare minmax(20rem, 1fr)
   * makes the track wider than the viewport once the page padding is counted,
   * and the whole page scrolls sideways by exactly that padding. Measured at
   * 12px, and it rendered perfectly well at every other width; only the 320
   * Playwright project saw it.
   *
   * This is the same class of defect Milestone 2 shipped (a column clipped
   * outside a 1440px viewport) and the reason the overflow assertion is
   * numeric rather than eyeballed. */
  grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr));
  align-items: start;
}

.tile {
  position: relative;
  background: hsl(var(--surface) / 0.7);
  border: 1px solid hsl(var(--line));
  border-radius: var(--radius);
  /* Uneven padding on purpose: the figure needs air above it, the qualifier
   * list needs none below. Uniform padding is what makes a layout read as a
   * template. */
  padding: var(--space-md) var(--space-md) var(--space-sm);
  box-shadow: 0 1px 0 hsl(0 0% 100% / 0.04) inset;
}
/* A hairline of brand blue along the top edge, so a tile reads as an
 * instrument rather than as a generic card. Blue, never orange: orange is
 * reserved for "live / acting as someone else" from Milestone 5, and it only
 * works as that signal if nothing else has spent it. */
.tile::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 2px;
  border-radius: var(--radius) var(--radius) 0 0;
  background: linear-gradient(90deg,
    hsl(var(--brand-blue-lift) / 0.7),
    hsl(var(--brand-blue-lift) / 0));
}
.tile--out { border-color: hsl(var(--danger) / 0.35); }
.tile--out::before { background: linear-gradient(90deg, hsl(var(--danger) / 0.6), hsl(var(--danger) / 0)); }

.tile__label {
  margin: 0 0 var(--space-2xs);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: hsl(var(--text-dim));
}
.tile__value {
  margin: 0;
  font-size: var(--step-3);
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: hsl(var(--text));
}
.tile__value--out {
  color: hsl(var(--text-dim));
  font-weight: 400;
}
.tile__reason {
  margin: var(--space-2xs) 0 0;
  font-size: var(--step--1);
  color: hsl(var(--danger) / 0.95);
}
.tile__zero {
  margin: var(--space-2xs) 0 0;
  font-size: var(--step--1);
  color: hsl(var(--text-muted));
}
.tile__def {
  margin: var(--space-sm) 0 0;
  font-size: var(--step--1);
  line-height: 1.5;
  color: hsl(var(--text-muted));
}

.tile__parts {
  margin: var(--space-md) 0 0;
  padding: var(--space-sm) 0 0;
  border-top: 1px solid hsl(var(--line));
  display: grid;
  gap: var(--space-xs);
}
.part {
  display: grid;
  grid-template-columns: 1fr auto;
  column-gap: var(--space-sm);
  align-items: baseline;
}
.part__label {
  margin: 0;
  font-size: var(--step--1);
  color: hsl(var(--text-muted));
}
.part__value {
  margin: 0;
  font-size: var(--step--1);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: hsl(var(--text));
}
.part__note {
  grid-column: 1 / -1;
  margin: var(--space-2xs) 0 0;
  font-size: var(--step--1);
  line-height: 1.45;
  color: hsl(var(--text-dim));
}
.part--anomaly .part__label,
.part--anomaly .part__value { color: hsl(var(--warning)); }
.part__flag {
  display: inline-grid;
  place-items: center;
  width: 1rem;
  height: 1rem;
  margin-left: var(--space-2xs);
  border-radius: 50%;
  background: hsl(var(--warning) / 0.18);
  color: hsl(var(--warning));
  font-size: 0.6875rem;
  font-weight: 800;
  line-height: 1;
}

@media (max-width: 26rem) {
  /* At 320px the two-column part row wraps the label into three lines beside
   * a two-character number. Stacking is the correct answer, and it is done by
   * changing the GRID rather than by hiding the note — the note is the part
   * that carries the meaning. */
  .part { grid-template-columns: 1fr; }
  .part__value { margin-top: var(--space-2xs); }
}

/* ── Milestone 4: the tenant detail page ─────────────────────────────────────
 *
 * Reuses `.tablewrap` + `.dirtable` rather than growing a second table style:
 * the people tables have the same problem the directory has and the same
 * answer already exists. The first version of this page used a bare `.table`
 * with no wrapper and no `data-label`, and at 320px it was 725px wide -- 438px
 * of horizontal page scroll. Caught by the numeric overflow assertion, not by
 * a screenshot, which is the second time in this programme that assertion has
 * earned its place.
 *
 * What remained after switching to the shared mechanism was 26px, and the
 * cause is worth writing down because it is not a layout bug: an email address
 * is a single unbreakable word. In the stacked-card layout each cell is a
 * two-column grid, so the value column is ~60% of 320px and
 * `manager@northwind.example` does not fit in it.
 *
 * `overflow-wrap: anywhere` is correct here specifically BECAUSE these are
 * addresses rather than prose -- breaking one mid-token is ugly and readable,
 * while truncating it with an ellipsis would hide the characters that
 * distinguish two operators at the same company. This page exists so the
 * founder can contact somebody; a contact detail that is cut off defeats it.
 *
 * `min-width: 0` on the cell is what actually lets the wrap happen: a grid
 * item's default `min-width: auto` refuses to shrink below its content's
 * min-content width, which for an unbreakable token is the whole token. Same
 * mechanism as the `.main`/`.tablewrap` fix Milestone 2 needed, in a third
 * place. */
.dirtable td { min-width: 0; }
.dirtable td .sub,
.dirtable td .strong { overflow-wrap: anywhere; min-width: 0; }

/* The identity panel. A two-column grid at width, one column below 768 --
 * each fact is a label, a value and sometimes a qualifier, and the qualifier
 * is as load-bearing as the value (the status disclaimer lives here). */
/* NOT `.fact`. That class already exists (line ~482) and belongs to the
 * overview page's session panel, where it is a two-column grid with its own
 * `.fact dt` / `.fact dd` rules -- and those outrank a plain `.fact__label`
 * on specificity, so reusing the name gave this page a layout it never asked
 * for: the label and the value side by side in a 252px column, with a long
 * email broken into six-character fragments.
 *
 * Found by LOOKING at the rendered page. Every assertion passed: the values
 * were present, correct, and in the DOM. This is the fourth defect in this
 * programme found that way and the first caused by a class-name collision.
 *
 * These facts STACK, deliberately. An operator email is the longest value on
 * this page and the one most worth reading intact. */
.factgrid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
  gap: var(--space-md) var(--space-lg);
  margin: 0;
}
.acctfact { min-width: 0; }
.acctfact__label {
  margin: 0;
  font-size: var(--step--1);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: hsl(var(--text-dim));
}
.acctfact__value {
  margin: var(--space-2xs) 0 0;
  font-size: var(--step-1);
  color: hsl(var(--text));
  overflow-wrap: anywhere;
}
.acctfact__note {
  margin: var(--space-2xs) 0 0;
  font-size: var(--step--1);
  line-height: 1.45;
  color: hsl(var(--text-dim));
}
.acctfact__note--warn { color: hsl(var(--warning)); }

/* `.strong` and `.sub` did not exist either -- they were invented by this
 * page's markup and rendered as bare inline spans, so a name and a username
 * in one cell came out as "Aisha Rahmannorthwind.owner" and an email and a
 * phone number as "ops@northwind.example+60123100001". Both read as corrupted
 * data rather than as a layout fault, which is what makes them worth a rule
 * of their own rather than a `<br>`. */
.strong {
  display: block;
  font-weight: 700;
  color: hsl(var(--text));
  overflow-wrap: anywhere;
}
.sub {
  display: block;
  font-size: var(--step--1);
  color: hsl(var(--text-dim));
  overflow-wrap: anywhere;
}

/* A panel groups one kind of fact. Unlike the totals page's tiles these are
 * sections of one document rather than independent cards. */
.panel__title { margin: 0 0 var(--space-2xs); font-size: var(--step-1); }
.panel__subtitle {
  margin: var(--space-lg) 0 var(--space-xs);
  font-size: var(--step-0);
  color: hsl(var(--text-dim));
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
.panel__sub {
  margin: 0 0 var(--space-md);
  color: hsl(var(--text-dim));
  font-size: var(--step--1);
}

/* The activity figures are smaller than the count tiles on purpose: they are
 * timestamps rather than headline numbers, and rendering a date at headline
 * size makes the page look like it has seven headlines. */
.tiles--compact .tile__value { font-size: var(--step-2); }
/* "never" is a real answer and must not read as a missing value -- it is the
 * difference between a tenant who has taken no bookings and one whose booking
 * count could not be read. */
.tile__value--never { color: hsl(var(--text-dim)); font-style: italic; }
.tile__flag {
  margin-left: var(--space-xs);
  font-size: var(--step--1);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: hsl(var(--text-dim));
}

.badges { display: flex; flex-wrap: wrap; gap: var(--space-xs); margin: var(--space-sm) 0 0; }

/* `.empty` is deliberately NOT redefined here. It belongs to the directory's
 * empty state -- a dashed, centred box meaning "nothing matched" -- and this
 * page uses it for exactly one thing that IS an absence: an account where
 * nobody can currently sign in. Adding rules to it here would restyle the
 * directory's empty state as a side effect of building this page.
 *
 * The rental-customer figure was in an `.empty` box in the first version, and
 * it is a real count of real people. A number rendered inside a "nothing here"
 * box says the opposite of what the number says. It gets its own treatment. */
.countnote {
  display: flex;
  align-items: baseline;
  gap: var(--space-md);
  padding: var(--space-md) 0 0;
  border-top: 1px solid hsl(var(--line));
}
.countnote__value {
  margin: 0;
  font-size: var(--step-2);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: hsl(var(--text));
}
.countnote__body {
  margin: 0;
  max-width: 60ch;
  font-size: var(--step--1);
  line-height: 1.5;
  color: hsl(var(--text-dim));
}
.rowname--link { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.rowname--link:hover { color: hsl(var(--brand-orange)); }
.sub--good { color: hsl(var(--success)); }
.sub--neutral { color: hsl(var(--text-dim)); }

/* ── The people tables stack below 1024, not below 768 ───────────────────────
 *
 * The directory's table stacks at 767 and that is right for it: six columns of
 * short values (a status word, a date, three counts). These tables carry five
 * columns of PROSE -- a full name, an email address, a phone number, a role
 * plus its state, and a sentence about Keycloak -- and at 768 that left each
 * column around 60px, which broke `lockedout.owner` into `lockedo / ut.owner`
 * and `ops@lockedout.example` into three fragments.
 *
 * Found by looking at the 768px baseline. It is legible and it is wrong: this
 * page exists so the founder can contact somebody, and a contact detail
 * reassembled by eye across three lines is a worse answer than a stacked card.
 *
 * Scoped to `.panel .dirtable` so the directory keeps its own breakpoint. The
 * card rules are the 767 block's, applied over a wider range rather than
 * rewritten -- one mechanism, two thresholds. */
@media (min-width: 768px) and (max-width: 1023px) {
  .panel .dirtable thead { display: none; }
  .panel .dirtable,
  .panel .dirtable tbody,
  .panel .dirtable tr,
  .panel .dirtable td { display: block; width: 100%; }
  .panel .tablewrap { border: 0; background: transparent; overflow-x: visible; }
  .panel .dirtable tbody tr {
    border: 1px solid hsl(var(--line));
    border-radius: var(--radius);
    background: hsl(var(--surface) / 0.6);
    margin-bottom: var(--space-sm);
    padding: var(--space-xs) 0;
  }
  .panel .dirtable td {
    display: grid;
    grid-template-columns: minmax(6.5rem, 30%) 1fr;
    gap: var(--space-sm);
    padding: var(--space-2xs) var(--space-sm);
  }
  .panel .dirtable td::before {
    content: attr(data-label);
    font-size: var(--step--1);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: hsl(var(--text-dim));
  }
  .panel .dirtable td.cell--name { grid-template-columns: 1fr; }
  .panel .dirtable td.cell--name::before { display: none; }
}

/* At 1024 and up the table is a table again and the columns have room, so the
 * name column is given enough of it that a username never wraps at all. */
@media (min-width: 1024px) {
  .panel .dirtable td.cell--name { min-width: 11rem; }
}

/* ── Milestone 5: acting as somebody ─────────────────────────────────────────
 *
 * CLASS NAMES CHECKED against every selector already in this file before a
 * single one was written. Milestone 4 collided with `.fact` and `.empty` and
 * invented `.strong` and `.sub`, which did not exist -- three defects in one
 * cause, every assertion passing, and the visual-regression baselines generated
 * FROM the broken render, so they encoded it. `.impers*` and `.actingas*` are
 * new prefixes and collide with nothing.
 *
 * Every var() below is checked against :root. The real token names are
 * --warning, --danger, --success and --brand-orange; --warn, --ok and --accent
 * do NOT exist, and a var() naming an undefined token invalidates the whole
 * declaration silently. Milestone 4 shipped four dead rules that way.
 *
 * DESIGN INTENT: this is the one element on the console that must interrupt.
 * Everything else here is an instrument panel -- quiet surfaces, tabular
 * figures, colour used semantically. This is the exception, and it earns it: it
 * is the only thing on screen that says an action taken now writes to a real
 * tenant's data as a real person. So it takes the brand orange rather than a
 * status colour (orange is used nowhere else as a full-width field), sits above
 * the page rather than inside it, and STICKS -- scrolling a long tenant page
 * must not scroll away the reason to be careful. */
.actingas {
  position: sticky;
  top: 0;
  z-index: 20;
  display: grid;
  gap: var(--space-2xs);
  padding: var(--space-sm) var(--space-md);
  border-bottom: 1px solid hsl(var(--brand-orange) / 0.5);
  /* A gradient rather than a flat tint so it reads as a layer over the page
   * rather than as another panel in it. */
  background:
    linear-gradient(180deg,
      hsl(var(--brand-orange) / 0.16),
      hsl(var(--brand-orange) / 0.06)),
    hsl(var(--ground));
  box-shadow: 0 10px 24px -18px hsl(0 0% 0% / 0.9);
}
.actingas__who {
  margin: 0;
  font-size: var(--step-0);
  color: hsl(var(--text));
}
.actingas__who strong { color: hsl(var(--brand-orange)); font-weight: 700; }
.actingas__who a { color: hsl(var(--text)); text-underline-offset: 0.2em; }
.actingas__who a:hover { color: hsl(var(--brand-orange)); }
.actingas__note {
  margin: 0;
  font-size: var(--step--1);
  color: hsl(var(--text-muted));
  max-width: 68ch;
}
/* The uncertainty line is deliberately the quietest thing in the banner. It is
 * true and it must be readable, and it must not compete with the sentence that
 * says side effects are real. */
.actingas__meta {
  margin: 0;
  font-size: var(--step--1);
  color: hsl(var(--text-dim));
}
.actingas__end { margin-top: var(--space-2xs); }
.actingas__end .linkish {
  border-color: hsl(var(--brand-orange) / 0.6);
  color: hsl(var(--text));
}
.actingas__end .linkish:hover {
  border-color: hsl(var(--brand-orange));
  background: hsl(var(--brand-orange) / 0.12);
}

/* The per-person action cell. Narrow on purpose: this table already carries
 * five columns of prose and stacks below 1024, and a sixth wide column is how
 * the 320px overflow assertion earns its place again. */
.impers { white-space: nowrap; }
/* ── FOUND BY LOOKING AT THE RENDERED PAGE ──────────────────────────────────
 *
 * At 0.45 opacity on the border and inherited text colour, this button rendered
 * as an ordinary `.linkish` chip -- indistinguishable from "Sign out" in the top
 * bar. The LEAST and the MOST consequential controls in the console looked the
 * same, on a page whose whole discipline is that a value and its meaning travel
 * together.
 *
 * No assertion could see it: the markup was correct, the testid was present, the
 * text was right. That is the fifth defect in this programme found by a person
 * looking at a render, and the reason the practice is load-bearing rather than
 * advisory.
 *
 * It now carries the brand orange in the TEXT as well as the border, which is
 * the only orange-on-surface control on any page. */
.impers__btn {
  border-color: hsl(var(--brand-orange) / 0.55);
  background: hsl(var(--brand-orange) / 0.08);
  color: hsl(var(--brand-orange));
  font-weight: 600;
}
.impers__btn:hover {
  border-color: hsl(var(--brand-orange));
  background: hsl(var(--brand-orange) / 0.18);
  color: hsl(var(--brand-orange));
}

/* The population-level refusal, on the tables that have no action column.
 *
 * Also found by looking: it rendered as a second `.panel__sub` paragraph
 * directly under the one describing the population, and nothing told the reader
 * that the two answer DIFFERENT questions -- "what are these people" and "can I
 * sign in as them". A left rule and the orange marker tie it to the action
 * vocabulary instead of to the descriptive prose. */
.impers__note {
  margin: 0 0 var(--space-sm);
  padding-left: var(--space-sm);
  border-left: 2px solid hsl(var(--brand-orange) / 0.5);
  font-size: var(--step--1);
  color: hsl(var(--text-muted));
  max-width: 78ch;
}
.impers__btn:focus-visible {
  outline: 2px solid hsl(var(--brand-orange));
  outline-offset: 2px;
}
/* A refusal is not an error and is not disabled-looking. It is a fact about
 * this person, and it carries the full sentence in its title. */
.impers__why {
  display: inline-block;
  font-size: var(--step--1);
  color: hsl(var(--text-dim));
  border-bottom: 1px dotted hsl(var(--line-strong));
  cursor: help;
  white-space: normal;
}

/* Stacked cards: the banner stops sticking, because on a phone a sticky block
 * this tall takes a third of the viewport for the whole session. */
@media (max-width: 767px) {
  .actingas { position: static; }
  .impers { white-space: normal; }
}

/* ─────────────────────────────────────────────────────────────────────────
   MILESTONE 6 — signing in as a rental customer.

   Class names checked against every existing selector in this file before a
   line of it was written: `custimp*` and `handoff*` collide with nothing.
   Milestone 4 shipped three rendering defects in one cause by colliding with
   `.fact` and `.empty` and inventing `.strong` and `.sub`, and every value on
   the page was correct while every assertion passed. Every var() below is one
   of the twenty-eight declared on :root.

   THE SHAPE IS THE DISCLOSURE DECISION. One input and no table, because rental
   customers are counted and never listed (Milestone 4) and this milestone
   answers one question at a time rather than reopening that.
   ───────────────────────────────────────────────────────────────────────── */

.custimp {
  margin-top: var(--space-md);
  padding: var(--space-md);
  border: 1px solid hsl(var(--line));
  border-radius: var(--radius);
  background: hsl(var(--surface-lift));
}

.custimp__form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-sm);
  margin-top: var(--space-sm);
}

.custimp__field { flex: 1 1 22rem; min-width: 0; }

.custimp__label {
  display: block;
  font-size: var(--step--1);
  color: hsl(var(--text-dim));
  margin-bottom: var(--space-2xs);
}

.custimp__input {
  width: 100%;
  padding: var(--space-xs) var(--space-sm);
  font: inherit;
  font-family: var(--font-mono);
  color: hsl(var(--text));
  background: hsl(var(--ground));
  border: 1px solid hsl(var(--line-strong));
  border-radius: var(--radius-sm);
  transition: border-color var(--duration) var(--ease);
}

.custimp__input:focus-visible {
  outline: 2px solid hsl(var(--brand-orange));
  outline-offset: 1px;
  border-color: hsl(var(--brand-orange));
}

/* The same orange-on-surface treatment the operator control got in Milestone 5,
   and for the same reason: this is the most consequential control on the page
   and a 45%-opacity border at this size amounts to nothing. That defect was
   found by a person LOOKING at the render, with every assertion passing. */
.custimp__btn {
  color: hsl(var(--brand-orange));
  font-weight: 650;
  border: 1px solid hsl(var(--brand-orange) / 0.55);
  border-radius: var(--radius-sm);
  padding: var(--space-xs) var(--space-sm);
  background: transparent;
  cursor: pointer;
  transition: background var(--duration) var(--ease), border-color var(--duration) var(--ease);
}

.custimp__btn:hover {
  background: hsl(var(--brand-orange) / 0.10);
  border-color: hsl(var(--brand-orange));
}

.custimp__btn:focus-visible {
  outline: 2px solid hsl(var(--brand-orange));
  outline-offset: 2px;
}

.custimp__note {
  margin-top: var(--space-sm);
  font-size: var(--step--1);
  color: hsl(var(--warning));
  max-width: 62ch;
}

/* A refusal is an ANSWER, not an error state. It gets its own block so it
   cannot be mistaken for the C8 warning above it — Milestone 5 shipped a
   population-level refusal that rendered as a second paragraph of body prose
   with nothing to say it answered a different question. */
.custimp__refusal {
  margin-top: var(--space-sm);
  padding: var(--space-sm);
  border-left: 3px solid hsl(var(--danger));
  background: hsl(var(--danger) / 0.07);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  max-width: 70ch;
}

.custimp__refusal-label {
  display: block;
  font-size: var(--step--1);
  font-weight: 650;
  color: hsl(var(--danger));
  margin-bottom: var(--space-2xs);
}

.custimp__refusal-body { font-size: var(--step--1); color: hsl(var(--text)); }

.custimp__scope {
  margin-top: var(--space-sm);
  font-size: var(--step--1);
  color: hsl(var(--text-muted));
  max-width: 70ch;
}

/* ── The handoff page ──────────────────────────────────────────────────── */

.handoff { max-width: 74ch; }

.handoff__who {
  font-size: var(--step-1);
  margin-bottom: var(--space-xs);
}

.handoff__who strong { color: hsl(var(--brand-orange)); font-weight: 700; }

.handoff__meta {
  font-size: var(--step--1);
  color: hsl(var(--text-dim));
  margin-bottom: var(--space-md);
}

.handoff__step {
  margin-top: var(--space-md);
  padding-top: var(--space-md);
  border-top: 1px solid hsl(var(--line));
}

.handoff__step-title {
  font-size: var(--step-0);
  font-weight: 650;
  margin-bottom: var(--space-2xs);
}

.handoff__step-note {
  font-size: var(--step--1);
  color: hsl(var(--text-dim));
  margin-bottom: var(--space-sm);
  max-width: 68ch;
}

/* The token lives here and nowhere else. Selectable, wrapped, monospace, and
   never in a URL — so it cannot reach a log line, a Referer header, or the
   browser's history. */
/* NO min-height. It fought the element's own `rows` and left the last visible
   line CLIPPED HORIZONTALLY THROUGH THE GLYPHS -- half a row of base64 showing
   at the bottom of the box, which reads as a truncated token rather than as a
   scrollable one. Found by looking at the render; no assertion could see it.
   `rows` alone gives a whole number of lines, and the box scrolls from there. */
.handoff__snippet {
  width: 100%;
  padding: var(--space-sm);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  line-height: 1.5;
  color: hsl(var(--text));
  background: hsl(var(--ground));
  border: 1px solid hsl(var(--line-strong));
  border-radius: var(--radius-sm);
  resize: vertical;
  white-space: pre-wrap;
  word-break: break-all;
}

.handoff__snippet:focus-visible {
  outline: 2px solid hsl(var(--brand-orange));
  outline-offset: 1px;
}

.handoff__open {
  display: inline-block;
  margin-top: var(--space-sm);
  color: hsl(var(--brand-orange));
  font-weight: 650;
}

.handoff__warn {
  margin-top: var(--space-md);
  padding: var(--space-sm);
  border-left: 3px solid hsl(var(--warning));
  background: hsl(var(--warning) / 0.08);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  font-size: var(--step--1);
  max-width: 70ch;
}

@media (max-width: 640px) {
  .custimp__form { flex-direction: column; align-items: stretch; }
  .custimp__btn { width: 100%; }
}

/* An outcome that is not a refusal must not be rendered inside a red-bordered
   block. Milestone 4 rendered a real count of four people inside the
   directory's "nothing matched" box; every value was correct and the frame said
   the opposite. */
/* --brand-blue is 24% lightness: a deep navy that is nearly invisible against
   this console's dark surface. Tried it, LOOKED at it, and it read as an
   unstyled block. --brand-blue-lift (60%) is the same hue at a lightness that
   survives the background, and it is what every other lifted accent here uses. */
.custimp__refusal--info {
  border-left-color: hsl(var(--brand-blue-lift));
  background: hsl(var(--brand-blue-lift) / 0.10);
}
.custimp__refusal--info .custimp__refusal-label { color: hsl(var(--brand-blue-lift)); }

/* ── A MASKED VALUE MUST BE A FIXED WIDTH, OR IT MOVES THE LINE ─────────────
   The acting-as timestamp had no rule of its own, so it rendered in the
   proportional UI face. Visual regression MASKS it — correctly, it changes
   between runs — but a proportional mask is a different WIDTH for
   "2026-08-25 20:00" than for "2026-08-26 04:32", which shifts every word after
   it and fails the snapshot on three of four breakpoints.
   That is a snapshot flaking for a real reason, and the fix is the one the
   brief asks for: find out why rather than raise the budget. Every other
   timestamp in this console is already `.mono`; this one simply was not.  */
.actingas__at { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

/* ════════════════════════════════════════════════════════════════════════════
   MILESTONE 7 — TRENDS

   Every class below is prefixed `trendwin`, `trend`, `chart` or `trendnot`.
   Checked against the 170 base class rules already in this file before a line
   of it was written: 0 collisions. Milestone 4 shipped three defects in one
   cause by colliding with `.fact` and `.empty` and inventing `.strong` and
   `.sub` — names concatenated, emails broke into fragments, and every
   assertion passed.

   Every var() below is one of the 30 defined on :root. Checked, not assumed —
   an undefined custom property is not an error, it is an empty string, so the
   declaration silently does nothing.
   ════════════════════════════════════════════════════════════════════════════ */

/* ── The range control ─────────────────────────────────────────────────────
   Three links, styled as a segmented control. Not a <select>: this console
   ships no JavaScript, so a select could not submit, and its own CSP would
   refuse an inline handler anyway. */
.trendwin {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2xs);
  margin-top: var(--space-md);
}
.trendwin__opt {
  padding: 0.35rem 0.75rem;
  border: 1px solid hsl(var(--line));
  border-radius: var(--radius-sm);
  color: hsl(var(--text-muted));
  text-decoration: none;
  font-size: var(--step--1);
  transition: border-color var(--duration) var(--ease), color var(--duration) var(--ease);
}
.trendwin__opt:hover { border-color: hsl(var(--line-strong)); color: hsl(var(--text)); }
.trendwin__opt:focus-visible { outline: 2px solid hsl(var(--brand-orange)); outline-offset: 2px; }
/* The current range is stated by colour AND by weight AND by aria-current.
   Colour alone is a contrast trap and would leave the selected state invisible
   in a monochrome screenshot. */
.trendwin__opt--on {
  border-color: hsl(var(--brand-orange));
  color: hsl(var(--brand-orange));
  font-weight: 600;
}
.trendwin__grain {
  margin-left: auto;
  color: hsl(var(--text-dim));
  font-size: var(--step--1);
}

/* ── Panels ───────────────────────────────────────────────────────────────── */
.trends {
  display: grid;
  gap: var(--space-lg);
  margin-top: var(--space-lg);
}
.trend {
  background: hsl(var(--surface) / 0.7);
  border: 1px solid hsl(var(--line));
  border-radius: var(--radius);
  /* Uneven, matching .tile: the heading needs air above, the last chart's
     footnote needs none below. */
  padding: var(--space-md) var(--space-md) var(--space-sm);
  box-shadow: 0 1px 0 hsl(0 0% 100% / 0.04) inset;
}
.trend__title {
  margin: 0;
  font-size: var(--step-1);
  letter-spacing: -0.01em;
}
.trend__intro {
  margin: var(--space-2xs) 0 0;
  color: hsl(var(--text-muted));
  font-size: var(--step--1);
  max-width: 70ch;
}

/* ── One chart ────────────────────────────────────────────────────────────── */
.chart { margin-top: var(--space-md); }
.chart + .chart {
  /* Two charts in one panel share a y-scale and are read together, so the rule
     between them is a hairline rather than a full separator. */
  border-top: 1px solid hsl(var(--line));
  padding-top: var(--space-md);
}
.chart__label {
  margin: 0;
  font-size: var(--step-0);
  font-weight: 600;
}
.chart__peak {
  margin: 0 0 var(--space-2xs);
  color: hsl(var(--text-dim));
  font-size: var(--step--1);
}
.chart__peaknum { font-family: var(--font-mono); font-variant-numeric: tabular-nums; color: hsl(var(--text-muted)); }

/* The SVG is sized by CSS and drawn by presentation attributes.
   `style-src 'self'` governs inline style ATTRIBUTES as well as <style>
   elements, so nothing in the markup may carry style="…" — a bar sized that way
   is blocked and renders as nothing at all, silently. See internal/ui/trends.go. */
.chart__svg {
  display: block;
  width: 100%;
  height: auto;
  /* The viewBox is 640x140; this keeps the bars a usable height on a phone
     without letting them tower on a wide screen. */
  max-height: 220px;
  overflow: visible;
}
.chart__base { stroke: hsl(var(--line-strong)); stroke-width: 1; }
.chart__bar {
  fill: hsl(var(--brand-orange) / 0.85);
  transition: fill var(--duration) var(--ease);
}
.chart__bar:hover { fill: hsl(var(--brand-orange)); }
/* An empty period still gets a bar — a flat one. A period that disappears when
   it is empty compresses time and makes a decline look like a plateau. */
.chart__bar--zero { fill: hsl(var(--line-strong)); }
.chart__tick {
  fill: hsl(var(--text-dim));
  font-family: var(--font-mono);
  font-size: 10px;
}

/* ── The arithmetic under the picture ─────────────────────────────────────── */
.chart__sums {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm) var(--space-md);
  margin: var(--space-sm) 0 0;
}
.chart__sum { display: flex; flex-direction: column; gap: 2px; }
.chart__sum dt {
  color: hsl(var(--text-dim));
  font-size: var(--step--1);
}
.chart__sum dd {
  margin: 0;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: hsl(var(--text-muted));
}
.chart__sum--total dd { color: hsl(var(--text)); font-weight: 600; }

/* The invariant, ON THE PAGE. Milestone 3 shipped a census summing to 15 under
   a headline of 11 with six assertions over the parts and none comparing them;
   this is that comparison made visible. */
.chart__check {
  margin: var(--space-xs) 0 0;
  color: hsl(var(--text-dim));
  font-size: var(--step--1);
}
.chart__check strong { color: hsl(var(--text-muted)); }
.chart__check--bad {
  padding: var(--space-xs) var(--space-sm);
  border-left: 3px solid hsl(var(--danger));
  background: hsl(var(--danger) / 0.08);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  color: hsl(var(--text));
}
.chart__flag { color: hsl(var(--danger)); }

/* Provenance: how far back this series can see. Three states, and they are
   deliberately not all the same colour — "recorded from this release" is the
   one PRD Risk 8 is about and it must not read as routine. */
.chart__prov {
  margin: var(--space-2xs) 0 0;
  font-size: var(--step--1);
  color: hsl(var(--text-dim));
  max-width: 70ch;
}
.chart__prov--complete { color: hsl(var(--text-dim)); }
.chart__prov--partial { color: hsl(var(--warning)); }
.chart__prov--from-deploy {
  color: hsl(var(--warning));
  font-weight: 500;
}

.chart__none,
.chart__out {
  margin: var(--space-xs) 0 0;
  color: hsl(var(--text-muted));
  font-size: var(--step--1);
  max-width: 70ch;
}
/* Unavailable is never a flat zero line, and it must not look like an empty
   one either. The em dash is the same signal .tile__value--out uses. */
.chart__out { color: hsl(var(--text-dim)); }
.chart__outmark {
  font-family: var(--font-mono);
  color: hsl(var(--text-dim));
  margin-right: 0.35rem;
}

.chart__def {
  margin: var(--space-xs) 0 0;
  color: hsl(var(--text-dim));
  font-size: var(--step--1);
  max-width: 72ch;
}

/* ── The table of the same numbers ────────────────────────────────────────── */
.chart__table { margin-top: var(--space-xs); }
.chart__table summary {
  cursor: pointer;
  color: hsl(var(--text-muted));
  font-size: var(--step--1);
}
.chart__table summary:focus-visible { outline: 2px solid hsl(var(--brand-orange)); outline-offset: 2px; }
.chart__grid {
  width: 100%;
  margin-top: var(--space-xs);
  border-collapse: collapse;
  font-size: var(--step--1);
}
.chart__caption {
  text-align: left;
  color: hsl(var(--text-dim));
  padding-bottom: var(--space-2xs);
  max-width: 72ch;
}
.chart__grid th,
.chart__grid td {
  text-align: left;
  padding: 4px var(--space-xs) 4px 0;
  border-bottom: 1px solid hsl(var(--line));
  font-weight: 400;
  color: hsl(var(--text-muted));
}
.chart__num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* ── What is not charted ──────────────────────────────────────────────────── */
.trendnot {
  margin-top: var(--space-lg);
  padding: var(--space-md);
  border: 1px dashed hsl(var(--line-strong));
  border-radius: var(--radius);
}
.trendnot__title {
  margin: 0 0 var(--space-xs);
  font-size: var(--step-0);
  color: hsl(var(--text-muted));
}
.trendnot__list { margin: 0; display: grid; gap: var(--space-sm); }
.trendnot__item { display: grid; gap: 2px; }
.trendnot__label { color: hsl(var(--text)); font-weight: 600; font-size: var(--step--1); }
.trendnot__why {
  margin: 0;
  color: hsl(var(--text-dim));
  font-size: var(--step--1);
  max-width: 78ch;
}

@media (min-width: 900px) {
  /* Two panels abreast once there is room, and NOT before: a 640-unit chart
     squeezed into half of a 768px viewport loses its axis labels. */
  .trends { grid-template-columns: 1fr 1fr; }
}

/* The period still running. Always the last bar, always short, and unmarked it
   reads as a collapse on every chart every month forever. Hatched rather than
   recoloured: a different colour would be read as a different KIND of value. */
.chart__bar--partial { fill: hsl(var(--brand-orange) / 0.45); }
.chart__sofar { color: hsl(var(--text-dim)); font-weight: 400; }
.trendwin__partial {
  margin: var(--space-2xs) 0 0;
  color: hsl(var(--text-dim));
  font-size: var(--step--1);
  max-width: 78ch;
}

/* ── FOUND BY LOOKING AT THE PAGE, AND IT IS THE FIFTH OF ITS KIND ──────────
   `.stampline` carries `margin-top: calc(var(--space-md) * -1)` — a NEGATIVE
   top margin — because on /totals it sits immediately under `.page-head` and
   that pulls it into deliberate proximity with the heading.

   /trends puts the range control between them. The negative margin then pulls
   the stamp UP ONTO the line above it, and the timestamp rendered superimposed
   on the "last bar is still in progress" sentence: two legible strings drawn at
   the same coordinates, both correct, both unreadable.

   Every value was present, every assertion passed, and no screenshot existed
   yet to diff against. Reusing a class whose margin encodes an assumption about
   what precedes it is how this happens; the fix is a modifier at the one place
   the assumption does not hold, rather than changing the base rule and moving
   /totals with it. */
.stampline--below-controls { margin-top: var(--space-sm); }

/* Cards size to their own content. Without this the grid stretches every card
   in a row to the tallest, and the add-ons panel — which on the day this ships
   has an empty timeline and a one-line baseline — rendered as a half-page of
   nothing next to the two-chart GPS panel. */
.trends { align-items: start; }

/* ── A FIFTH NAV ITEM BROKE THE HEADER AT 768, AND ONLY AT 768 ──────────────
   Adding "Trends" to the nav pushed the topbar past one row on a tablet-width
   viewport: `Sign out` dropped onto a second line, alone and left-aligned, and
   every page's content moved down with it. It looked like a broken template,
   on four pages that had shipped correctly across three milestones.

   Found by LOOKING at the visual-regression DIFF instead of regenerating the
   baselines. Every one of those baselines legitimately had to change — the nav
   really did gain a link — and regenerating them without looking would have
   encoded the wrapped header as the new correct render. Milestone 4 did exactly
   that: its baselines were generated FROM a broken page, so they made the
   defect permanent and invisible.

   The remedy takes width back from decoration rather than from content: the
   "PLATFORM" scope word beside the mark is a flourish, and the nav's own gaps
   are generous for a five-item bar. Both give way before a control does. */
@media (max-width: 60rem) {
  /* Scoped to the TOPBAR. `.mark__scope` also appears on the sign-in card,
     where there is no crowding and no reason to change a page this milestone
     has nothing to do with -- an unscoped rule silently dropped the word there
     too, which the visual diff caught. */
  .topbar .mark__scope { display: none; }
  .topnav { margin-left: var(--space-sm); gap: var(--space-2xs); }
  .topnav__link { padding: var(--space-2xs); }
  .topbar { gap: var(--space-sm); }
}
