/* App Hub shell — implements the "TTM App Hub UX Design" handoff.
   Token names/values are final (from the UX design package token layer).
   Brand name/marks are runtime slots — nothing brand-literal lives here. */

@import url('/vendor/fonts.css');

/* THE TOKENS LIVE IN /brand/app-shell.css AND ONLY THERE.
   This block used to redefine every `--hub-*` value here, with LIGHT values,
   and this stylesheet loads AFTER the shell — so the shell's dark redefinitions
   were overridden and the hub had no working dark mode at all: the wordmark
   stayed light and every control kept its light colour while the ground went
   dark. The aliases in app-shell.css already map `--hub-*` onto `--tsnz-*`, so
   deleting this block is what makes the hub obey its own design system.

   Anything genuinely local to the shell goes below, and must be DERIVED from a
   token — never a literal. */
:root {
  /* Shell-only, not part of the platform token set. */
  --hub-locked-surface: var(--tsnz-locked-surface);
  --hub-locked-fg: var(--tsnz-locked-fg);
  --hub-requested-surface: var(--tsnz-requested-surface);
  --hub-requested-fg: var(--tsnz-requested-fg);
  --hub-motion-fast: var(--tsnz-motion-fast);
  --hub-motion-standard: var(--tsnz-motion-standard);
  --hub-motion-ease: var(--tsnz-motion-ease);
}

* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; }
/* Type comes from the shell's scale, not from a literal here — 24 Aug 2026,
   when the platform default moved 13/19 -> 14/20 with the Strata merge. The
   only thing this rule still says is that the lobby paints CANVAS rather than
   the shell's default sunken ground. */
body { font: var(--tsnz-fw-regular) var(--tsnz-fs-body)/var(--tsnz-lh-body) var(--hub-font-ui);
       color: var(--tsnz-ink);
       background: var(--tsnz-canvas); }
/* `:focus-visible { outline: none; box-shadow: var(--hub-focus-ring) }` lived
   here. The colour was right, but `outline: none` deleted the one layer that
   survives forced-colors / Windows High Contrast, where box-shadow is dropped
   — so the hub had no visible keyboard focus at all in that mode. The shell
   paints both layers; this rule only ever removed one of them. */
button, input, textarea { font: inherit; }

/* ------------------------------------------------------------- top bar */
.topbar { height: var(--hub-topbar-height); background: var(--hub-surface);
  border-bottom: 1px solid var(--hub-border); display: flex; align-items: center;
  padding: 0 20px; gap: 16px; position: sticky; top: 0; z-index: 20; }
/* The companion cone tile (.brand-mark) is gone — the wordmark already contains
   the cone as the A of LANE. See brandSlot() in app.js. */
.brand-slot { display: flex; align-items: center; text-decoration: none; }
/* Centred on each other, not flush left: the tracked endorsement sets the wider
   box (~103px vs the wordmark's ~95px), so left-aligning them read as an error. */
.brand-text { display: flex; flex-direction: column; align-items: center;
  line-height: 1.05; }
/* Wordmark 18 -> 21.6 -> 25.92 -> 31.104px (+20% three times); endorsement -20%
   (8.5 -> 6.8px) and held there. The text rule is the no-image fallback for the
   same slot, so it scales with the image. */
.brand-wordmark { font: 700 29.376px var(--hub-font-brand); color: var(--hub-navy); }
.brand-wordmark img { height: 31.104px; display: block; }
/* letter-spacing appends a trailing space after the final Z, which is inside the
   box being centred and would shove the glyphs ~1px left. Negative margin of one
   track pulls the box back to the visible text. */
.brand-endorse { font: 600 6.8px var(--hub-font-brand); letter-spacing: 0.24em;
  color: var(--hub-slate-600); text-transform: uppercase; margin-top: 2px;
  margin-right: -0.24em; text-align: center; }
.topbar .spacer { flex: 1; }

.orgbtn, .avatarbtn { border: 1px solid transparent; background: none; border-radius:
  var(--hub-radius-control); cursor: pointer; height: var(--tsnz-control-h); }
.orgbtn { display: flex; align-items: center; gap: 8px; padding: 0 12px;
  color: var(--hub-ink); font-weight: 500; }
.orgbtn:hover { background: var(--hub-surface-sunken); }
.orgbtn.static { cursor: default; }
.orgbtn.static:hover { background: none; }
.orgbtn i { font-size: 18px; color: var(--hub-slate-600); }
.avatarbtn { width: 40px; padding: 0; }
.avatar { width: 40px; height: 40px; border-radius: 50%; background: var(--hub-navy);
  color: var(--tsnz-on-action); display: grid; place-items: center;
  font: var(--tsnz-fw-semibold) var(--tsnz-fs-body) var(--hub-font-ui); }

/* ------------------------------------------------------------- menus */
.menu { position: absolute; top: calc(var(--hub-topbar-height) + 6px); right: 20px;
  background: var(--hub-surface); border: 1px solid var(--hub-border);
  border-radius: var(--hub-radius-control); box-shadow: 0 8px 24px rgba(14,42,71,.12);
  padding: 6px; z-index: 30; }
.menu.orgs { width: 280px; right: 72px; }
.menu.account { width: 260px; }
.menu .helper { font-size: 11px; color: var(--hub-slate-600); padding: 8px 10px 6px; }
.menu .row { display: flex; align-items: center; gap: 10px; width: 100%; min-height: 44px;
  padding: 0 10px; border: 0; background: none; border-radius: 6px; cursor: pointer;
  text-align: left; color: var(--hub-ink); }
.menu .row:hover { background: var(--hub-surface-sunken); }
.menu .row.active { background: var(--hub-requested-surface); color: var(--hub-navy);
  font-weight: 600; }
.menu .row i { font-size: 18px; color: var(--hub-slate-600); }
.menu .who { padding: 10px 10px 8px; }
.menu .who .nm { font-weight: 600; }
.menu .who .em { font: 400 12px var(--hub-font-mono); color: var(--hub-slate-600); }
.menu .who .rl { font-size: 11px; color: var(--hub-slate-300); margin-top: 2px; }
.menu hr { border: 0; border-top: 1px solid var(--hub-border); margin: 6px 0; }

/* ------------------------------------------------------------- layout */
/* ONE measure, from the platform. This was `.shellwrap` 1120 wrapping
   `.content` 1040 — two max-widths where the inner is exactly the outer minus
   its padding, i.e. a no-op that reads as a decision. */
.shellwrap { max-width: var(--tsnz-measure-wide); margin: 0 auto;
  padding: var(--tsnz-space-8) var(--tsnz-space-10) var(--tsnz-space-16); }
.content { width: 100%; margin: 0 auto; }
/* h1 and .subline are the platform's `.tsnz-page-head` / `.tsnz-page-meta`
   now; these two rules said the same thing in local numbers. */
h1 { margin: 0 0 4px; }
.subline { color: var(--hub-slate-600); margin: 0 0 24px; }

/* ------------------------------------------------------------- banners */
.banner { display: flex; gap: var(--tsnz-space-3); align-items: flex-start; border-radius:
  var(--hub-radius-control); padding: var(--tsnz-space-3) var(--tsnz-space-4);
  margin-bottom: var(--tsnz-space-4);
  font-size: var(--tsnz-fs-body-small); line-height: var(--tsnz-lh-body-small); }
/* `display: flex` beats the UA's `[hidden]` rule in every browser that does not
   mark it `!important`, so a banner written hidden renders anyway. Stated once,
   here, rather than discovered once per page. */
.banner[hidden] { display: none; }
.banner.info { background: var(--hub-requested-surface); color: var(--hub-navy); }
.banner.quiet { background: var(--hub-surface-sunken); color: var(--hub-slate-600); }
.banner i { font-size: var(--tsnz-icon-md); flex: 0 0 auto; margin-top: 1px; }
.banner .banner-action { margin-top: var(--tsnz-space-2); }
.returnpill { display: inline-flex; align-items: center; gap: 6px; margin-top: 8px;
  background: var(--hub-surface); border: 1px solid var(--hub-border); border-radius: 999px;
  padding: 4px 12px; font-weight: 500; color: var(--hub-navy); font-size: 12px; }

/* ------------------------------------------------------------- auth card */
.authwrap { min-height: calc(100vh - var(--hub-topbar-height)); display: flex;
  flex-direction: column; align-items: center; padding: 48px 20px; }
.authcard { width: 400px; max-width: 100%; background: var(--hub-surface);
  border: 1px solid var(--hub-border); border-radius: var(--hub-radius-control);
  padding: 28px; }
.authcard h1 { margin-bottom: 20px; }
/* A LABEL, A CONTROL, AND THE TEXT THAT BELONGS TO THEM — in that order, one
   spacing step apart, every step from the 4px scale. The sizes are the
   platform's: the control is `--tsnz-control-h-field` (48), not a literal, and
   the type is `body`. The values replaced here were 6px, 14px and a bare 14px
   font size — three numbers off the scale in one four-line component. */
.field { margin-bottom: var(--tsnz-space-4); }
.field label { display: block; font-weight: var(--tsnz-fw-medium);
  margin-bottom: var(--tsnz-space-2); }
.field input { width: 100%; height: var(--tsnz-control-h-field);
  padding: 0 var(--tsnz-space-3); border: 1px solid var(--hub-border-strong);
  border-radius: var(--hub-radius-control);
  font: var(--tsnz-fw-regular) var(--tsnz-fs-body)/var(--tsnz-lh-body) var(--hub-font-ui);
  /* AN INPUT PAINTS ITS OWN PAIR. Neither the shell nor this sheet styled the
     field surface, and nothing in the estate declares `color-scheme`, so in
     dark mode a password box kept the user agent's white ground and black ink
     — the one control on this page a person has to look at while typing. */
  background: var(--tsnz-surface); color: var(--tsnz-ink); }
/* The shell's focus rule is `:where(a, button, .btn, [role="button"], [tabindex])`
   — it does not reach a text input, and no platform component styles one yet.
   The ring is still the PLATFORM's: its tokens, and BOTH layers. The `outline`
   is what survives forced-colors / Windows High Contrast, where `box-shadow` is
   dropped; the halo is what keeps the ring visible against a filled ground.
   (The durable fix is `input, select, textarea` in the shell's own selector.) */
.field input:focus-visible {
  outline: var(--tsnz-focus-width) solid var(--tsnz-focus);
  outline-offset: var(--tsnz-focus-offset);
  box-shadow: 0 0 0 var(--tsnz-focus-offset) var(--tsnz-surface); }
/* The control carries the failure too, not only the message below it. */
.field input[aria-invalid="true"] { border-color: var(--tsnz-danger-border); }
.field input[readonly] { background: var(--hub-surface-sunken);
  font-family: var(--hub-font-mono); font-size: var(--tsnz-fs-body-small); }

/* A VALIDATION ERROR, AT THE CONTROL IT IS ABOUT — the estate's `.field-error`
   (`tmd-selection-assistant/web/app.css`), same class, same treatment: the
   danger FAMILY as a bg/border/fg triple, never a bare colour, with a left
   accent so it reads as a mark against the control rather than a panel of its
   own, and an icon so the state is not carried by colour alone.
   It was `.err` here and `.field-error` there — one job, two names.

   IT IS NOT A `.field-note`. A note reports the outcome of something the person
   asked for, including a failure that is nobody's fault; an error says an answer
   is missing or wrong. Collapsing them is how "we could not reach the provider"
   comes to read as "you typed this wrong". A provider outage on this page stays
   a page-level `.banner bad` and never appears under a field. */
.field-error { display: flex; align-items: flex-start; gap: var(--tsnz-space-2);
  margin-top: var(--tsnz-space-2);
  padding: var(--tsnz-space-2) var(--tsnz-space-3);
  border-left: 3px solid var(--tsnz-danger-border);
  border-radius: var(--hub-radius-control);
  background: var(--tsnz-danger-bg); color: var(--tsnz-danger-fg);
  font-size: var(--tsnz-fs-body-small); line-height: var(--tsnz-lh-body-small); }
.field-error i { font-size: var(--tsnz-icon-sm); flex: none; margin-top: 1px; }
.field-error[hidden] { display: none; }

/* THE REQUIREMENT LINE — stated before the person types, never only after a
   refusal, and associated with the input by `aria-describedby` so it is read
   WITH the field instead of sitting beside it. This replaces `.rule`, whose
   bare `ph-circle` was a decorative dot with no label semantics and a -6px
   negative margin that pulled it into the control above it.

   MET CHANGES THE ICON AS WELL AS THE COLOUR. A tick and a colour say the same
   thing twice, which is the point: colour is never the only carrier. */
.field-hint { display: flex; align-items: flex-start; gap: var(--tsnz-space-2);
  margin: var(--tsnz-space-2) 0 0; color: var(--tsnz-muted);
  font-size: var(--tsnz-fs-body-small); line-height: var(--tsnz-lh-body-small); }
.field-hint i { font-size: var(--tsnz-icon-sm); flex: none; margin-top: 1px; }
.field-hint.met { color: var(--tsnz-success-fg); font-weight: var(--tsnz-fw-medium); }

/* Caps Lock: the platform shell draws the in-field indicator (brand/app-shell.css). */
.forgot { display: inline-block; margin-top: 14px; color: var(--hub-navy); font-size: 12px; }
.endorse-footer { margin-top: 28px; font: 600 9px var(--hub-font-brand);
  letter-spacing: 0.24em; color: var(--hub-slate-300); text-transform: uppercase; }
/* A DISABLED ACTION EXPLAINS WHY, adjacent to it (state contract). Not a live
   region: it changes on every keystroke, and a screen reader that reads each
   revision aloud is worse than one that reads none. */
.disable-why { margin: var(--tsnz-space-2) 0 0; color: var(--hub-slate-600);
  font-size: var(--tsnz-fs-body-small); line-height: var(--tsnz-lh-body-small); }
.disable-why[hidden] { display: none; }

/* ------------------------------------------------------------- buttons */
/* THE BUTTON IS THE SHELL'S. `.btn`, `.btn.primary`, `.btn.secondary`,
   `.btn.ghost` and `:disabled` are all defined in /brand/app-shell.css, and
   every one of them was restated here in local numbers — 40px, 16px, 8px, the
   same colours — which is two definitions of one value even while the values
   agree. The restatements are deleted; what is left below is only what the
   platform genuinely does not have.

   `.btn.primary:disabled` went with them, and it was not harmless: it painted
   `--hub-slate-300` (a LINE colour) as a FILL and set no foreground, so the
   inherited white `--tsnz-on-action` sat on it at about 2:1. The shell's
   disabled state is a colour PAIR — `--tsnz-action-disabled` with
   `--tsnz-on-action-disabled` — and the change-password button is disabled the
   moment the page opens, so this was the first thing on that screen. */
.btn.full { width: 100%; height: var(--tsnz-control-h-comfortable); }
/* The spinner moved to the shell as `.tsnz-spinner` (panel ruling, 16 Sep
   2026). Everything this rule had learned went with it — currentColor so the
   ring is visible on whatever fill it sits on, and no blanket reduced-motion
   suppression, because a spinner reduced to no animation freezes into
   something that reads as a hang. What is NOT kept is the second definition:
   TMD had its own `.spinner` and its own `@keyframes spin` with different
   numbers, which is two answers to one question. */

/* ------------------------------------------------------------- lobby cards */
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.card { background: var(--hub-surface); border: 1px solid var(--hub-border);
  border-radius: var(--hub-radius-control); padding: var(--tsnz-space-5); }
.card .head { display: flex; gap: 12px; align-items: flex-start; }
.apptile { width: 36px; height: 36px; border-radius: 8px; background:
  var(--hub-surface-sunken); display: grid; place-items: center; flex: 0 0 auto; }
.apptile i { font-size: 20px; color: var(--hub-navy); }
.card .name { font: 600 16px/22px var(--hub-font-ui); color: var(--hub-navy); }
.card .purpose { color: var(--hub-slate-600); margin-top: 1px; }
.chips { margin: 12px 0 14px; display: flex; flex-wrap: wrap; gap: 6px; }
.chip { display: inline-flex; align-items: center; gap: 5px; background:
  var(--hub-surface-sunken); border-radius: 999px; padding: 3px 10px; font-size: 12px;
  color: var(--hub-ink); }
.chip i { font-size: 13px; color: var(--hub-slate-600); }
.entries { display: flex; flex-wrap: wrap; gap: 8px; }

.card.locked { background: var(--hub-locked-surface); }
.card.locked .name { color: var(--tsnz-locked-fg); }
.card.locked .apptile i { color: var(--hub-slate-300); }
.lockrow { display: flex; align-items: center; gap: 6px; margin: 12px 0 6px;
  font: 600 12px var(--hub-font-ui); color: var(--hub-locked-fg); }
.lockrow i { font-size: 15px; }
/* A held role that cannot be used yet (no seat, MFA owed, plan lapsed): the
   card stays a live card — it is theirs — but says why and offers the one fix. */
.card.blocked .lockrow { color: var(--tsnz-warning-fg); font-weight: 500; }
.card.blocked .actions { margin-top: 8px; }
.price { font: 600 15px var(--hub-font-ui); color: var(--hub-ink); }
.price small { font: 400 12px var(--hub-font-ui); color: var(--hub-slate-600);
  margin-left: 6px; }
.reqpill { display: inline-flex; align-items: center; gap: 6px; background:
  var(--hub-requested-surface); color: var(--hub-requested-fg); border-radius: 999px;
  padding: 5px 12px; font-weight: 500; font-size: 12px; }
.reqpill i { font-size: 14px; }
.lapse { display: flex; gap: 8px; align-items: center; background:
  var(--hub-requested-surface); color: var(--hub-navy); border-radius: 6px;
  padding: 8px 10px; font-size: 12px; margin-top: 10px; }
.card .actions { margin-top: 14px; display: flex; gap: 8px; align-items: center;
  flex-wrap: wrap; }
.admin-ctx { font-size: 12px; color: var(--hub-slate-600); margin-top: 8px; }

/* skeletons */
.skel .card { pointer-events: none; }
.sk { background: var(--hub-surface-sunken); border-radius: 6px;
  animation: pulse 1.4s var(--hub-motion-ease) infinite; }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: .55; } }

/* empty / error */
.statecard { border: 1.5px dashed var(--hub-border-strong); border-radius:
  var(--hub-radius-control); padding: 48px 24px; text-align: center;
  color: var(--hub-slate-600); }
.statecard i { font-size: 32px; color: var(--hub-slate-300); }
.statecard .t { font: 600 16px var(--hub-font-ui); color: var(--hub-ink); margin: 10px 0 4px; }
.statecard .btn { margin-top: 16px; }

/* ------------------------------------------------------------- modal */
.overlay { position: fixed; inset: 0; background: rgba(14,42,71,.35); z-index: 40;
  display: grid; place-items: center; padding: 20px; }
.modal { width: 460px; max-width: 100%; background: var(--hub-surface);
  border-radius: var(--hub-radius-modal); padding: 24px; }
.modal h2 { font: 600 18px/24px var(--hub-font-ui); color: var(--hub-navy); margin: 0 0 10px; }
.modal p { margin: 0 0 12px; color: var(--hub-ink); }
.modal .muted { color: var(--hub-slate-600); font-size: 12px; }
.modal textarea { width: 100%; min-height: 72px; border: 1px solid var(--hub-border-strong);
  border-radius: var(--hub-radius-control); padding: 10px 12px; resize: vertical; }
.modal .field input { height: 44px; }
.modal .total { font-weight: 600; margin: 12px 0 4px; }
.modal .foot { display: flex; justify-content: flex-end; gap: 8px; margin-top: 18px; }

/* ------------------------------------------------------------- toast */
.toastwrap { position: fixed; left: 0; right: 0; bottom: 24px; display: flex;
  justify-content: center; z-index: 50; pointer-events: none; }
.toast { display: flex; align-items: center; gap: 8px; background: var(--hub-navy);
  color: var(--tsnz-on-action); border-radius: var(--tsnz-radius-round);
  padding: 10px 18px; font-size: var(--tsnz-fs-body-small);
  box-shadow: 0 8px 24px rgba(8,29,51,.3); }
.toast i { font-size: 16px; }

/* ------------------------------------------------------------- tablet */
@media (max-width: 768px) {
  .grid { grid-template-columns: 1fr; }
  .shellwrap { padding: var(--tsnz-space-5) var(--tsnz-space-4) var(--tsnz-space-12); }
  /* DENSITY IS THE DIAL, and it reassigns exactly one token. This was
     `.btn { height: 44px }` — a control height restated in an app sheet, and
     only for buttons, so a rail item or a menu row beside them stayed 40px on
     the same phone. Pointing `--tsnz-control-h` at the platform's comfortable
     height gives every control the 44px touch target at once. An explicit
     `data-density` on <body> still wins: it is nearer in the inheritance
     chain than :root. */
  :root { --tsnz-control-h: var(--tsnz-control-h-comfortable); }
  .menu.orgs { right: 60px; }
}

/* ---------------------------------------------------------- system health
   Fleet dashboard (observability-health-dashboard.md §5). Every colour is a
   shell status-family token — rule 0 holds: zero hexes in this file. */
.pagehead { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; }
.healthlink { font-size: var(--tsnz-fs-body-small); line-height: var(--tsnz-lh-body-small);
  color: var(--tsnz-link); text-decoration: underline; white-space: nowrap; }
.hstatus { display: inline-flex; align-items: center; gap: 8px; font-weight: 600;
  border-radius: 999px; padding: 2px 12px; font-size: var(--tsnz-fs-body-small); }
.hstatus.success { background: var(--tsnz-success-bg); color: var(--tsnz-success-fg);
  border: 1px solid var(--tsnz-success-border); }
.hstatus.warning { background: var(--tsnz-warning-bg); color: var(--tsnz-warning-fg);
  border: 1px solid var(--tsnz-warning-border); }
.hstatus.danger  { background: var(--tsnz-danger-bg); color: var(--tsnz-danger-fg);
  border: 1px solid var(--tsnz-danger-border); }
.hstatus.neutral { background: var(--tsnz-neutral-bg); color: var(--tsnz-neutral-fg);
  border: 1px solid var(--tsnz-neutral-border); }
.hbanner { display: flex; align-items: center; justify-content: space-between;
  border-radius: 8px; padding: 10px 16px; margin: 12px 0 20px;
  font-size: var(--tsnz-fs-body); }
.hbanner .hb-when { font-weight: 400; }
.healthgrid .healthtile { display: flex; flex-direction: column; gap: 10px; }
.ht-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.ht-name { font-weight: 600; font-size: var(--tsnz-fs-body-large); }
.ht-grid { display: grid; grid-template-columns: auto 1fr auto 1fr; gap: 4px 10px;
  font-size: var(--tsnz-fs-body-small); line-height: var(--tsnz-lh-body-small); }
.ht-grid .k { color: var(--tsnz-muted); }
.ht-grid .v { font-variant-numeric: tabular-nums; }
.ht-foot { color: var(--tsnz-muted); font-size: var(--tsnz-fs-caption);
  line-height: var(--tsnz-lh-caption); }
.htimeline { margin-top: 20px; }
.hwin { display: inline-flex; gap: 6px; }
.pillbtn { border: 1px solid var(--tsnz-line); background: var(--tsnz-surface);
  color: var(--tsnz-ink); border-radius: 999px; padding: 2px 12px; cursor: pointer;
  font-size: var(--tsnz-fs-body-small); }
.pillbtn.on { background: var(--tsnz-action); color: var(--tsnz-on-action);
  border-color: var(--tsnz-action); }
#hscrub { width: 100%; margin: 14px 0 4px; accent-color: var(--tsnz-action); }
.hscale { display: flex; justify-content: space-between; color: var(--tsnz-muted);
  font-size: var(--tsnz-fs-caption); }
.strips { margin-top: 14px; display: flex; flex-direction: column; gap: 8px; }
.striprow { display: grid; grid-template-columns: 160px 1fr; align-items: center; gap: 12px; }
.stripname { font-size: var(--tsnz-fs-body-small); color: var(--tsnz-ink-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.strip { width: 100%; height: 14px; border-radius: 3px; background: var(--tsnz-surface-sunken); }
.strip .hs-ok { fill: var(--tsnz-success-border); }
.strip .hs-degraded { fill: var(--tsnz-warning-border); }
.strip .hs-down { fill: var(--tsnz-danger-border); }

/* ------------------------------------------------- account pages
   #/account/password and #/security. Both used to render into
   `<main class="lobby">` — a class NO stylesheet in the estate defines — so
   they had no container, no measure and no page padding at all: on a 1950px
   display every input was 1950px wide and the card had no edge to sit inside.
   Nothing failed; a page simply inherited the viewport. They now render into
   `.shellwrap > .content[data-measure="prose"]`, the platform's 640px reading
   column, which is what `--tsnz-measure-prose` exists for.

   A FORM IS A READING COLUMN AND ONE RHYTHM. Fields are one step apart
   (space-5); inside a field, label, control and the text belonging to it are
   one step apart (space-2). Nothing here chooses a number. */
.stackform { display: flex; flex-direction: column; gap: var(--tsnz-space-5); }
.stackform .field { margin-bottom: 0; }

/* THE ACTION ROW. A block control is a field control: full width is right on a
   390px phone and becomes a slab on a desk. So the submit is the platform's
   ordinary `.btn` — auto width — and goes block only below the compact
   breakpoint. `.btn.full` is deliberately NOT used here: the shell degrades a
   block control inside a `content` or `wide` measure, and this form sits in
   `prose`, which that rule does not cover. */
.form-actions { display: flex; flex-direction: column; align-items: flex-start; }
@media (max-width: 719px) {
  .form-actions { align-items: stretch; }
}

/* A RELATED TASK, NOT A FOOTNOTE. "Sign-in security" was a trailing sentence
   under the form with no separation from it; it is a second thing a person can
   do with their credential, so it gets its own panel and its own control. One
   dominant action per region — this one is secondary. */
.card.related { margin-top: var(--tsnz-space-4); }
.card.related h2 { margin: 0 0 var(--tsnz-space-2);
  font: var(--tsnz-fw-semibold) var(--tsnz-fs-heading-sm)/var(--tsnz-lh-heading-sm) var(--hub-font-ui);
  color: var(--tsnz-navy); }
.card.related p { margin: 0 0 var(--tsnz-space-4); color: var(--tsnz-ink-2); }

/* ---- Sign-in security (#/security) — tokens only, no new colours ---- */
.muted { color: var(--hub-slate-600);
  font-size: var(--tsnz-fs-body-small); line-height: var(--tsnz-lh-body-small); }
.authcard .muted { margin: -8px 0 16px; }
.steps { padding-left: 20px; margin: 8px 0 16px; }
.steps li { margin-bottom: 10px; }
.mono { font-family: var(--hub-font-mono); background: var(--hub-surface-sunken);
        padding: 2px 6px; border-radius: var(--hub-radius-control); letter-spacing: .06em;
        user-select: all; }
.btn.small { height: 28px; padding: 0 10px; font-size: 12px; margin-left: 6px; }

/* ---- CSP: no inline style attributes in markup (style-src 'self') ---- */
.caret-sm { font-size: 13px; }
.applogo { width: 24px; height: 24px; object-fit: contain; }
.sk-icon { width: 36px; height: 36px; }
.sk-title { height: 16px; width: 60%; margin: 14px 0 8px; }
.sk-line { height: 12px; width: 85%; }
.sk-btn { height: 36px; width: 40%; margin-top: 18px; }
.push-right { margin-left: auto; }
.field-gap { margin-top: 14px; }

/* C-54: the collection notice — quiet, readable, never a modal */
.notice { color: var(--hub-slate-600); font-size: 13px; line-height: 1.45; margin: -6px 0 14px; }
.banner.notice-first { align-items: flex-start; }

/* ---------------------------------------------------------------- customer agreement (C-55) */
.card.agreement { max-width: var(--tsnz-measure-wide); }
.card.agreement .head p { margin: 0 0 12px; }
.agrtext { max-height: 52vh; overflow-y: auto; padding: 16px 20px; border: 1px solid var(--hub-border);
  border-radius: var(--hub-radius-control); background: var(--hub-surface-sunken); line-height: 1.55; }
.agrtext:focus-visible { box-shadow: var(--hub-focus-ring); }
.agrtext h2 { font: 600 18px/24px var(--hub-font-ui); color: var(--hub-navy); margin: 4px 0 12px; }
.agrtext h3 { font: 600 14px/20px var(--hub-font-ui); color: var(--hub-navy); margin: 18px 0 6px; }
.agrtext p { margin: 0 0 10px; }
.agrtext blockquote { margin: 8px 0 12px; padding: 10px 14px; border-left: 3px solid var(--hub-border-strong);
  background: var(--hub-surface); color: var(--hub-slate-600); }
.agrmeta { margin: 10px 0 16px; }
.agrform .actions { margin-top: 6px; display: flex; gap: 14px; align-items: center; flex-wrap: wrap; }
.agrform .field-error { margin-bottom: var(--tsnz-space-3); }
.agrscroll { margin: 0 0 12px; }

/* Authenticator enrolment QR (server-rendered inline SVG). A white quiet zone
   is part of the code, so the box is always white regardless of theme. */
.qr { display: inline-block; background: #fff; padding: 8px; border: 1px solid var(--hub-border);
  border-radius: 8px; margin: 10px 0; line-height: 0; }
.qr svg { width: 220px; height: 220px; display: block; shape-rendering: crispEdges; }
.steps details { margin-top: 8px; }
.steps summary { cursor: pointer; }

/* Banner status families. `.info` and `.quiet` existed; a success and a failure
   had no form, so the change-password page would have rendered its outcome as
   an unstyled block. Families from the hub's own tokens — never a bare colour. */
/* A status family travels as a TRIPLE — bg, border and fg together. These two
   carried bg and fg only, so a success banner on a light card had no edge. */
.banner.ok  { background: var(--tsnz-success-bg); color: var(--tsnz-success-fg);
  border: 1px solid var(--tsnz-success-border); }
.banner.bad { background: var(--tsnz-danger-bg);  color: var(--tsnz-danger-fg);
  border: 1px solid var(--tsnz-danger-border); }
