/* @userbus/design 396bee2fc0af6fc50a05773b18c22279f4f406a9 – generated by tools/sync.sh from userbus-design; do not edit, re-run the script */
/* UserBus tokens – THE SOURCE OF TRUTH for colour, type, spacing, radius and control size.
   Every surface reads these values from here: this package's components, and the Elixir apps through
   a vendored copy written by `tools/sync.sh` (pinned by the commit in its first line). A value changes
   here first and reaches an app by re-running the script there – never by editing the app's copy.

   One identity for every surface: the landing's transit-signage palette (yellow, black, white),
   the Studio's application scale, and two registers of one palette. Dark is the default register
   (Studio, and every panel where a figure or a picture is judged); light is the landing's page.
   `data-theme` on any element – the whole document or one band – picks the register:
   - `dark` (or no attribute at the root): the dark register;
   - `light`: the light register;
   - `auto`: follows the reader's system setting (`prefers-color-scheme`) – light when it asks for
     light, dark otherwise. For pages a person reaches before any preference exists (sign-in).

   Where the values first came from (they were copied verbatim; from now on they live here):
   - palette literals: userbus-landing/public/styles.css :root (also userbus-admin app.css)
   - application tokens: userbus-studio/priv/static/app.css :root and :root[data-theme="light"]
   - sign colours: userbus-landing/public/page.js (LIT, DIM) and styles.css .sign
   - the Google sign-in button: developers.google.com/identity/branding-guidelines (read 2026-09-26) */

:root {
  /* ---- the palette: literals that never flip ---- */
  --yellow: #ffd400;        /* signage yellow – the product's colour */
  --black: #000000;
  --white: #ffffff;
  --grey: #5c5c5c;          /* secondary text on white */
  --rule: #d9d9d9;          /* hairline on white */
  --night-rule: #3a3a3a;    /* hairline on black – dark surfaces only */
  --amber: #ffb000;         /* "on / live": the lit sign, the running job */
  --alarm: #ff8a75;         /* negative, on black */
  --refused: #b00020;       /* negative, on white */
  --sign-ground: #0b0900;   /* the destination sign's panel */
  --sign-dim: #2e2400;      /* an unlit sign dot */

  /* ---- type: one system stack, no font files ---- */
  --font-sans: "Helvetica Neue", Helvetica, Arial, "Liberation Sans", sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
  --fs-xs: 0.75rem;     /* 12px – metadata, chips */
  --fs-sm: 0.8125rem;   /* 13px – dense data, table cells, buttons */
  --fs-md: 0.9375rem;   /* 15px – body, controls */
  --fs-lg: 1.125rem;    /* 18px – section titles */
  --fs-xl: 1.625rem;    /* 26px – page titles */
  --lh-tight: 1.25;
  --lh-body: 1.55;

  /* ---- spacing: 4 8 12 16 24 32 48, nothing between ---- */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-6: 32px;
  --sp-7: 48px;

  /* ---- edges: no drop shadows anywhere; structure is rules and surfaces ---- */
  --r-sm: 4px;
  --r-md: 8px;
  --r-lg: 14px;
  --r-bus: 18px;        /* the bus body around the sign */
  --r-pill: 999px;

  /* ---- control heights ---- */
  --ctl-md: 36px;
  --ctl-lg: 44px;
  --ctl-touch: 44px;
  --ctl-touch-lg: 48px;

  --focus-offset: 3px;
}

/* ---- the dark register: the default, and `auto` unless the system asks for light ----
   `[data-theme="dark"]` is listed so a dark region nested in a light one switches back (custom
   properties inherit, so without it the nested region kept the light values). */
:root, [data-theme="dark"], [data-theme="auto"] {
  color-scheme: dark;
  --c-bg: #0a0a0a;
  --c-bg-raised: #0f0f0f;
  --c-surface: #111111;
  --c-surface-2: #171717;
  --c-surface-3: #1f1f1f;
  --c-line: #2a2a2a;
  --c-line-strong: #3a3a3a;
  --c-text: #ffffff;
  --c-text-soft: #e2e2e2;
  --c-text-dim: #c9c9c9;
  --c-text-invert: #000000;
  --c-accent: #ffd400;
  --c-accent-ink: #ffd400;
  --c-accent-strong: #ffe14d;
  --c-accent-hover: #ffe14d;
  --c-accent-quiet: rgba(255, 212, 0, 0.14);
  --c-accent-line: rgba(255, 212, 0, 0.45);
  --c-queued: #c9c9c9;
  --c-running: #ffb000;
  --c-succeeded: #ffd400;
  --c-failed: #ff8a75;
  --c-canceled: #c9c9c9;
  --c-warn: #ffb000;
  --c-media: #000000;
  --c-btn: #171717;
  --c-btn-hover: #1f1f1f;
  --c-primary-edge: transparent;
  /* the B (buyers') route bullet: yellow disc on dark, black disc on light */
  --c-bullet-b: #ffd400;
  --c-bullet-b-ink: #000000;
  /* "Continue with Google" – Google's dark theme, the one sanctioned exception to "no other hues" */
  --c-google-bg: #131314;
  --c-google-line: #8e918f;
  --c-google-text: #e3e3e3;
  --focus: 3px solid #ffd400;
}

/* ---- the light register: the landing's white page ---- */
[data-theme="light"] {
  color-scheme: light;
  --c-bg: #f7f7f7;
  --c-bg-raised: #ffffff;
  --c-surface: #ffffff;
  --c-surface-2: #f2f2f2;
  --c-surface-3: #e9e9e9;
  --c-line: #d9d9d9;
  --c-line-strong: #858585;
  --c-text: #000000;
  --c-text-soft: #000000;
  --c-text-dim: #5c5c5c;
  --c-text-invert: #000000;
  --c-accent-ink: #000000;
  --c-accent-hover: #f0c400;
  --c-accent-line: #000000;
  --c-queued: #5c5c5c;
  --c-running: #000000;
  --c-succeeded: #000000;
  --c-failed: #b00020;
  --c-canceled: #5c5c5c;
  --c-warn: #000000;
  --c-btn: #ffffff;
  --c-btn-hover: #f2f2f2;
  --c-primary-edge: #000000;
  --c-bullet-b: #000000;
  --c-bullet-b-ink: #ffd400;
  --c-google-bg: #ffffff;
  --c-google-line: #747775;
  --c-google-text: #1f1f1f;
  --focus: 3px solid #000000;
}

/* BEGIN auto register */
/* GENERATED by tools/auto-register.sh from the light register above – do not edit here. */
@media (prefers-color-scheme: light) {
  [data-theme="auto"] {
    color-scheme: light;
    --c-bg: #f7f7f7;
    --c-bg-raised: #ffffff;
    --c-surface: #ffffff;
    --c-surface-2: #f2f2f2;
    --c-surface-3: #e9e9e9;
    --c-line: #d9d9d9;
    --c-line-strong: #858585;
    --c-text: #000000;
    --c-text-soft: #000000;
    --c-text-dim: #5c5c5c;
    --c-text-invert: #000000;
    --c-accent-ink: #000000;
    --c-accent-hover: #f0c400;
    --c-accent-line: #000000;
    --c-queued: #5c5c5c;
    --c-running: #000000;
    --c-succeeded: #000000;
    --c-failed: #b00020;
    --c-canceled: #5c5c5c;
    --c-warn: #000000;
    --c-btn: #ffffff;
    --c-btn-hover: #f2f2f2;
    --c-primary-edge: #000000;
    --c-bullet-b: #000000;
    --c-bullet-b-ink: #ffd400;
    --c-google-bg: #ffffff;
    --c-google-line: #747775;
    --c-google-text: #1f1f1f;
    --focus: 3px solid #000000;
  }
}
/* END auto register */


/* UserBus component styles.
   Class names are the ones the apps already ship, so a design made from these components maps
   1:1 onto Studio's HEEx templates and the landing's HTML:
   - .btn .chip .alert .card .field .input .timeline .meter .kv .video-tile .topbar .nav
     .balance-badge .empty-state – userbus-studio/priv/static/app.css
   - .bullet .mark .route .rows .band .sign .bus-body .button – userbus-landing/public/styles.css
   - .auth .btn-google .divider .input-group .input-toggle .field-caps .input-code .toast .qr, the
     busy button and .visually-hidden – written here first (2026-09-26, for the sign-in flow); the
     apps take them with the rest through tools/sync.sh
   Landing rules that painted literal black and white are written here with the application tokens
   (`--c-text`, `--c-bg-raised`), which resolve to exactly black and white in the light register –
   so the landing renders unchanged there and inverts cleanly on the dark one. */

/* ---------- the root: base type and ground (UserBusRoot) ---------- */

.ub {
  background: var(--c-bg);
  color: var(--c-text);
  font-family: var(--font-sans);
  font-size: var(--fs-md);
  line-height: var(--lh-body);
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
}
:where(.ub) *, :where(.ub) *::before, :where(.ub) *::after { box-sizing: border-box; }
:where(.ub) [hidden] { display: none !important; }
:where(.ub) h1, :where(.ub) h2, :where(.ub) h3 { line-height: var(--lh-tight); margin: 0 0 var(--sp-3); font-weight: 700; }
:where(.ub) h1 { font-size: var(--fs-xl); letter-spacing: -0.045em; }
:where(.ub) h2 { font-size: var(--fs-lg); letter-spacing: -0.035em; }
:where(.ub) h3 { font-size: var(--fs-md); letter-spacing: -0.02em; }
:where(.ub) p { margin: 0 0 var(--sp-3); }
:where(.ub) a { color: var(--c-accent-ink); text-decoration: none; text-underline-offset: 0.18em; text-decoration-thickness: 0.09em; }
:where(.ub) :where(p, li, dd, .alert) a:not([class]) { text-decoration: underline; }
:where(.ub) a:not([class]):hover { text-decoration: underline; text-decoration-thickness: 0.16em; }
:where(.ub) code, :where(.ub) .mono { font-family: var(--font-mono); font-size: 0.92em; word-spacing: -0.3em; }
:where(.ub) :focus-visible { outline: var(--focus); outline-offset: var(--focus-offset); }
:where(.ub) .muted { color: var(--c-text-dim); }
:where(.ub) .row { display: flex; gap: var(--sp-3); align-items: center; flex-wrap: wrap; }
/* read by a screen reader, not shown: a live status line, a fuller name for a short visible one */
.visually-hidden {
  position: absolute !important; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* ---------- display type: the landing's hero scale ---------- */

.display { font-weight: 700; line-height: 0.95; letter-spacing: -0.045em; text-wrap: balance; margin: 0; }
.display-1 { font-size: clamp(2.6rem, 6.2vw, 5.4rem); }
.display-2 { font-size: clamp(2.1rem, 4.8vw, 3.9rem); line-height: 1; letter-spacing: -0.035em; max-width: 18ch; }
.display-3 { font-size: 1.3rem; line-height: 1.2; letter-spacing: -0.02em; }
.lead { font-size: clamp(1.1rem, 1.45vw, 1.3rem); line-height: 1.42; max-width: 36em; margin: 0; }

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

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  min-height: var(--ctl-md); padding: 0 var(--sp-4);
  font: inherit; font-size: var(--fs-sm); font-weight: 700; line-height: 20px;
  text-align: center; text-decoration: none; white-space: nowrap;
  color: var(--c-text); background: var(--c-btn);
  border: 1px solid var(--c-line-strong); border-radius: var(--r-md);
  cursor: pointer;
  transition: background-color 120ms ease-out, border-color 120ms ease-out, color 120ms ease-out;
}
.btn:hover { background: var(--c-btn-hover); border-color: color-mix(in srgb, var(--c-text-dim) 60%, transparent); text-decoration: none; }
.btn:active { filter: brightness(0.92); }
.btn-primary { background: var(--c-accent); border-color: var(--c-primary-edge); color: var(--c-text-invert); }
.btn-primary:hover { background: var(--c-accent-hover); border-color: var(--c-primary-edge); }
.btn-quiet { background: transparent; border-color: transparent; color: var(--c-text-soft); }
.btn-quiet:hover { background: var(--c-surface-2); border-color: transparent; color: var(--c-text); }
.btn-danger { background: transparent; border-color: var(--c-failed); color: var(--c-failed); }
.btn-danger:hover { background: color-mix(in srgb, var(--c-failed) 14%, transparent); border-color: var(--c-failed); }
.btn-lg { min-height: var(--ctl-lg); padding: 0 var(--sp-5); font-size: var(--fs-md); }
.btn-block { width: 100%; }
.btn .icon { width: 16px; height: 16px; }

/* "Continue with Google": Google's own button, and the one sanctioned exception to "no other hues".
   developers.google.com/identity/branding-guidelines (read 2026-09-26) fixes the fill, the 1px stroke
   and the text colour per theme (the --c-google-* tokens follow our register), the full-colour G on
   its own, 12px before the G, 10px after it, and 14/20 medium type. It names Google Sans, which is not
   shipped here (no font files); a machine that has it uses it, others get Helvetica at 500. Worn with
   .btn .btn-lg so it stands at the height of the primary it sits beside. Never yellow. The rules come
   after .btn-lg (which would restore its padding and size) and before the disabled and busy rules. */
.btn-google {
  background: var(--c-google-bg); border-color: var(--c-google-line); color: var(--c-google-text);
  font-family: "Google Sans", var(--font-sans); font-weight: 500; font-size: 14px; line-height: 20px;
  padding: 0 12px; gap: 10px;
}
/* hover is Material's 8% state layer of the text colour over the fill – a flat mix, not a gradient */
.btn-google:hover { background: color-mix(in srgb, var(--c-google-text) 8%, var(--c-google-bg)); border-color: var(--c-google-line); }
.provider-mark { flex: none; display: block; width: 20px; height: 20px; }

.btn:disabled, .btn[aria-disabled="true"] {
  background: var(--c-surface-2); border-color: var(--c-line); color: var(--c-text-dim);
  cursor: not-allowed; filter: none;
}
/* busy: the action was sent and the page waits for the answer. It looks disabled, and the label stays
   in place but unseen, so the button keeps its width and nothing beside it moves; a ring turns where
   the label was. Screen readers still read the label, and aria-busy says it is working. Set it in the
   form's submit event (a button disabled on click never submits its form). */
.btn[aria-busy="true"], .btn.is-busy {
  position: relative; pointer-events: none; filter: none;
  background: var(--c-surface-2); border-color: var(--c-line); color: transparent;
}
.btn[aria-busy="true"] > *, .btn.is-busy > * { visibility: hidden; }
.btn[aria-busy="true"]::after, .btn.is-busy::after {
  content: ""; position: absolute; inset: 0; margin: auto;
  width: 16px; height: 16px; border-radius: 50%;
  border: 2px solid var(--c-text-dim); border-right-color: transparent;
  animation: ub-spin 800ms linear infinite;
}
@keyframes ub-spin { to { transform: rotate(360deg); } }
@keyframes ub-fade { from { opacity: 1; } to { opacity: 0.35; } }
.btn-group { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }
@media (max-width: 720px), (pointer: coarse) {
  .btn { min-height: var(--ctl-touch); }
  .btn-lg { min-height: var(--ctl-touch-lg); }
}

/* the landing's call to action: black, 700, 8px – inverts on dark */
.button {
  display: inline-block; font: inherit; font-weight: 700; text-decoration: none; cursor: pointer;
  background: var(--c-text); color: var(--c-bg-raised);
  padding: 14px 22px; border-radius: var(--r-md); border: 2px solid var(--c-text);
}
.button:hover { background: var(--c-bg-raised); color: var(--c-text); }
.button-quiet { background: transparent; color: var(--c-text); }
.button-quiet:hover { background: var(--c-text); color: var(--c-bg-raised); }
.button-sign { background: var(--yellow); color: var(--black); border-color: var(--yellow); }
.button-sign:hover { background: var(--white); color: var(--black); border-color: var(--white); }

/* ---------- status chip: one job state ---------- */

.chip {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  font-size: var(--fs-xs); font-weight: 700; line-height: 18px;
  padding: 2px var(--sp-2); border-radius: var(--r-pill);
  border: 1.5px solid var(--c-line-strong);
  color: var(--c-text-soft); background: var(--c-surface-2); white-space: nowrap;
}
.chip::before { content: ""; width: 6px; height: 6px; border-radius: var(--r-pill); background: currentColor; }
.chip--queued { color: var(--c-queued); border-color: color-mix(in srgb, var(--c-queued) 45%, transparent); }
.chip--running { color: var(--c-running); border-color: color-mix(in srgb, var(--c-running) 45%, transparent); }
.chip--succeeded { color: var(--c-succeeded); border-color: color-mix(in srgb, var(--c-succeeded) 45%, transparent); }
.chip--failed { color: var(--c-failed); border-color: color-mix(in srgb, var(--c-failed) 45%, transparent); }
.chip--canceled { color: var(--c-canceled); border-color: color-mix(in srgb, var(--c-canceled) 45%, transparent); }
.chip--warn { color: var(--c-warn); border-color: color-mix(in srgb, var(--c-warn) 45%, transparent); }
.chip--unknown { border-style: dashed; }
.chip--plain::before { display: none; }

/* ---------- provenance mark: where a figure comes from ---------- */

.mark {
  display: inline-flex; align-items: center; gap: 6px; vertical-align: 0.08em; white-space: nowrap;
  font-size: 0.76rem; font-weight: 700; line-height: 1; letter-spacing: 0.01em;
  padding: 4px 9px 4px 7px; border: 1.5px solid var(--c-text); border-radius: var(--r-pill);
  background: var(--c-bg-raised); color: var(--c-text);
}
.mark::before { content: ""; width: 8px; height: 8px; border-radius: 50%; border: 1.5px solid var(--c-text); }
.mark-measured::before { background: var(--c-text); }
.mark-published::before { background: linear-gradient(90deg, var(--c-text) 50%, var(--c-bg-raised) 50%); }
.mark-planned, .mark-planned::before { border-style: dashed; }

/* ---------- route bullet: B is the buyers' line, P the providers' ---------- */

.bullet {
  display: inline-grid; place-items: center; flex: none;
  width: 1.55em; height: 1.55em; border-radius: 50%;
  font-weight: 700; font-size: 0.82em; line-height: 1; letter-spacing: 0;
}
.bullet-b { background: var(--c-bullet-b); color: var(--c-bullet-b-ink); }
.bullet-p { background: var(--c-bg-raised); color: var(--c-text); box-shadow: inset 0 0 0 2px var(--c-text); }
.line-tag { display: inline-flex; align-items: center; gap: 10px; font-weight: 700; }

/* ---------- timeline: where the work is ---------- */

.timeline { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-2) var(--sp-3); list-style: none; margin: 0 0 var(--sp-2); padding: 0; }
.timeline-step { display: inline-flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-sm); font-weight: 700; color: var(--c-text-dim); }
.timeline-step + .timeline-step::before { content: ""; width: 24px; height: 6px; background: var(--c-line-strong); }
.timeline-dot { width: 14px; height: 14px; border-radius: 50%; border: 3px solid var(--c-line-strong); background: transparent; flex: none; }
.timeline-time { font-family: var(--font-mono); font-size: var(--fs-xs); font-weight: 400; color: var(--c-text-dim); }
.timeline-step.is-done { color: var(--c-text-soft); }
.timeline-step.is-done .timeline-dot { background: var(--c-line-strong); }
.timeline-step.is-current { color: var(--c-text); }
.timeline-step.is-current .timeline-dot { background: var(--c-accent); border-color: var(--c-accent-ink); box-shadow: 0 0 12px var(--c-accent); }
.timeline-step.is-current[data-timeline-step="generating"] .timeline-dot { background: var(--c-running); border-color: var(--c-running); box-shadow: 0 0 12px var(--c-running); }
.timeline-step.is-unknown .timeline-dot { border-style: dashed; }
.timeline-step.is-ended { color: var(--c-text); }
.timeline-step.is-ended .timeline-dot { background: var(--c-failed); border-color: var(--c-failed); box-shadow: 0 0 12px var(--c-failed); }
.timeline-step.is-ended[data-timeline-status="canceled"] .timeline-dot { background: var(--c-canceled); border-color: var(--c-canceled); box-shadow: none; }
.timeline-note { color: var(--c-text-dim); font-size: var(--fs-xs); margin: 0; }

/* ---------- meter ---------- */

.meter { display: flex; align-items: center; gap: var(--sp-3); }
.meter-track { flex: 1; height: 8px; background: var(--c-surface-3); border-radius: var(--r-pill); overflow: hidden; min-width: 80px; }
.meter-fill { height: 100%; background: var(--c-running); border-radius: var(--r-pill); transition: width 200ms ease-out; }
.meter-fill[data-state="succeeded"] { background: var(--c-succeeded); }
.meter-fill[data-state="failed"] { background: var(--c-failed); }
.meter-fill[data-state="canceled"] { background: var(--c-canceled); }
.meter-value { font-variant-numeric: tabular-nums; font-size: var(--fs-xs); color: var(--c-text-soft); min-width: 3.5ch; text-align: right; }

/* ---------- alert ---------- */

.alert {
  border: 1px solid var(--c-line-strong); border-left-width: 6px; border-radius: var(--r-md);
  padding: var(--sp-3) var(--sp-4); background: var(--c-surface); font-size: var(--fs-sm); color: var(--c-text);
}
.alert.error { border-left-color: var(--c-failed); }
.alert.notice { border-left-color: var(--c-succeeded); }
.alert.info { border-left-color: var(--c-accent-ink); }
.alert > strong { display: block; margin-bottom: 2px; }

/* ---------- card ---------- */

.card { background: var(--c-surface); border: 1px solid var(--c-line); border-radius: var(--r-lg); padding: var(--sp-5); min-width: 0; color: var(--c-text); }
.card-head { display: flex; align-items: baseline; gap: var(--sp-3); margin-bottom: var(--sp-4); flex-wrap: wrap; min-width: 0; }
.card-head h2 { margin: 0; font-size: var(--fs-lg); letter-spacing: -0.035em; line-height: var(--lh-tight); }
.card-note { color: var(--c-text-dim); font-size: var(--fs-xs); margin-left: auto; }

/* ---------- key–value facts ---------- */

.kv { display: grid; grid-template-columns: minmax(9rem, auto) minmax(0, 1fr); gap: var(--sp-2) var(--sp-4); font-size: var(--fs-sm); margin: 0; }
.kv dt { color: var(--c-text-dim); }
.kv dd { margin: 0; overflow-wrap: anywhere; }
.kv.money { gap: 0 var(--sp-4); }
.kv.money dt, .kv.money dd { border-top: 1px solid var(--c-line); padding-block: var(--sp-2); }
.kv.money dt:first-of-type, .kv.money dd:first-of-type { border-top: 0; }
.kv.money dd.is-total { font-weight: 700; font-size: var(--fs-md); }

/* ---------- forms ---------- */

.field { min-width: 0; }
.field > label, .field-label { display: block; font-size: var(--fs-sm); font-weight: 550; margin-bottom: var(--sp-2); color: var(--c-text); }
.field-hint { color: var(--c-text-dim); font-size: var(--fs-xs); margin: var(--sp-2) 0 0; }
.field-error { color: var(--c-failed); font-size: var(--fs-xs); margin: var(--sp-2) 0 0; }
.input, .textarea, .select {
  width: 100%; font: inherit; font-size: var(--fs-sm); color: var(--c-text);
  background: var(--c-bg-raised); border: 1px solid var(--c-line-strong); border-radius: var(--r-md);
  padding: var(--sp-2) var(--sp-3); min-height: 38px;
}
.textarea { min-height: 132px; resize: vertical; line-height: var(--lh-body); }
.input[readonly], .select:disabled { color: var(--c-text-soft); background: var(--c-surface-2); }
.input::placeholder, .textarea::placeholder { color: var(--c-text-dim); }
.input[aria-invalid="true"], .textarea[aria-invalid="true"] { border-color: var(--c-failed); box-shadow: inset 0 0 0 1px var(--c-failed); }

/* a control with a button beside it – the password's Show toggle. The button sits OUTSIDE the input,
   not over it, so a password manager's own icon in the field never lands under it; 44px square at
   least, under a mouse too. */
.input-group { display: flex; align-items: stretch; gap: var(--sp-2); }
.input-group > .input { flex: 1; min-width: 0; }
.input-toggle { flex: none; min-width: var(--ctl-touch); min-height: var(--ctl-touch); padding: 0 var(--sp-3); }
/* "Caps Lock is on." – present but empty until it applies, so the live region exists before it speaks */
.field-caps { color: var(--c-text); font-size: var(--fs-xs); font-weight: 700; margin: var(--sp-2) 0 0; }
.field-caps:empty { display: none; }

/* a one-time code: ONE input, never a box per digit – paste and the phone's code suggestion must fill
   it whole (WCAG 2.2, 3.3.8). Figures in the monospace, spaced so six digits read as a code. */
.input.input-code {
  font-family: var(--font-mono); font-size: var(--fs-lg); font-variant-numeric: tabular-nums;
  letter-spacing: 0.3em; max-width: 13rem;
}

/* "or" between two ways in: a hairline each side of a dim word */
.divider { display: flex; align-items: center; gap: var(--sp-3); margin: 0; color: var(--c-text-dim); font-size: var(--fs-sm); }
.divider::before, .divider::after { content: ""; flex: 1; height: 1px; background: var(--c-line); }

/* ---------- top bar ---------- */

.topbar { background: var(--c-bg-raised); border-bottom: 1px solid var(--c-line); color: var(--c-text); }
.topbar-inner { display: flex; align-items: center; gap: var(--sp-4); max-width: 1240px; width: 100%; margin: 0 auto; padding: var(--sp-3) var(--sp-5); flex-wrap: wrap; }
.brand { display: inline-flex; align-items: center; gap: var(--sp-2); min-height: 44px; flex-shrink: 0; color: var(--c-text); text-decoration: none; }
.brand-mark { flex: none; display: block; }
.brand-name { font-weight: 700; letter-spacing: -0.03em; font-size: 1.5rem; line-height: 1; }
.brand-context { color: var(--c-text-dim); font-size: var(--fs-sm); }
.nav { display: flex; flex-wrap: wrap; gap: var(--sp-1); margin-left: var(--sp-2); }
.nav a { display: inline-flex; align-items: center; min-height: 44px; color: var(--c-text-soft); padding: var(--sp-2) var(--sp-3); border-radius: var(--r-md); font-size: 1rem; font-weight: 500; white-space: nowrap; }
.nav a:hover, .nav a[aria-current="page"] { color: var(--c-text); text-decoration: underline; text-decoration-thickness: 3px; text-decoration-color: var(--c-accent); text-underline-offset: 8px; }
.topbar-end { margin-left: auto; display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2) var(--sp-3); min-width: 0; }

.balance-badge {
  display: inline-flex; align-items: center; gap: var(--sp-2); height: var(--ctl-md); padding: 0 var(--sp-3);
  border: 1px solid var(--c-line); border-radius: var(--r-pill); color: var(--c-text);
  font-size: var(--fs-sm); text-decoration: none; white-space: nowrap;
}
.balance-badge .balance-label { color: var(--c-text-dim); }
.balance-badge .balance-value { font-weight: 700; font-variant-numeric: tabular-nums; }
.balance-badge:hover { border-color: var(--c-accent-line); }

/* ---------- video tile ---------- */

.video-grid { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: var(--sp-4); }
.video-tile { display: flex; flex-direction: column; width: 100%; min-width: 0; border: 1px solid var(--c-line); border-radius: var(--r-md); background: var(--c-bg-raised); overflow: hidden; color: var(--c-text); }
.tile-preview, .video-tile[data-tile-case="failed"]::before { flex: none; aspect-ratio: 16 / 9; background: var(--c-surface-2); border-bottom: 1px solid var(--c-line); }
.tile-preview { display: flex; align-items: center; justify-content: center; overflow: hidden; }
.video-tile[data-tile-case="failed"]::before {
  content: ""; display: block;
  background: linear-gradient(to top right, transparent calc(50% - 1px), var(--c-line-strong) 50%, transparent calc(50% + 1px)), var(--c-surface-2);
}
.video-tile[data-tile-case="running"] .tile-preview { background: repeating-linear-gradient(135deg, var(--c-surface-2) 0 6px, var(--c-surface-3) 6px 12px); }
.tile-thumb { display: block; width: 100%; height: 100%; object-fit: contain; background: var(--c-media); }
.tile-absent { color: var(--c-text-dim); font-size: var(--fs-xs); line-height: 18px; padding: var(--sp-1) var(--sp-3); border-radius: var(--r-sm); background: var(--c-surface-2); text-align: center; }
.tile-body { flex: 1; display: flex; flex-direction: column; gap: var(--sp-2); padding: var(--sp-3) var(--sp-4) var(--sp-4); min-width: 0; }
.tile-title { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; min-height: 44px; line-height: 22px; font-weight: 700; overflow-wrap: anywhere; color: var(--c-text); margin: 0; }
.tile-figures { display: flex; flex-wrap: wrap; row-gap: 2px; margin: 0 0 0 calc(-1 * var(--sp-4)); padding: 0; list-style: none; clip-path: inset(0 0 0 var(--sp-4)); color: var(--c-text-soft); font-size: var(--fs-xs); line-height: 18px; }
.tile-figure { position: relative; margin-left: var(--sp-4); font-variant-numeric: tabular-nums; }
.tile-figure::before { content: "·"; position: absolute; left: calc(-1 * var(--sp-4)); width: var(--sp-4); text-align: center; color: var(--c-text-dim); }
.tile-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); margin-top: auto; padding-top: var(--sp-1); }
.tile-actions > .btn { flex: 1 1 0; min-width: 0; }

/* ---------- empty state ---------- */

.empty-state { text-align: center; padding: var(--sp-5) var(--sp-4); color: var(--c-text-soft); }
.empty-state h3 { color: var(--c-text); margin: 0 0 var(--sp-2); font-size: var(--fs-md); letter-spacing: -0.02em; }
.empty-state p { max-width: 46ch; margin: 0 auto var(--sp-4); }

/* ---------- auth card: the sign-in pages ---------- */

/* One centred column on the page ground: a 420px card on the surface, 14px corners, a hairline and no
   shadow. The card centres vertically while it is shorter than the window and scrolls when it is not;
   under 480px it goes full-bleed (no edge, no corners), because a framed card on a phone only wastes
   the width a form needs. */
.auth { min-height: 100vh; min-height: 100dvh; display: flex; flex-direction: column; align-items: center; padding: var(--sp-7) var(--sp-4); background: var(--c-bg); color: var(--c-text); }
.auth-card { width: 100%; max-width: 420px; margin-block: auto; background: var(--c-surface); border: 1px solid var(--c-line); border-radius: var(--r-lg); padding: var(--sp-6); }
.auth-brand { margin-bottom: var(--sp-5); }
.auth-context { margin: var(--sp-1) 0 0; color: var(--c-text-dim); font-size: var(--fs-sm); }
.auth-title { font-size: var(--fs-xl); letter-spacing: -0.045em; line-height: var(--lh-tight); margin: 0 0 var(--sp-2); }
.auth-lede { color: var(--c-text-soft); margin: 0 0 var(--sp-5); }
.auth-body, .auth-body form { display: grid; gap: var(--sp-4); }
.auth-body form { margin: 0; }
.auth-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 0 var(--sp-4); margin-top: var(--sp-5); padding-top: var(--sp-3); border-top: 1px solid var(--c-line); font-size: var(--fs-sm); color: var(--c-text-dim); }
/* underlined: in the light register the link colour is black, so the line is what marks them as links */
.auth-foot a { display: inline-flex; align-items: center; min-height: var(--ctl-touch); text-decoration: underline; }
@media (max-width: 480px) {
  .auth { padding: 0; }
  .auth-card { max-width: none; margin-block: 0; flex: 1; border: 0; border-radius: 0; padding: var(--sp-5) var(--sp-4); }
}
/* iOS Safari zooms the page into any focused control set under 16px, so under a finger the card's
   inputs are 17px on a 48px control (what userbus-auth shipped before this card). */
@media (max-width: 720px), (pointer: coarse) {
  .auth-card .input:not(.input-code) { min-height: var(--ctl-touch-lg); font-size: 1.0625rem; }
}

/* ---------- toast: a short confirmation ("Copied", "Link sent") ---------- */

/* The region is the live region and is in the page from the start (a region inserted with its
   message is often not read); the app puts one .toast in it and empties it after a few seconds.
   Flat: the second surface, a hairline, 8px corners, no shadow. .is-inline keeps it in the flow. */
.toast-region { position: fixed; inset-inline: 0; bottom: var(--sp-5); z-index: 10; display: flex; justify-content: center; padding-inline: var(--sp-4); pointer-events: none; }
.toast-region.is-inline { position: static; justify-content: flex-start; padding: 0; }
.toast { margin: 0; max-width: 36em; padding: var(--sp-2) var(--sp-4); background: var(--c-surface-2); border: 1px solid var(--c-line); border-radius: var(--r-md); color: var(--c-text); font-size: var(--fs-sm); font-weight: 700; pointer-events: auto; }

/* ---------- QR code frame ---------- */

/* Black modules on white with a white quiet zone in BOTH registers: phone cameras look for dark on
   light, and many readers fail an inverted code. The 24px padding is the quiet zone – the standard
   asks for four modules, and an authenticator URI drawn at 200px is about 5px a module (estimated
   for a 37-module code, not measured). Give the SVG no margin of its own. */
.qr { display: inline-block; width: fit-content; padding: var(--sp-5); background: var(--white); color: var(--black); border: 1px solid var(--c-line); border-radius: var(--r-md); line-height: 0; }
.qr svg { display: block; width: 200px; max-width: 100%; height: auto; }

/* ---------- bands: the landing's yellow and black ---------- */

.band { padding-block: clamp(32px, 4.5vw, 64px); padding-inline: clamp(16px, 4vw, 40px); }
.band-yellow { background: var(--yellow); color: var(--black); --c-text: #000000; --c-text-soft: #000000; --c-text-dim: #2b2b2b; --c-bg-raised: #ffffff; --c-bullet-b: #000000; --c-bullet-b-ink: #ffd400; --focus: 3px solid #000000; }
.band-black { background: var(--black); color: var(--white); --c-text: #ffffff; --c-text-soft: #e2e2e2; --c-text-dim: #c9c9c9; --c-bg-raised: #000000; --c-bullet-b: #ffd400; --c-bullet-b-ink: #000000; --focus: 3px solid #ffd400; }
.band-ruled { border-block: 3px solid var(--c-text); }

/* ---------- route: a real sequence, drawn as a transit line ---------- */

.route { display: grid; grid-template-columns: repeat(var(--stops, 4), minmax(0, 1fr)); list-style: none; margin: 0; padding: 0; counter-reset: stop; color: var(--c-text); }
.route li { position: relative; padding: 68px 28px 0 0; counter-increment: stop; }
.route li::before { content: ""; position: absolute; top: 16px; left: 0; right: 0; height: 8px; background: var(--c-text); }
.route li:last-child::before { right: auto; width: 22px; }
.route li::after {
  content: counter(stop); position: absolute; top: 0; left: 0;
  display: grid; place-items: center; width: 40px; height: 40px; border-radius: 20px;
  background: var(--c-bg-raised); color: var(--c-text); border: 6px solid var(--c-text); font-weight: 700; font-size: 0.95rem;
}
.route li.route-interchange::after { width: 76px; background: var(--yellow); color: var(--black); }
.route h3 { font-size: 1.3rem; line-height: 1.2; letter-spacing: -0.02em; font-weight: 700; margin: 0 0 8px; }
.route p { font-size: 1rem; margin: 0; }
@media (max-width: 900px) {
  .route { grid-template-columns: 1fr; }
  .route li { padding: 4px 0 36px 68px; }
  .route li::before { top: 0; bottom: 0; left: 16px; right: auto; width: 8px; height: auto; }
  .route li:last-child::before { width: 8px; height: 22px; bottom: auto; }
  .route li.route-interchange::after { width: 40px; }
}

/* ---------- board: the dark figures panel ---------- */

.board { background: var(--black); color: var(--white); padding: clamp(18px, 2.6vw, 32px); border-radius: var(--r-lg); --focus: 3px solid #ffd400; }
.board-title { font-size: 1.3rem; line-height: 1.2; letter-spacing: -0.02em; font-weight: 700; margin: 0; }
.rows { margin: 18px 0 0; }
.rows > div { display: flex; justify-content: space-between; gap: 20px; align-items: baseline; padding: 11px 0; border-top: 1px solid var(--night-rule); }
.rows dt { color: #d0d0d0; font-size: 0.98rem; }
.rows dd { margin: 0; font-weight: 700; font-size: 1.1rem; text-align: right; }
.rows .row-strong dt { color: var(--white); }
.rows .row-strong dd { color: var(--yellow); font-size: 1.35rem; }

/* ---------- destination sign ---------- */

.bus-body { background: var(--black); border-radius: var(--r-bus); padding: 18px 18px 16px; }
.sign { display: block; width: 100%; height: auto; background: var(--sign-ground); border-radius: 6px; }
.sign-dot-dim { fill: var(--sign-dim); }
.sign-dot-lit { fill: var(--amber); }
.bus-plate { display: flex; align-items: center; gap: 12px; margin-top: 14px; color: var(--white); font-size: 0.95rem; line-height: 1.3; }
.bus-plate b { flex: none; background: var(--yellow); color: var(--black); padding: 3px 12px; border-radius: var(--r-sm); letter-spacing: 0.04em; }

@media (prefers-reduced-motion: reduce) {
  .btn, .meter-fill { transition: none; }
  /* the busy ring stops turning and breathes instead: still visibly working, nothing moves */
  .btn[aria-busy="true"]::after, .btn.is-busy::after { animation: ub-fade 1.2s ease-in-out infinite alternate; }
}
