/* ==========================================================================
   Symmy — Raynet & Outlook Sync · design system
   --------------------------------------------------------------------------
   Brand (symmy.visualbook.pro):
     Systémová modrá   #3452f8   primary — actions, links, focus, active nav
     Propojovací zelená #3bd895  accent  — success, "live" states, logo
     Stabilní zelená   #022b26   dark    — app chrome, headings, deep text
     Bílá              #ffffff

   Typefaces: the manual specifies PP Formula Extended Bold (display) and
   Saans Regular (text). Both are commercial and are not redistributed with
   this repo, so the stacks below fall back to the closest freely available
   equivalents — Archivo Expanded and Inter — and finally to Arial, which the
   manual itself names as the universal fallback. To adopt the licensed
   fonts, drop the webfonts in static/fonts/, add @font-face rules, and put
   their family names at the front of --font-display / --font-text. Nothing
   else in this file needs to change.
   ========================================================================== */

/* ── Tokens ─────────────────────────────────────────────────────────────── */
:root {
  /* Brand */
  --brand-blue: #3452f8;
  --brand-blue-600: #2a44d8;
  --brand-blue-700: #2138b4;
  --brand-green: #3bd895;
  --brand-green-600: #2ec07f;
  --brand-dark: #022b26;

  /* Neutrals — tinted towards the brand dark rather than pure grey, which is
     what keeps large flat areas from reading as a default browser page. */
  --ink-900: #022b26;
  --ink-700: #1f3d38;
  --ink-500: #55706a;
  --ink-400: #5f7873;   /* 4.75:1 on white — help text, table heads, captions */
  --ink-300: #a7b8b4;
  --ink-200: #d3dedb;
  --ink-100: #e8efed;
  --ink-050: #f4f8f7;
  --surface: #ffffff;
  --canvas: #f4f6f9;

  /* Semantic */
  --ok: #127a4f;        /* 4.86:1 inside --ok-bg */
  --ok-bg: #e7f8ef;
  --ok-border: #b6e7ce;
  --warn: #9a6700;
  --warn-bg: #fff6e0;
  --warn-border: #f2d999;
  /* The staff-preview bar. Loud on purpose; #022b26 on it is 8.9:1. */
  --preview-bar: #fbbf24;
  --danger: #c8342b;
  --danger-bg: #fdeceb;
  --danger-border: #f6c4c0;
  --info: #2138b4;
  --info-bg: #ecefff;
  --info-border: #c5cdfb;

  /* Typography */
  --font-display: "Archivo Expanded", "Archivo", Arial, sans-serif;
  --font-text: "Inter", Arial, Helvetica, sans-serif;
  --font-mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, monospace;

  /* Fluid type scale — clamp() so small screens are not simply "the desktop
     size, smaller"; each step keeps a readable floor. */
  --t-xs: 0.75rem;
  --t-sm: 0.8125rem;
  --t-base: 0.9375rem;
  --t-md: 1rem;
  --t-lg: clamp(1.0625rem, 0.4vw + 0.97rem, 1.1875rem);
  --t-xl: clamp(1.25rem, 0.8vw + 1.06rem, 1.5rem);
  --t-2xl: clamp(1.5rem, 1.6vw + 1.13rem, 2.125rem);
  --t-3xl: clamp(1.875rem, 2.6vw + 1.26rem, 2.75rem);

  /* Space — 4px base */
  --s-1: 0.25rem;
  --s-2: 0.5rem;
  --s-3: 0.75rem;
  --s-4: 1rem;
  --s-5: 1.25rem;
  --s-6: 1.5rem;
  --s-8: 2rem;
  --s-10: 2.5rem;
  --s-12: 3rem;
  --s-16: 4rem;

  /* Shape */
  --r-sm: 8px;
  --r-md: 10px;
  --r-lg: 14px;
  --r-xl: 20px;
  --r-pill: 999px;

  /* Elevation — layered and blue-tinted; a single hard shadow is the tell of
     an untouched default. */
  --sh-1: 0 1px 2px rgba(2, 43, 38, 0.05), 0 1px 3px rgba(2, 43, 38, 0.04);
  --sh-2: 0 2px 4px rgba(2, 43, 38, 0.04), 0 6px 16px rgba(52, 82, 248, 0.06);
  --sh-3: 0 4px 8px rgba(2, 43, 38, 0.05), 0 16px 40px rgba(52, 82, 248, 0.10);
  --sh-focus: 0 0 0 3px rgba(52, 82, 248, 0.28);
  --sh-focus-green: 0 0 0 3px rgba(59, 216, 149, 0.40);

  --dur: 160ms;
  --ease: cubic-bezier(0.4, 0, 0.2, 1);
  --shell: 1180px;
  --shell-narrow: 760px;
}

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  min-height: 100vh;
  font-family: var(--font-text);
  font-size: var(--t-base);
  line-height: 1.55;
  color: var(--ink-900);
  background: var(--canvas);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

[hidden] { display: none !important; }

h1, h2, h3, h4 { margin: 0; font-weight: 600; line-height: 1.2; letter-spacing: -0.01em; }
p { margin: 0; }
ul, ol { margin: 0; padding: 0; }
img, svg { max-width: 100%; }
a { color: var(--brand-blue); text-decoration: none; }
a:hover { text-decoration: underline; }

/* One visible focus treatment everywhere, keyboard-only. */
:focus-visible {
  outline: 2px solid var(--brand-blue);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}
.on-brand :focus-visible { outline-color: #fff; }

/* ── Typography helpers ─────────────────────────────────────────────────── */
.display { font-family: var(--font-display); font-weight: 700; letter-spacing: -0.02em; }
.t-3xl { font-size: var(--t-3xl); }
.t-2xl { font-size: var(--t-2xl); }
.t-xl { font-size: var(--t-xl); }
.t-lg { font-size: var(--t-lg); }
.t-md { font-size: var(--t-md); }
.t-sm { font-size: var(--t-sm); }
.t-xs { font-size: var(--t-xs); }
.mono { font-family: var(--font-mono); font-size: 0.9em; font-variant-ligatures: none; }
.muted { color: var(--ink-500); }
.subtle { color: var(--ink-400); }
.strong { font-weight: 600; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nowrap { white-space: nowrap; }
@media (max-width: 640px) { .hide-sm { display: none; } }

.eyebrow {
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-400);
}

/* Screen-reader-only: real labels for inputs that show only a placeholder. */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
/* …unless it is focusable, in which case it has to be reachable on screen. */
a.sr-only:focus, a.sr-only:focus-visible, button.sr-only:focus {
  position: fixed; top: var(--s-3); left: var(--s-3);
  width: auto; height: auto; margin: 0; overflow: visible;
  clip: auto; z-index: 100;
}

/* ── Layout ─────────────────────────────────────────────────────────────── */
.shell { width: 100%; max-width: var(--shell); margin: 0 auto; padding: 0 var(--s-5); }
.shell-narrow { max-width: var(--shell-narrow); }
.page { padding: var(--s-8) 0 var(--s-16); }

.stack > * + * { margin-top: var(--s-4); }
.stack-lg > * + * { margin-top: var(--s-6); }
.stack-sm > * + * { margin-top: var(--s-2); }

.row { display: flex; align-items: center; gap: var(--s-3); }
.row-between { display: flex; align-items: center; justify-content: space-between; gap: var(--s-4); }
.row-wrap { flex-wrap: wrap; }
.row-top { align-items: flex-start; }
.spacer { flex: 1 1 auto; }

.grid { display: grid; gap: var(--s-5); }
.grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
/* 230px keeps four tiles on a desktop row; below that the fallback is two
   up rather than one, so the table is not pushed a screen down. */
.grid-auto { grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
/* A grid item's default `min-width: auto` is its content's intrinsic width, so a
   cell holding a button wider than its track overflows into the next one rather
   than shrinking. On the instance card at 390px "Zkontrolovat nyní" sat on top
   of the port number — measured 2026-09-12. Nothing clipped it, because the
   overflow was into a sibling and not past the viewport. */
.grid > *, .grid-auto > * { min-width: 0; }
@media (max-width: 640px) {
  .grid-auto { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-3); }
  .stat { padding: var(--s-4); }
  .stat-value { font-size: var(--t-xl); }
  /* A button in a two-up cell takes the cell, rather than deciding the width of
     a column it shares with a number. */
  .grid-auto > * > .btn,
  .grid-auto > * > form > .btn { width: 100%; }
}
.col-span-2 { grid-column: span 2; }

@media (max-width: 860px) {
  .grid-2, .grid-3 { grid-template-columns: minmax(0, 1fr); }
  .col-span-2 { grid-column: auto; }
  .page { padding: var(--s-6) 0 var(--s-12); }
}

/* ── App bar (internal admin) ───────────────────────────────────────────── */
.appbar {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--brand-dark);
  /* A hairline of brand blue reads as intent; a plain edge reads as unstyled. */
  box-shadow: inset 0 -1px 0 rgba(255, 255, 255, 0.07), 0 1px 0 rgba(2, 43, 38, 0.06);
}
.appbar-inner { display: flex; align-items: center; gap: var(--s-5); min-height: 60px; }
.appbar-logo { display: flex; align-items: center; flex-shrink: 0; }
.appbar-logo svg { width: 86px; height: auto; display: block; }
.appbar-wordmark {
  color: rgba(255, 255, 255, 0.5);
  font-size: var(--t-xs);
  letter-spacing: 0.06em;
  padding-left: var(--s-4);
  margin-left: var(--s-1);
  border-left: 1px solid rgba(255, 255, 255, 0.14);
}
.appbar-actions { display: flex; align-items: center; gap: var(--s-3); margin-left: auto; }
.appbar-user {
  display: flex; align-items: center; gap: var(--s-2);
  color: rgba(255, 255, 255, 0.72);
  font-size: var(--t-sm);
}
.avatar {
  display: grid; place-items: center;
  width: 28px; height: 28px;
  border-radius: var(--r-pill);
  background: rgba(59, 216, 149, 0.18);
  color: var(--brand-green);
  font-size: var(--t-xs);
  font-weight: 600;
  text-transform: uppercase;
}

@media (max-width: 640px) {
  .appbar-wordmark { display: none; }
  .appbar-user span:not(.avatar) { display: none; }
  .appbar-inner { gap: var(--s-3); }
}

/* ── Segmented control (language switch) ────────────────────────────────── */
.segmented {
  display: inline-flex;
  gap: 2px;
  padding: 3px;
  background: rgba(255, 255, 255, 0.10);
  border-radius: var(--r-pill);
}
.segmented.on-light { background: var(--ink-100); }
.segmented button {
  border: 0;
  cursor: pointer;
  border-radius: var(--r-pill);
  padding: 5px 11px;
  font-family: inherit;
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: 0.03em;
  background: transparent;
  color: rgba(255, 255, 255, 0.85);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.segmented button:hover { color: rgba(255, 255, 255, 0.9); }
.segmented button[aria-pressed="true"] { background: rgba(255, 255, 255, 0.22); color: #fff; }
.segmented.on-light button { color: var(--ink-500); }
.segmented.on-light button:hover { color: var(--ink-900); }
.segmented.on-light button[aria-pressed="true"] { background: var(--surface); color: var(--brand-blue); box-shadow: var(--sh-1); }

/* ── Buttons ────────────────────────────────────────────────────────────── */
.btn {
  --btn-bg: var(--brand-blue);
  --btn-fg: #fff;
  --btn-bd: transparent;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  /* 44px min target: the old 1.5rem-tall buttons were a miss on touch. */
  min-height: 44px;
  padding: 0 var(--s-5);
  border: 1px solid var(--btn-bd);
  border-radius: var(--r-pill);
  background: var(--btn-bg);
  color: var(--btn-fg);
  max-width: 100%;
  font-family: inherit;
  font-size: var(--t-base);
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.btn:hover { background: var(--brand-blue-600); text-decoration: none; }
.btn:active { transform: translateY(1px); }
.btn svg { width: 17px; height: 17px; flex-shrink: 0; }

/* White on the connecting green is 1.84:1 — unreadable. The brand dark on it
   is 8.31:1, and pairs two brand colours, so the CTA stays green. */
.btn-accent { --btn-bg: var(--brand-green); --btn-fg: var(--brand-dark); }
.btn-accent:hover { background: var(--brand-green-600); }

.btn-ghost { --btn-bg: transparent; --btn-fg: var(--ink-700); --btn-bd: var(--ink-200); }
.btn-ghost:hover { background: var(--ink-050); border-color: var(--ink-300); }

.btn-danger { --btn-bg: transparent; --btn-fg: var(--danger); --btn-bd: var(--danger-border); }
.btn-danger:hover { background: var(--danger-bg); border-color: var(--danger); }

.btn-on-brand { --btn-bg: rgba(255,255,255,0.14); --btn-fg: rgba(255,255,255,0.92); }
.btn-on-brand:hover { background: rgba(255, 255, 255, 0.24); }

.btn-sm { min-height: 36px; padding: 0 var(--s-4); font-size: var(--t-sm); }
.btn-lg { min-height: 52px; padding: 0 var(--s-6); font-size: var(--t-md); }
.btn-block { width: 100%; }

.btn:disabled, .btn[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

.btn-link {
  background: none; border: 0; padding: 0;
  color: var(--brand-blue); font: inherit; font-weight: 600;
  cursor: pointer;
}
.btn-link:hover { text-decoration: underline; }

/* ── Cards & widgets ────────────────────────────────────────────────────── */
.card {
  background: var(--surface);
  border: 1px solid var(--ink-100);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
}
.card-pad { padding: var(--s-6); }
.card-head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-4);
  flex-wrap: wrap;
  padding: var(--s-4) var(--s-6);
  border-bottom: 1px solid var(--ink-100);
}
/* The instance card's head carries four action buttons. At 390px they were
   197px wider than the phone and pushed the whole page sideways — measured
   2026-09-12 — because the head could not wrap and the row inside it could not
   either. Both wrap now, and on a phone the actions take the full width rather
   than squeezing onto a line with the heading. */
.card-head > .row { flex-wrap: wrap; min-width: 0; }
@media (max-width: 560px) {
  .card-head { align-items: flex-start; }
  .card-head > .row { width: 100%; }
  .card-head > .row > form, .card-head > .row > form > .btn { width: 100%; }
  .card-head > .row > .btn { flex: 1 1 auto; justify-content: center; }
}
.card-head h2, .card-head h3 { font-size: var(--t-base); font-weight: 600; }
.card-body { padding: var(--s-6); }
.card-foot {
  padding: var(--s-4) var(--s-6);
  border-top: 1px solid var(--ink-100);
  background: var(--ink-050);
  border-radius: 0 0 var(--r-lg) var(--r-lg);
}
.card-hover { transition: box-shadow var(--dur) var(--ease), border-color var(--dur) var(--ease); }
.card-hover:hover { box-shadow: var(--sh-2); border-color: var(--ink-200); }

@media (max-width: 640px) {
  .card-pad, .card-body { padding: var(--s-5); }
  .card-head, .card-foot { padding: var(--s-4) var(--s-5); }
}

/* Stat tile — a number that means something, not a bare <dt>/<dd>. */
.stat {
  position: relative;
  padding: var(--s-5);
  background: var(--surface);
  border: 1px solid var(--ink-100);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
  overflow: hidden;
}
.stat::before {
  content: "";
  position: absolute; inset: 0 auto 0 0;
  width: 3px;
  background: var(--stat-accent, var(--ink-200));
}
.stat-ok { --stat-accent: var(--brand-green); }
.stat-warn { --stat-accent: #e3a008; }
.stat-danger { --stat-accent: var(--danger); }
.stat-info { --stat-accent: var(--brand-blue); }
.stat-label { font-size: var(--t-xs); font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-400); }
.stat-value { font-family: var(--font-display); font-size: var(--t-2xl); font-weight: 700; line-height: 1.05; margin-top: var(--s-2); letter-spacing: -0.02em; }
.stat-note { font-size: var(--t-xs); color: var(--ink-400); margin-top: var(--s-1); }

/* Definition rows — replaces the flex-justify-between <dl> soup. */
.deflist { display: grid; gap: 0; }
.deflist > div {
  display: grid;
  /* Capped, not a percentage: on a full-width card 38% pushed the value half a
     screen away from the label it belongs to. */
  grid-template-columns: minmax(120px, 15rem) 1fr;
  gap: var(--s-4);
  padding: var(--s-3) 0;
  border-bottom: 1px solid var(--ink-050);
  font-size: var(--t-base);
}
.deflist > div:first-child { padding-top: 0; }
.deflist > div:last-child { border-bottom: 0; padding-bottom: 0; }
.deflist dt { color: var(--ink-500); }
.deflist dd { margin: 0; color: var(--ink-900); min-width: 0; overflow-wrap: anywhere; }

@media (max-width: 520px) {
  .deflist > div { grid-template-columns: minmax(0, 1fr); gap: 2px; }
  .deflist dt { font-size: var(--t-xs); }
}

/* ── Status pills ───────────────────────────────────────────────────────── */
.pill {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: 5px 11px 5px 9px;
  border-radius: var(--r-pill);
  font-size: var(--t-xs);
  font-weight: 600;
  border: 1px solid transparent;
  white-space: nowrap;
}
.pill .dot { width: 7px; height: 7px; border-radius: var(--r-pill); flex-shrink: 0; background: currentColor; }
.pill-ok { color: var(--ok); background: var(--ok-bg); border-color: var(--ok-border); }
.pill-warn { color: var(--warn); background: var(--warn-bg); border-color: var(--warn-border); }
.pill-danger { color: var(--danger); background: var(--danger-bg); border-color: var(--danger-border); }
.pill-info { color: var(--info); background: var(--info-bg); border-color: var(--info-border); }
/* Slate, not the teal-tinted neutral: the status dot inherits this colour, and
   a green-ish dot on "unknown" or "not created" reads as healthy. 5.46:1. */
.pill-neutral { color: #5b6673; background: var(--ink-050); border-color: var(--ink-200); }
.pill-lg { font-size: var(--t-sm); padding: 7px 14px 7px 11px; }

/* "Live" only where something really is live, and never for reduced motion. */
.dot-live { position: relative; }
.dot-live::after {
  content: "";
  position: absolute; inset: -4px;
  border-radius: var(--r-pill);
  border: 1px solid currentColor;
  opacity: 0;
  animation: ping 2.4s var(--ease) infinite;
}
@keyframes ping {
  0% { transform: scale(0.7); opacity: 0.7; }
  70%, 100% { transform: scale(1.6); opacity: 0; }
}

/* ── Alerts ─────────────────────────────────────────────────────────────── */
.alert {
  display: flex;
  gap: var(--s-3);
  padding: var(--s-4) var(--s-5);
  border-radius: var(--r-md);
  border: 1px solid var(--info-border);
  background: var(--info-bg);
  color: var(--info);
  font-size: var(--t-base);
}
.alert-success { border-color: var(--ok-border); background: var(--ok-bg); color: var(--ok); }
.alert-error { border-color: var(--danger-border); background: var(--danger-bg); color: var(--danger); }
.alert-warning { border-color: var(--warn-border); background: var(--warn-bg); color: var(--warn); }
.alert svg { width: 18px; height: 18px; flex-shrink: 0; margin-top: 1px; }
.alert-body { min-width: 0; }
.alert-title { font-weight: 600; }
.alert ul { list-style: none; }

/* Banner — an alert that also carries an action. */
.banner {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-5); flex-wrap: wrap;
  padding: var(--s-5);
  border-radius: var(--r-lg);
  border: 1px solid var(--warn-border);
  background: linear-gradient(180deg, #fffaf0, var(--warn-bg));
}

/* ── Forms ──────────────────────────────────────────────────────────────── */
.field { min-width: 0; }
.label {
  display: block;
  font-size: var(--t-sm);
  font-weight: 600;
  color: var(--ink-700);
  margin-bottom: var(--s-2);
}
.label-req { color: var(--danger); margin-left: 2px; }
.help { font-size: var(--t-xs); color: var(--ink-400); margin-top: var(--s-2); }
.error { font-size: var(--t-xs); color: var(--danger); margin-top: var(--s-2); font-weight: 500; }

.input, .fi {
  display: block;
  width: 100%;
  min-height: 44px;
  padding: 0.6rem 0.9rem;
  border: 1px solid var(--ink-200);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--ink-900);
  font-family: inherit;
  font-size: var(--t-md);
  line-height: 1.4;
  outline: none;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
/* Placeholders are text too: ink-300 was 2.07:1. The filled value uses
   ink-900 at 15:1, so the two remain easy to tell apart. */
.input::placeholder, .fi::placeholder { color: var(--ink-400); }
.input:hover, .fi:hover { border-color: var(--ink-300); }
.input:focus, .fi:focus { border-color: var(--brand-blue); box-shadow: var(--sh-focus); }
.input-error, .fi-err { border-color: var(--danger); }
.input-error:focus, .fi-err:focus { box-shadow: 0 0 0 3px rgba(200, 52, 43, 0.22); }

select.input, select.fi, .fi-select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 2.5rem;
  cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2355706a' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.85rem center;
}
textarea.input, textarea.fi, .fi-ta {
  min-height: 6rem;
  resize: vertical;
  line-height: 1.6;
}
.fi-flex { width: auto; flex: 1 1 0; min-width: 0; }

/* Checkbox / radio as real chips — a bare checkbox row is the "20 years old"
   look the redesign is meant to remove. */
.choice-set { display: flex; flex-wrap: wrap; gap: var(--s-3); }
.choice {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  min-height: 44px;
  border: 1px solid var(--ink-200);
  border-radius: var(--r-md);
  background: var(--surface);
  cursor: pointer;
  font-size: var(--t-base);
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.choice:hover { border-color: var(--ink-300); background: var(--ink-050); }
.choice input { accent-color: var(--brand-blue); width: 18px; height: 18px; margin: 0; cursor: pointer; }
.choice:has(input:checked) {
  border-color: var(--brand-blue);
  background: var(--info-bg);
  box-shadow: inset 0 0 0 1px var(--brand-blue);
}

/* Date field.

   The native control is replaced outright rather than restyled. Two attempts at
   restyling it were rejected on the same ground: the popup is the browser's
   chrome, the segmented dd/mm/yyyy value paints its own colours, and neither
   obeys this stylesheet. `_datepicker.html` renders a real calendar over a
   plain text input, so the value is spelled by the server rather than by
   whatever locale the browser happens to run in.

   `-webkit-calendar-picker-indicator` is hidden, not stretched: our own popup
   opens on click, and two calendars opening at once is worse than either. */
.fi-date {
  padding-right: 2.75rem;
  cursor: pointer;
}
.fi-date::-webkit-calendar-picker-indicator { display: none; }
.fi-date::-webkit-inner-spin-button,
.fi-date::-webkit-clear-button { display: none; }

.datefield { position: relative; }
.datefield-btn {
  position: absolute;
  top: 1px; right: 1px; bottom: 1px;
  width: 2.6rem;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 0 var(--r-md) var(--r-md) 0;
  background: transparent;
  color: var(--ink-500);
  cursor: pointer;
  transition: color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.datefield-btn:hover { color: var(--brand-blue); background: var(--ink-050); }
.datefield-btn:focus-visible { outline: none; box-shadow: var(--sh-focus); color: var(--brand-blue); }
.datefield-btn svg { width: 18px; height: 18px; }

.datepicker {
  position: absolute;
  z-index: 40;
  top: calc(100% + 6px);
  left: 0;
  width: 19rem;
  padding: var(--s-4);
  border: 1px solid var(--ink-100);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--sh-3);
}
.datepicker[hidden] { display: none; }
/* Flipped above the field when there is no room below — a picker on the last
   row of a long form otherwise opens off-screen. */
.datepicker.is-above { top: auto; bottom: calc(100% + 6px); }

.datepicker-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-2);
  margin-bottom: var(--s-3);
}
.datepicker-title {
  font-weight: 700;
  font-size: var(--t-base);
  color: var(--ink-900);
  text-transform: capitalize;
}
.datepicker-nav {
  display: grid;
  place-items: center;
  width: 32px; height: 32px;
  border: 1px solid var(--ink-200);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--ink-700);
  cursor: pointer;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.datepicker-nav:hover { border-color: var(--brand-blue); color: var(--brand-blue); }
.datepicker-nav:focus-visible { outline: none; box-shadow: var(--sh-focus); }
.datepicker-nav svg { width: 16px; height: 16px; }

.datepicker-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 2px;
}
.datepicker-dow {
  padding: var(--s-2) 0;
  text-align: center;
  font-size: var(--t-xs);
  font-weight: 600;
  color: var(--ink-400);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.datepicker-day {
  aspect-ratio: 1;
  min-height: 34px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--r-md);
  background: transparent;
  color: var(--ink-900);
  font-family: inherit;
  font-size: var(--t-base);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.datepicker-day:hover { background: var(--ink-100); }
.datepicker-day:focus-visible { outline: none; box-shadow: var(--sh-focus); }
.datepicker-day.is-outside { color: var(--ink-300); }
/* Today is outlined, the selection is filled: two different statements, and a
   day that is both must still read as selected. */
.datepicker-day.is-today { box-shadow: inset 0 0 0 1.5px var(--ink-300); font-weight: 600; }
.datepicker-day.is-selected {
  background: var(--brand-blue);
  color: #fff;
  font-weight: 600;
  box-shadow: none;
}
.datepicker-day.is-selected:hover { background: var(--brand-blue-700); }

.datepicker-foot {
  display: flex;
  gap: var(--s-2);
  margin-top: var(--s-3);
  padding-top: var(--s-3);
  border-top: 1px solid var(--ink-100);
}
.datepicker-foot .btn { flex: 1 1 0; justify-content: center; }

.fi-num { max-width: 12rem; }

/* Two fields that are one statement — an amount and its currency. Side by side
   whatever the surrounding grid is doing: putting them last in a two-column flow
   left them on different rows, diagonally apart. */
.field-pair {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 9rem);
  gap: var(--s-3);
  align-items: start;
}
.field-pair .field { min-width: 0; }
.field-pair .fi-num { max-width: none; }
@media (max-width: 480px) {
  .field-pair { grid-template-columns: minmax(0, 1fr); }
}



/* Switch — for the questions that are genuinely yes/no. A bare tick box is the
   one control the redesign could not restyle into the rest of the system. */
.switch {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  cursor: pointer;
  user-select: none;
  min-height: 44px;
}
.switch-input {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.switch-track {
  position: relative;
  flex: none;
  width: 46px;
  height: 26px;
  border-radius: 999px;
  background: var(--ink-200);
  transition: background var(--dur) var(--ease);
}
.switch-track::after {
  content: "";
  position: absolute;
  top: 3px; left: 3px;
  width: 20px; height: 20px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 2px rgba(2, 43, 38, 0.28);
  transition: transform var(--dur) var(--ease);
}
.switch-input:checked + .switch-track { background: var(--brand-blue); }
.switch-input:checked + .switch-track::after { transform: translateX(20px); }
.switch:hover .switch-track { background: var(--ink-300); }
.switch:hover .switch-input:checked + .switch-track { background: var(--brand-blue-700, var(--brand-blue)); }
/* `:focus-visible` on a visually hidden input, so the ring lands on the track
   the user can actually see. */
.switch-input:focus-visible + .switch-track {
  box-shadow: var(--sh-focus);
}
.switch-state { font-size: var(--t-base); font-weight: 600; color: var(--ink-700); }
.switch-state .switch-on { display: none; }
.switch-input:checked ~ .switch-state .switch-on { display: inline; }
.switch-input:checked ~ .switch-state .switch-off { display: none; }

/* The staff preview bar. Sticky at the very top, outside the page body: the
   point of the preview is to see what the customer sees, and a panel in the body
   pushes their content down and changes the thing being looked at. */
.preview-bar {
  position: sticky;
  top: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  flex-wrap: wrap;
  padding: var(--s-2) var(--s-5);
  /* Amber, not the brand dark. The bar has one job -- to be noticed -- and dark
     green reads as more chrome on a page that already has a dark header. Amber
     is the convention for "you are not looking at this as yourself", and
     #022b26 on it measures 8.9:1. */
  background: var(--preview-bar);
  color: var(--ink-900);
  font-size: var(--t-sm);
  font-weight: 600;
  box-shadow: 0 1px 0 rgba(2, 43, 38, 0.16);
}
.preview-bar-text { display: inline-flex; align-items: center; gap: var(--s-2); font-weight: 600; }
.preview-bar svg { width: 15px; height: 15px; flex: none; }
.preview-bar-exit {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-height: 32px;
  padding: 0 var(--s-3);
  border: 1px solid rgba(2, 43, 38, 0.35);
  border-radius: var(--r-pill);
  color: var(--ink-900);
  text-decoration: none;
  font-weight: 600;
  white-space: nowrap;
  transition: background var(--dur) var(--ease);
}
.preview-bar-exit:hover { background: rgba(2, 43, 38, 0.12); }
.preview-bar-exit:focus-visible { outline: none; box-shadow: var(--sh-focus); }
@media (hover: none), (max-width: 640px) {
  .preview-bar-exit { min-height: 40px; }
}

/* A square button carrying only an icon — the close on an edit form. Sized to
   the same 44px as every other button, so being wordless does not make it a
   smaller target. */
.btn-icon { width: 44px; padding: 0; flex: none; }
.btn-icon svg { width: 18px; height: 18px; }

/* Section tabs on one customer. Links, not buttons: each is a page of its own,
   so the browser's history, middle-click and "open in new tab" all work. */
.tabs {
  display: flex;
  gap: var(--s-1);
  flex-wrap: wrap;
  margin-bottom: var(--s-6);
  border-bottom: 1px solid var(--ink-100);
}
.tab {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-3) var(--s-4);
  min-height: 44px;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  color: var(--ink-500);
  font-size: var(--t-base);
  font-weight: 600;
  text-decoration: none;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.tab svg { width: 17px; height: 17px; }
.tab:hover { color: var(--ink-900); border-bottom-color: var(--ink-200); }
.tab.is-current { color: var(--brand-blue); border-bottom-color: var(--brand-blue); }
.tab:focus-visible { outline: none; box-shadow: var(--sh-focus); border-radius: var(--r-md); }

.count-badge-warn { background: var(--warn-bg); color: var(--warn); }

/* A whole row that opens its customer. The pointer and the hover tint are the
   only affordance a table row can offer, so both are on the row itself rather
   than on the one cell that happens to hold a link. */
.table tbody tr.row-link { cursor: pointer; }
.table tbody tr.row-link:hover { background: var(--ink-050); }
.table tbody tr.row-link:focus-within { background: var(--ink-050); }

/* A read-only list of addresses. Eighteen mailboxes joined by commas is one
   long line that wraps mid-address, and the only question anyone asks of it --
   "is this person in it?" -- cannot be answered by scanning that. A column grid
   can be scanned, and the count answers the other question without counting. */
.maillist {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: var(--s-1) var(--s-4);
  margin: 0;
  padding: 0;
  list-style: none;
}
.maillist li {
  font-size: var(--t-xs);
  color: var(--ink-700);
  padding: 3px 0;
  overflow-wrap: anywhere;
}
.maillist-muted li { color: var(--ink-500); }
.count-badge {
  display: inline-block;
  min-width: 1.5rem;
  padding: 1px 7px;
  margin-left: var(--s-2);
  border-radius: 999px;
  background: var(--ink-100);
  color: var(--ink-600);
  font-size: var(--t-xs);
  font-weight: 600;
  text-align: center;
}

.input-icon { position: relative; }
.input-icon svg {
  position: absolute; left: 0.9rem; top: 50%;
  transform: translateY(-50%);
  width: 18px; height: 18px;
  color: var(--ink-300);
  pointer-events: none;
}
.input-icon .input, .input-icon .fi { padding-left: 2.7rem; }

/* The mailbox rows go two-up once there is room. Eighteen full-width rows is a
   screen and a half of scrolling for a list whose whole job is to be looked
   over -- and `.stack-sm` stacked them even on a 1440px form. */
.email-rows {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: var(--s-2) var(--s-4);
}
.email-row { display: flex; align-items: center; gap: var(--s-2); min-width: 0; }
.email-remove {
  display: grid; place-items: center;
  width: 36px; height: 36px; flex-shrink: 0;
  border: 0; border-radius: var(--r-pill);
  background: transparent;
  color: var(--ink-300);
  font-size: 1.25rem; line-height: 1;
  cursor: pointer;
  transition: color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.email-remove:hover { color: var(--danger); background: var(--danger-bg); }

/* Save and Cancel — a plain right-aligned row at the end of the form.

   Sticky was tried and was wrong. It spent most of a long form as a white band
   floating over the content, and at the end of the page it separated from the
   last card and read as a stray element; on a short form it hung below the card
   with a gap. The bar drew attention to itself in every state except the one it
   was meant to help with.

   Right-aligned, primary last: the form is a stack of cards, the actions close
   it, and the eye finishing a row of fields ends on the right. Cancel sits to
   the left of Save so the destructive-by-accident option is not the one under
   the thumb.

   Long forms are answered by the close (X) in the page header instead, which is
   reachable without scrolling at all. */
.form-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s-3);
  flex-wrap: wrap;
  padding-top: var(--s-2);
}
@media (max-width: 520px) {
  .form-actions { flex-direction: column-reverse; align-items: stretch; }
  .form-actions .btn { width: 100%; }
}

/* ── Data table → cards on narrow screens ───────────────────────────────── */
.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.table { width: 100%; border-collapse: collapse; font-size: var(--t-base); }
.table thead th {
  text-align: left;
  padding: var(--s-3) var(--s-5);
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-400);
  background: var(--ink-050);
  border-bottom: 1px solid var(--ink-100);
  white-space: nowrap;
}
.table tbody td { padding: var(--s-4) var(--s-5); border-bottom: 1px solid var(--ink-050); vertical-align: middle; }
.table tbody tr:last-child td { border-bottom: 0; }
.table tbody tr { transition: background var(--dur) var(--ease); }
.table tbody tr:hover { background: var(--ink-050); }
.table .cell-right { text-align: right; }

.row-title { font-weight: 600; color: var(--ink-900); }
.row-title:hover { color: var(--brand-blue); text-decoration: none; }
.row-sub { font-size: var(--t-xs); color: var(--ink-400); margin-top: 2px; }

/* Below 900px the table becomes a stack of cards: each cell keeps its label
   through data-label, so nothing depends on the header row any more. */
@media (max-width: 900px) {
  .table thead { display: none; }
  .table, .table tbody, .table tr, .table td { display: block; width: 100%; }
  .table tbody tr {
    border: 1px solid var(--ink-100);
    border-radius: var(--r-lg);
    background: var(--surface);
    box-shadow: var(--sh-1);
    padding: var(--s-4);
    margin-bottom: var(--s-3);
  }
  .table tbody tr:hover { background: var(--surface); box-shadow: var(--sh-2); }
  .table tbody td { border: 0; padding: var(--s-1) 0; }
  .table tbody td[data-label] {
    display: grid;
    grid-template-columns: 40% 1fr;
    gap: var(--s-3);
    align-items: baseline;
  }
  .table tbody td[data-label]::before {
    content: attr(data-label);
    font-size: var(--t-xs);
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--ink-400);
  }
  .table td.cell-primary { padding-bottom: var(--s-3); }
  .table td.cell-right { text-align: left; padding-top: var(--s-3); }
  .table-card-hide { display: none !important; }
}

/* ── Toolbar (search / filter above a table) ────────────────────────────── */
.toolbar {
  display: flex; align-items: center; gap: var(--s-3); flex-wrap: wrap;
  padding: var(--s-4) var(--s-5);
  border-bottom: 1px solid var(--ink-100);
}
.toolbar .input-icon { flex: 1 1 220px; min-width: 0; }
.toolbar .input { min-height: 40px; }

/* ── Empty state ────────────────────────────────────────────────────────── */
.empty { padding: var(--s-16) var(--s-6); text-align: center; }
.empty-icon {
  display: grid; place-items: center;
  width: 56px; height: 56px; margin: 0 auto var(--s-4);
  border-radius: var(--r-pill);
  background: var(--ink-050);
  color: var(--ink-300);
}
.empty-icon svg { width: 26px; height: 26px; }
.empty h2 { font-size: var(--t-lg); margin-bottom: var(--s-2); }

/* ── Log viewer ─────────────────────────────────────────────────────────── */
.logview {
  margin: 0;
  padding: var(--s-5);
  max-height: 26rem;
  overflow: auto;
  background: var(--brand-dark);
  color: #cfe3de;
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  line-height: 1.65;
  white-space: pre;
  border-radius: 0 0 var(--r-lg) var(--r-lg);
  -webkit-overflow-scrolling: touch;
}

/* ── Breadcrumb ─────────────────────────────────────────────────────────── */
.crumbs { display: flex; align-items: center; gap: var(--s-2); font-size: var(--t-xs); color: var(--ink-400); }
.crumbs a { color: var(--ink-500); }
.crumbs a:hover { color: var(--brand-blue); text-decoration: none; }
.crumbs .sep { color: var(--ink-300); }

/* ── Page header ────────────────────────────────────────────────────────── */
.page-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--s-5); flex-wrap: wrap;
  margin-bottom: var(--s-6);
}
.page-head h1 { font-size: var(--t-xl); }
.page-head .page-sub { color: var(--ink-500); font-size: var(--t-sm); margin-top: 2px; }
.page-head-actions { display: flex; align-items: center; gap: var(--s-3); flex-wrap: wrap; }

@media (max-width: 640px) {
  .page-head-actions { width: 100%; }
  .page-head-actions .btn { flex: 1 1 auto; }
}

/* ── Reduced motion ─────────────────────────────────────────────────────── */
/* Nothing in the previous design honoured this; every animation below opts
   out, including the SVG SMIL on the login page (freeze, not remove). */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── Print ──────────────────────────────────────────────────────────────── */
@media print {
  .appbar, .page-head-actions, .toolbar { display: none !important; }
  body { background: #fff; }
  .card { box-shadow: none; break-inside: avoid; }
}

/* ==========================================================================
   Public / brand surfaces — login, password reset, registration, portal
   The admin keeps a calm dark chrome so data reads first; these pages are
   where the brand blue belongs, full-bleed.
   ========================================================================== */

.brandpage {
  min-height: 100vh;
  min-height: 100dvh;   /* iOS: 100vh sits under the browser chrome */
  background: var(--brand-blue);
  color: #fff;
  display: flex;
  flex-direction: column;
  position: relative;
  overflow: hidden;
}
/* A single soft radial keeps the flat blue from looking like an error page,
   without becoming a gradient-for-its-own-sake. */
.brandpage::before {
  content: "";
  position: absolute;
  inset: -30% -10% auto -10%;
  height: 70%;
  background: radial-gradient(60% 60% at 50% 0%, rgba(59, 216, 149, 0.18), transparent 70%);
  pointer-events: none;
}
.brandpage > * { position: relative; }

.brandpage-top {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-5) var(--s-6);
}
.brandpage-main {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: var(--s-6) var(--s-6) var(--s-10);
}
/* Admin chrome had no footer at all; this one carries the running release. */
.page-foot {
  border-top: 1px solid var(--ink-100);
  padding: var(--s-4) 0;
  margin-top: var(--s-8);
}
.foot-note { font-size: var(--t-xs); color: var(--ink-400); }

.brandpage-foot {
  padding: var(--s-6);
  text-align: center;
  font-size: var(--t-sm);
  color: rgba(255, 255, 255, 0.85);
}

.brand-logo svg { width: 132px; height: auto; display: block; }
.brand-logo-sm svg { width: 96px; }

.brand-card { width: 100%; max-width: 400px; }
.brand-card h1 {
  font-family: var(--font-display);
  font-size: var(--t-2xl);
  font-weight: 700;
  color: #fff;
  letter-spacing: -0.02em;
}
.brand-lede { color: rgba(255, 255, 255, 0.88); font-size: var(--t-md); line-height: 1.6; }

/* Inputs on the brand background: white fields, green focus — the blue focus
   ring would vanish against the blue. */
.brandpage .input, .brandpage .fi {
  border-color: transparent;
  min-height: 48px;
  font-size: var(--t-md);
}
.brandpage .input:focus, .brandpage .fi:focus {
  border-color: transparent;
  box-shadow: var(--sh-focus-green);
}
.brandpage .label { color: rgba(255, 255, 255, 0.9); }
.brandpage a { color: rgba(255, 255, 255, 0.9); }
.brandpage a:hover { color: #fff; }
.brandpage .error, .brandpage .form-error { color: #ffc9c4; }

.brand-status-icon {
  display: grid; place-items: center;
  width: 64px; height: 64px;
  margin: 0 auto var(--s-5);
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
}
.brand-status-icon svg { width: 30px; height: 30px; }
.brand-status-icon.is-ok { background: rgba(59, 216, 149, 0.2); color: var(--brand-green); }

/* The support block appears on both the blue brand pages and the light
   registration/portal pages. It defaults to the light-background colours;
   white links were unreadable on white until this was split. */
.support {
  display: flex; align-items: center; justify-content: center;
  gap: var(--s-3); flex-wrap: wrap;
  font-size: var(--t-sm);
}
.support a { color: var(--ink-500); }
.support .sep { color: var(--ink-300); }
.support-lead { font-size: var(--t-sm); color: var(--ink-500); }

.on-brand .support a, .brandpage .support a { color: rgba(255, 255, 255, 0.92); }
.on-brand .support a:hover, .brandpage .support a:hover { color: #fff; }
.on-brand .support .sep, .brandpage .support .sep { color: rgba(255, 255, 255, 0.45); }
.on-brand .support-lead, .brandpage .support-lead { color: rgba(255, 255, 255, 0.85); }

/* ── Login: split layout ────────────────────────────────────────────────── */
.login-split { display: grid; grid-template-columns: 1fr 460px; min-height: 100vh; min-height: 100dvh; }
/* Diagram and caption are one centred column. The caption used to be pinned to
   the bottom corner, which read as two unrelated things on one white field. */
.login-visual {
  position: relative;
  background: var(--surface);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--s-8);
  padding: var(--s-10);
  overflow: hidden;
}
.login-visual-caption { max-width: 30rem; text-align: center; }
.login-visual-caption h2 {
  font-family: var(--font-display);
  font-size: var(--t-xl);
  color: var(--ink-900);
  letter-spacing: -0.02em;
}
.login-visual-caption p { color: var(--ink-500); font-size: var(--t-base); margin-top: var(--s-3); }
.login-panel { display: flex; flex-direction: column; }

@media (max-width: 1024px) {
  .login-split { grid-template-columns: 1fr; }
  .login-visual { display: none; }
}

/* ── Registration wizard ────────────────────────────────────────────────── */
/* The steps used to be pills inside a card of their own, stacked above a card
   holding the form — the same container twice, plus a row of dots repeating
   what the pills already said. A rail alongside the form carries the same
   information in space that was empty, drops a panel, and leaves room to say
   what each step actually asks for. */
.wizard {
  display: grid;
  grid-template-columns: 17rem minmax(0, 1fr);
  gap: var(--s-8);
  align-items: start;
}
/* Sticky: on the longer steps the form scrolls past the rail, and losing your
   place in a five-step form is the whole problem a stepper exists to solve. */
.wizard-rail { position: sticky; top: var(--s-5); }

.stepper { list-style: none; margin: 0; padding: 0; }
.stepper-item { position: relative; }
/* Connector, drawn behind the markers and stopping at the last one. */
.stepper-item:not(:last-child)::before {
  content: "";
  position: absolute;
  left: 15px;
  top: 34px;
  /* Past the item's own edge so the segments meet the next marker, which paints
     its own background over the end of the line. */
  bottom: -6px;
  width: 2px;
  border-radius: 2px;
  background: var(--ink-100);
}
.stepper-item.is-done:not(:last-child)::before { background: var(--brand-green); }

.stepper-btn {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: var(--s-3);
  width: 100%;
  padding: var(--s-2) var(--s-2) var(--s-4) 0;
  border: 0;
  background: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
  color: var(--ink-400);
  transition: color var(--dur) var(--ease);
}
.stepper-btn:hover:not(:disabled) { color: var(--ink-700); }
.stepper-btn:disabled { cursor: not-allowed; opacity: 0.55; }

.stepper-marker {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 32px; height: 32px;
  border-radius: var(--r-pill);
  border: 2px solid var(--ink-200);
  background: var(--surface);
  color: var(--ink-400);
  font-size: var(--t-sm);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  transition: all var(--dur) var(--ease);
}
.stepper-marker .tick { display: none; width: 15px; height: 15px; }

.stepper-item.is-active .stepper-marker {
  border-color: var(--brand-blue);
  background: var(--brand-blue);
  color: #fff;
  box-shadow: 0 0 0 4px rgba(52, 82, 248, 0.14);
}
.stepper-item.is-done .stepper-marker {
  border-color: var(--brand-green);
  background: var(--brand-green);
  color: var(--brand-dark);
}
.stepper-item.is-done .stepper-marker .num { display: none; }
.stepper-item.is-done .stepper-marker .tick { display: block; }

.stepper-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; padding-top: 4px; }
.stepper-title { font-size: var(--t-base); font-weight: 600; }
.stepper-item.is-active .stepper-title { color: var(--brand-blue-700); }
.stepper-item.is-done .stepper-title { color: var(--ink-700); }
.stepper-hint { font-size: var(--t-xs); color: var(--ink-400); }

/* Compact indicator for the narrow layout, where a vertical rail would push
   the form off the first screen. */
.wizard-compact { display: none; }
.wizard-track {
  height: 6px;
  border-radius: var(--r-pill);
  background: var(--ink-100);
  overflow: hidden;
}
.wizard-fill {
  height: 100%;
  width: 20%;
  border-radius: var(--r-pill);
  background: linear-gradient(90deg, var(--brand-blue), var(--brand-green));
  transition: width 260ms var(--ease);
}
.wizard-compact-label {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-3);
  margin-top: var(--s-3);
  font-size: var(--t-sm);
}
.wizard-compact-step { font-weight: 600; }
.wizard-compact-count { color: var(--ink-400); font-variant-numeric: tabular-nums; }

@media (max-width: 900px) {
  .wizard { grid-template-columns: minmax(0, 1fr); gap: var(--s-5); }
  .wizard-rail { position: static; }
  .stepper { display: none; }
  .wizard-compact { display: block; }
}

.step-panel { display: none; }
.step-panel.is-active { display: block; animation: step-in 220ms var(--ease); }
@keyframes step-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

/* ── Portal (customer-facing) ───────────────────────────────────────────── */
.portal-hero {
  background: var(--brand-blue);
  color: #fff;
  /* Deep enough that the card below can lift into it and still leave a band of
     blue: with a shallower hero the overlap reads as a layout accident. */
  padding: var(--s-6) 0 var(--s-12);
  position: relative;
  overflow: hidden;
}
.portal-hero::before {
  content: "";
  position: absolute;
  inset: auto -10% -60% 40%;
  height: 120%;
  background: radial-gradient(50% 50% at 50% 50%, rgba(59, 216, 149, 0.22), transparent 70%);
  pointer-events: none;
}
.portal-hero > * { position: relative; }
.portal-hero h1 {
  font-family: var(--font-display);
  font-size: var(--t-2xl);
  color: #fff;
  margin-top: var(--s-6);
}
.portal-hero .portal-sub { color: rgba(255, 255, 255, 0.88); margin-top: var(--s-2); }
/* The content was pulled up into the hero with a negative margin. The hero is
   positioned, so it painted over the card's background while the card's own
   children still showed through — the wizard tabs appeared to float on the
   blue. A positive gap has no stacking order to get wrong, so the decoration
   goes rather than getting a z-index patch. */
.portal-body { margin-top: var(--s-6); padding-bottom: var(--s-16); }

/* ==========================================================================
   Login illustration — two lanes, one direction each
   The first attempt pulsed concentric circles behind rounded-square nodes; the
   two shapes fought each other and a frozen frame looked like a blob. Two
   labelled lanes state "both directions" even with every animation stopped,
   which is what a still screenshot — or a reduced-motion visitor — sees.
   ========================================================================== */
.flow { width: 100%; max-width: 40rem; height: auto; }
.flow-node { fill: #fff; stroke: var(--ink-100); stroke-width: 1.5; }
.flow-lane { stroke: var(--ink-100); stroke-width: 1.5; }
.flow-label { font-family: var(--font-text); font-size: 14px; font-weight: 600; fill: var(--ink-900); }
.flow-sub { font-family: var(--font-text); font-size: 11.5px; fill: var(--ink-400); }
.flow-lane-label { font-family: var(--font-text); font-size: 10.5px; font-weight: 600; letter-spacing: 0.06em; }

.flow-packet { animation: flow-right 4.5s linear infinite; }
.flow-packet-back { animation-name: flow-left; }
@keyframes flow-right {
  0%   { transform: translateX(0);     opacity: 0; }
  15%  { opacity: 1; }
  85%  { opacity: 1; }
  100% { transform: translateX(150px); opacity: 0; }
}
@keyframes flow-left {
  0%   { transform: translateX(0);      opacity: 0; }
  15%  { opacity: 1; }
  85%  { opacity: 1; }
  100% { transform: translateX(-150px); opacity: 0; }
}

/* Parked at the lane's start rather than hidden: the arrows already carry the
   meaning, and a stray dot mid-lane would look like a rendering fault. */
@media (prefers-reduced-motion: reduce) {
  .flow-packet { animation: none; opacity: 1; }
}

/* ==========================================================================
   Touch targets
   The compact variants are sized for a mouse. On a touch screen they fall
   under the ~44px a finger needs, and these are the controls that matter most
   on a phone: start/stop an instance, remove an address, switch language.
   ========================================================================== */
@media (hover: none), (max-width: 640px) {
  .btn-sm { min-height: 44px; padding: 0 var(--s-4); }
  /* Padding alone left these at 33px — measured 2026-09-12 at 390px. The floor
     is on the button, not on the pill around it. */
  .segmented button { padding: 9px 14px; min-height: 40px; }
  .email-remove { width: 44px; height: 44px; }
  .stepper-btn { padding-top: var(--s-3); padding-bottom: var(--s-5); }
  /* The card rows on the customer list are the tap target for the whole row. */
  .table tbody tr { padding: var(--s-5); }
  .table td.cell-right .btn { width: 100%; }
}

/* Long, unbreakable values — tenant ids, API keys, webhook URLs — are the
   usual source of a page that scrolls sideways on a phone. */
.mono, .deflist dd, .row-sub { overflow-wrap: anywhere; }

/* Nothing may push the page sideways; wide content scrolls inside its own box
   (.table-wrap, .logview) rather than dragging the whole layout with it.

   `clip`, not `hidden`. `overflow: hidden` makes the element a scroll container,
   and `position: sticky` sticks to its nearest scroll container -- so this rule
   silently un-stuck every sticky element on the page: the app bar, the preview
   bar and the wizard rail. `clip` crops without scrolling and leaves sticky
   alone. The `hidden` line before it is the fallback for a browser that does not
   know `clip`, which then scrolls-locks rather than leaking sideways. */
html, body {
  overflow-x: hidden;
  overflow-x: clip;
}

/* Per-mailbox sync activity. One block per mailbox, one row per direction.
   `-cold` marks a mailbox that has never synced in either direction — the shape
   of the complaint the panel exists to answer, so it should be findable without
   reading every row. */
.act-mailbox {
  padding: var(--s-3) 0;
  border-top: 1px solid var(--ink-100);
}
.act-mailbox:first-child { border-top: 0; padding-top: 0; }
.act-mailbox-cold { background: var(--warn-bg); border-radius: var(--r-sm); padding: var(--s-3); }
.act-email { font-weight: 600; margin-bottom: var(--s-2); }
.act-dir { display: flex; align-items: baseline; gap: var(--s-2); flex-wrap: wrap; padding: 2px 0; }
