/* Railnotes components — DESIGN_SPEC §3. Depends on tokens.css + base.css.
 * Every component from the inventory is built once here and reused everywhere. */

/* =========================================================================
   App chrome — header, drawer (§3.13)
   ========================================================================= */
.app-header {
  position: sticky; top: 0; z-index: 50;
  height: var(--header-h);
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 var(--page-pad-x);
  background: var(--color-surface);
  border-bottom: 2px solid var(--color-ink);
}
.app-header__brand {
  font-size: 26px; font-weight: 800; color: var(--color-ink); letter-spacing: -0.01em;
}
.app-header__brand:hover { text-decoration: none; }
.hamburger {
  width: var(--tap-min); height: var(--tap-min);
  display: flex; align-items: center; justify-content: center;
  color: var(--color-ink);
}
.hamburger svg { width: 26px; height: 26px; }

/* Footer copyright notice (helpers::copyright_notice()) — small, centred, on every
   page, below .app-content. */
.app-footer {
  text-align: center;
  padding: var(--space-4) var(--page-pad-x) var(--space-6);
}

.drawer-overlay {
  position: fixed; inset: 0; z-index: 60;
  background: var(--backdrop);
  display: flex; justify-content: flex-end;
}
.drawer-overlay[hidden] { display: none; }
.drawer {
  width: min(320px, 85vw); height: 100%;
  background: var(--color-surface);
  border-left: var(--border-card);
  padding: var(--space-4);
  display: flex; flex-direction: column; gap: var(--space-1);
  overflow-y: auto;
}
.drawer__head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: var(--space-2);
}
.drawer__item {
  display: flex; align-items: center; gap: var(--space-2);
  min-height: var(--tap-min);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-cell);
  font-size: var(--fs-body); color: var(--color-ink);
}
.drawer__item:hover { background: var(--color-surface-sunk); text-decoration: none; }
.drawer__item-icon { width: 18px; height: 18px; color: var(--color-ink-mute); }
.drawer__spacer { flex: 1 1 auto; }
.drawer__chip-row {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-2);
  flex-wrap: wrap;
  padding: var(--space-2) var(--space-3);
}
.sync-now {
  display: inline-flex; align-items: center; gap: var(--space-1);
  flex: 0 0 auto;
  min-height: 36px; padding: var(--space-1) var(--space-3);
  border: 2px solid var(--color-ink); border-radius: var(--radius-pill);
  background: var(--color-surface); color: var(--color-ink);
  font-size: var(--fs-caption); font-weight: 700;
}
.sync-now__icon { width: 14px; height: 14px; }
.sync-now:active { filter: brightness(0.96); }
.sync-now:disabled { opacity: 0.45; pointer-events: none; }

.drawer__search { padding: 0 var(--space-1) var(--space-2); display: flex; flex-direction: column; gap: var(--space-2); }
.drawer__search .field { margin: 0; }
.drawer__dropdown { display: flex; flex-direction: column; }
.drawer__toggle {
  width: 100%; border: none; background: none; cursor: pointer; text-align: left; font: inherit;
}
.drawer__toggle-label { flex: 1 1 auto; }
.drawer__toggle-chev { width: 16px; height: 16px; color: var(--color-ink-mute); flex: 0 0 auto; transition: transform 0.15s ease; }
.drawer__toggle[aria-expanded="true"] .drawer__toggle-chev { transform: rotate(180deg); }
.drawer__routes { list-style: none; display: flex; flex-direction: column; gap: 2px; padding: 2px 0 var(--space-1) var(--space-6); }
.drawer__routes[hidden] { display: none; }
.drawer__routes-item {
  display: block; min-height: 40px; padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-cell); font-size: var(--fs-small); color: var(--color-ink);
}
.drawer__routes-item:hover { background: var(--color-surface-sunk); text-decoration: none; }
.drawer__routes-empty { display: block; padding: var(--space-2) var(--space-3); }

/* Nested category "folders" inside the drawer Routes dropdown. */
.drawer__folder { list-style: none; }
.drawer__folder-toggle {
  display: flex; align-items: center; gap: var(--space-2);
  width: 100%; min-height: 40px; padding: var(--space-2) var(--space-3);
  border: none; background: none; cursor: pointer; text-align: left; font: inherit;
  border-radius: var(--radius-cell); color: var(--color-ink); font-size: var(--fs-small);
}
.drawer__folder-toggle:hover { background: var(--color-surface-sunk); }
.drawer__folder-count {
  flex: 0 0 auto; min-width: 20px; padding: 0 6px; text-align: center;
  font-size: var(--fs-caption); font-weight: 700; color: var(--color-ink-mute);
  background: var(--color-surface-sunk); border-radius: var(--radius-pill);
}
.drawer__folder-body { padding-left: var(--space-3); }
.drawer__folder-body[hidden] { display: none; }

/* =========================================================================
   Card base + section heading
   ========================================================================= */
.card {
  background: var(--color-surface);
  border: var(--border-card);
  border-radius: var(--radius-card);
  padding: var(--space-4);
}
@media (min-width: 600px) { .card { padding: var(--space-5); } }

.section-heading {
  font-size: var(--fs-display); font-weight: 900; line-height: 1.15;
  letter-spacing: 0.005em; color: var(--color-ink);
  margin-bottom: var(--space-3);
}

/* =========================================================================
   Buttons (§3.13)
   ========================================================================= */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  min-height: var(--tap-min); padding: var(--space-2) var(--space-4);
  border: 2px solid transparent; border-radius: var(--radius-pill);
  font-size: var(--fs-body-strong); font-weight: 700; text-align: center;
  transition: filter 0.1s ease, background-color 0.1s ease;
}
.btn:hover { text-decoration: none; }
.btn:active { filter: brightness(0.96); }
.btn:disabled, .btn[aria-disabled="true"] { opacity: 0.45; pointer-events: none; }

/* Contact/Codes/Connections keep the --tap-min (44px) floor via min-height on .btn —
   only padding/font-size are tightened, so the touch target never shrinks. */
.btn--contact, .btn--codes, .btn--connections { padding: var(--space-2) var(--space-3); font-size: var(--fs-small); }
.btn--contact     { background: var(--color-blue-fill);   border-color: var(--color-blue-border);   color: var(--color-blue-text); }
.btn--codes       { background: var(--color-purple-fill); border-color: var(--color-purple-border); color: var(--color-purple-text); }
.btn--connections { background: var(--color-teal-fill);   border-color: var(--color-teal-border);   color: var(--color-teal-text); }
.btn--primary   { background: var(--color-ink);         border-color: var(--color-ink);           color: #fff; }
.btn--secondary { background: var(--color-surface);     border-color: var(--color-ink);           color: var(--color-ink); }
.btn--danger    { background: var(--color-red-fill);    border-color: var(--color-red-border);    color: var(--color-red-text); }
.btn--list {
  width: 100%; border-radius: var(--radius-cell);
  background: var(--color-surface-sunk); border-color: var(--color-ink); color: var(--color-ink);
  font-weight: 600;
}
.btn--ghost {
  min-height: auto; padding: 0; border: none; background: none;
  color: var(--color-blue-text); font-weight: 700;
}
.btn--ghost:hover { text-decoration: underline; }
.btn--sm { min-height: 36px; padding: var(--space-1) var(--space-3); font-size: var(--fs-caption); }
.btn__icon { width: 14px; height: 14px; }
.btn__icon-lead { width: 18px; height: 18px; flex: 0 0 auto; }

/* =========================================================================
   3.2 CRS badge
   ========================================================================= */
.crs {
  display: inline-flex; align-items: center; white-space: nowrap;
  padding: 2px var(--space-2); border: 2px solid; border-radius: var(--radius-pill);
  font-size: var(--fs-caption); font-weight: 700; line-height: 1.3; text-transform: uppercase;
}
.crs--step_free { background: var(--color-green-fill); border-color: var(--color-green-border); color: var(--color-green-text); }
.crs--part      { background: var(--color-amber-fill); border-color: var(--color-amber-border); color: var(--color-amber-text); }
.crs--none      { background: var(--color-red-fill);   border-color: var(--color-red-border);   color: var(--color-red-text); }
.crs--default   { background: var(--color-surface);    border-color: var(--color-ink);          color: var(--color-ink); }
/* Text variant (§3.2): same pill, same colour semantics, but a sentence-case
   label ('Step free' / 'Part step free' / 'No step free') instead of the code. */
.crs--text      { text-transform: none; }

/* Section heading with an inline status pill (Accessibility section, §4.1) */
.section-headrow {
  display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  margin-bottom: var(--space-3);
}
.section-headrow .section-heading { margin-bottom: 0; }

/* =========================================================================
   3.6 Symbol & aspect chips (shared primitive)
   ========================================================================= */
.chip {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 28px; height: 28px; padding: 0 6px;
  background: var(--color-chip-bg); color: var(--color-chip-text);
  border-radius: var(--radius-chip); font-weight: 800; font-size: var(--fs-caption); line-height: 1;
  vertical-align: middle;
}
.chip svg { width: 16px; height: 16px; fill: currentColor; stroke: none; }
.chip--aspect { letter-spacing: 0.02em; padding: 0 8px; }
/* BR (banner repeater): a solid white disc (circle) cut by a diagonal band
   drawn IN THE CHIP'S BACKGROUND COLOUR (not a stroke outline) so it reads as
   a black slash through a white disc, not a thin ring. */
.chip--BR svg { width: 18px; height: 18px; }
.chip--BR svg circle { fill: currentColor; stroke: none; }
.chip--BR svg line   { fill: none; stroke: var(--color-chip-bg); stroke-width: 6; stroke-linecap: butt; }

/* =========================================================================
   3.3 Signal indicator (overview card top-right)
   ========================================================================= */
.signal { width: 64px; flex: 0 0 auto; display: flex; flex-direction: column; }
.signal__body {
  background: var(--color-chip-bg);
  border-radius: var(--radius-signal) var(--radius-signal) 0 0;
  padding: var(--space-3) 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-2);
  flex: 1 1 auto; /* stretches to fill the aside row height (§3.1); the tab keeps its size */
}
.signal__dot { width: 20px; height: 20px; border-radius: 50%; }
.signal__dot--green { background: var(--color-green-solid); }
.signal__dot--red   { background: var(--color-signal-red); }
.signal__tab {
  background: var(--color-surface);
  border: 2px solid var(--color-ink); border-top: none;
  border-radius: 0 0 var(--radius-signal) var(--radius-signal);
  min-height: 26px; padding: var(--space-1) 2px;
  display: flex; align-items: center; justify-content: center; text-align: center;
  font-size: var(--fs-caption); font-weight: 600; color: var(--color-ink); line-height: 1.15;
}
/* TRTS tab (§3.3, feedback D): a black tab below the white label tab. The white tab
   loses its bottom radius so the black tab docks flush and carries the rounded
   bottom corners matching the indicator's silhouette. */
.signal--trts .signal__tab { border-radius: 0; }
.signal__trts {
  background: var(--color-chip-bg);
  border: 2px solid var(--color-ink); border-top: none;
  border-radius: 0 0 var(--radius-signal) var(--radius-signal);
  min-height: 22px; padding: 2px;
  display: flex; align-items: center; justify-content: center; text-align: center;
  font-size: var(--fs-caption); font-weight: 700; color: var(--color-chip-text); line-height: 1;
}

/* Signal glyph — bodyless variant used as a pole anchor (no tab) */
.signal-glyph {
  width: 40px;
  background: var(--color-chip-bg); border-radius: var(--radius-signal);
  padding: var(--space-2) 0;
  display: flex; flex-direction: column; align-items: center; gap: var(--space-2);
}
.signal-glyph .signal__dot { width: 18px; height: 18px; }

/* TRTS glyph — pole anchor (§3.7, feedback D): a 40px black rounded chip with
   'TRTS' text over a small white square outline, matching the signal-glyph size. */
.trts-glyph {
  width: 40px; height: 40px;
  background: var(--color-chip-bg); border-radius: var(--radius-signal);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
}
.trts-glyph__label {
  color: var(--color-chip-text); font-size: 11px; font-weight: 800; line-height: 1; letter-spacing: 0.02em;
}
.trts-glyph__box {
  width: 9px; height: 9px; border: 1.5px solid var(--color-chip-text); background: transparent;
}

/* =========================================================================
   3.4 UDS/ASDO pill
   ========================================================================= */
.udspill {
  width: 52px; flex: 0 0 auto; overflow: hidden;
  background: var(--color-red-fill); border: 2px solid var(--color-red-border);
  border-radius: var(--radius-pill); color: var(--color-red-text);
  display: flex; flex-direction: column;
}
.udspill__seg {
  padding: var(--space-2) 2px; text-align: center;
  font-size: var(--fs-caption); font-weight: 700; line-height: 1.3;
  /* Segments share the pill's full height equally (§3.4 full-height rule) —
     a single-value pill fills the whole aside row on its own. */
  flex: 1 1 0; display: flex; flex-direction: column; justify-content: center;
}
.udspill__seg + .udspill__seg { border-top: 2px solid var(--color-red-border); }
/* Length-only stations flag the amber platform-length table rather than an SDO
   requirement, so the pill takes that table's colour (§3.4). */
.udspill--amber {
  background: var(--color-amber-fill); border-color: var(--color-amber-border);
  color: var(--color-amber-text);
}
.udspill--amber .udspill__seg + .udspill__seg { border-top-color: var(--color-amber-border); }
.udspill__seg b { display: block; font-weight: 700; }
.udspill__seg span { display: block; }

/* =========================================================================
   3.1 Station overview card
   ========================================================================= */
.overview { display: flex; gap: var(--space-3); align-items: flex-start; }
.overview__main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: var(--space-2); }
/* The aside stretches to the full height of its row so the pill and the signal
   end level with the main column — bottom whitespace equals top whitespace (§3.1). */
.overview__aside { flex: 0 0 auto; align-self: stretch; display: flex; gap: var(--space-2); align-items: stretch; }
.overview__namerow { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.overview__name {
  font-size: 1.375rem; font-weight: 800; line-height: 1.1;
  /* Long names: single line, clipped; app.js adds .is-wrapped when the text
     overflows, splitting it over 2 lines taking the same vertical space (§3.1). */
  flex: 0 1 auto; min-width: 0; max-width: 100%;
  overflow: hidden; white-space: nowrap;
}
.overview__name-inner { display: inline-block; }
.overview__name.is-wrapped {
  white-space: normal;
}
.overview__name.is-wrapped .overview__name-inner {
  display: block;
}
.overview__meta { display: flex; flex-direction: column; gap: 2px; font-size: var(--fs-body); }
.overview__meta-line { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.overview__actions { display: flex; gap: var(--space-2); flex-wrap: wrap; margin-top: var(--space-1); }

/* Route-flow compact variant (§3.11) */
.overview--route { width: 100%; text-align: left; }
.route-card { padding: 0; overflow: hidden; }
.route-card__head { padding: var(--space-4); }
@media (min-width: 600px) { .route-card__head { padding: var(--space-5); } }
.route-card__expand {
  display: flex; align-items: center; justify-content: center; gap: var(--space-2);
  width: 100%; min-height: var(--tap-min);
  border-top: var(--border-inner); color: var(--color-ink-soft); font-weight: 700;
}
.route-card__chev { width: 22px; height: 22px; transition: transform 0.15s ease; }
.route-card__expand[aria-expanded="true"] .route-card__chev { transform: rotate(180deg); }
.route-card__detail { padding: 0 var(--space-4) var(--space-4); display: flex; flex-direction: column; gap: var(--space-4); }
.route-card__detail[hidden] { display: none; }

/* =========================================================================
   3.5 Warning collapsibles (amber + red)
   ========================================================================= */
.warn__header {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  width: 100%; min-height: var(--tap-min);
  padding: var(--space-3) var(--space-4);
  /* Constant --radius-card corners: the top radius is identical collapsed and
     expanded, matching the docked table's bottom corners (§3.5). */
  border: 2px solid; border-radius: var(--radius-card);
  font-size: var(--fs-heading); font-weight: 800; text-align: left;
  transition: border-radius 0.15s ease;
}
.warn--amber .warn__header { background: var(--color-amber-fill); border-color: var(--color-amber-border); color: var(--color-amber-text); }
.warn--red   .warn__header { background: var(--color-red-fill);   border-color: var(--color-red-border);   color: var(--color-red-text); }
.warn__title { display: flex; align-items: center; gap: var(--space-2); }
.warn__warn-icon { width: 18px; height: 18px; flex: 0 0 auto; }
.warn__chev { width: 24px; height: 24px; flex: 0 0 auto; transition: transform 0.15s ease; }
.warn__header[aria-expanded="true"] { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
.warn__header[aria-expanded="true"] .warn__chev { transform: rotate(180deg); }
.warn__panel {
  border: 2px solid; border-top: none;
  border-radius: 0 0 var(--radius-card) var(--radius-card);
  overflow: hidden;
}
.warn__panel[hidden] { display: none; }
.warn--amber .warn__panel { border-color: var(--color-amber-border); }
.warn--red   .warn__panel { border-color: var(--color-red-border); }
.warn__table { width: 100%; }
.warn__table th, .warn__table td {
  padding: var(--space-3) var(--space-2); text-align: center; border: 1px solid var(--color-hairline);
}
.warn__table thead th { font-size: var(--fs-caption); font-weight: 600; color: var(--color-ink-mute); }
.warn__table tbody td { font-size: var(--fs-body); background: var(--color-surface); }
/* "Not authorised" length cell (§3.5a, feedback A): a restriction, so red text
   (weight 700) — distinct from the plain "N Coaches" values. */
.warn__na { color: var(--color-red-text); font-weight: 700; }

/* =========================================================================
   3.7 Route Indicators & Start Signals — the "pole" card
   ========================================================================= */
/* Pole geometry (§3.7): the anchors sit INSIDE the card, --space-2 (8px) right of
   the border. Anchor column is 40px wide; content clears it with 64px left padding
   (8px gap + 40px anchor + 16px gap to text). The 6px bar is centred under the
   anchors (8 + 20 - 3 = 25px from the inner border edge) and runs from the first
   anchor's middle to the card's inner bottom edge; anchors paint on top. */
.pole { position: relative; padding-left: 64px; }
.pole__bar {
  position: absolute; left: 25px; width: 6px;
  top: calc(var(--space-4) + 20px); bottom: 0;
  background: var(--color-ink); z-index: 1; border-radius: 0px;
}
@media (min-width: 600px) { .pole__bar { top: calc(var(--space-5) + 20px); } }
.pole__section { position: relative; }
.pole__section + .pole__section { margin-top: var(--space-5); }
.pole__anchor {
  position: absolute; left: -56px; top: 0; width: 40px; z-index: 2;
  display: flex; justify-content: center;
}
/* Anchor symbol chip: same width as the signal glyph and square (40 x 40). */
.pole__anchor .chip {
  width: 40px; height: 40px; min-width: 40px; flex: 0 0 auto;
  font-size: var(--fs-heading);
}
.pole__anchor .chip svg { width: 20px; height: 20px; }
.pole__body { min-height: 40px; }
.ri__scope { font-size: var(--fs-body); margin: var(--space-1) 0; }
.ri__grid + .ri__scope { margin-top: var(--space-4); }
.ri__grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--space-2) var(--space-4);
}
.ri__entry { display: flex; align-items: center; gap: var(--space-2); font-size: var(--fs-body); }
.ss__line {
  font-size: var(--fs-body); line-height: 2; margin-top: var(--space-1);
}
.ss__line .chip { margin: 0 2px; }

/* =========================================================================
   3.8 Modal
   ========================================================================= */
.modal-overlay {
  position: fixed; inset: 0; z-index: 100;
  background: var(--backdrop);
  display: flex; align-items: center; justify-content: center; padding: var(--space-4);
}
.modal-overlay[hidden] { display: none; }
.modal {
  position: relative; width: 100%; max-width: 400px; max-height: 90vh; overflow-y: auto;
  background: var(--color-surface); border: var(--border-card);
  border-radius: var(--radius-modal); box-shadow: var(--shadow-modal);
  padding: var(--space-6);
}
.modal__close {
  position: absolute; top: var(--space-2); right: var(--space-2);
  width: var(--tap-min); height: var(--tap-min);
  display: flex; align-items: center; justify-content: center;
  color: var(--color-ink-mute);
}
.modal__close .icon { width: 20px; height: 20px; }
.modal__title { text-align: center; font-size: var(--fs-modal-title); font-weight: 800; line-height: 1.15; margin-bottom: var(--space-3); }
.modal__explainer { text-align: center; color: var(--color-ink-soft); margin-bottom: var(--space-4); }
/* Connections modal freshness stamp — empty until the first fetch resolves. Its
   own line below the explainer sentence, normal (not monospace) type. */
.modal__updated { display: block; margin-top: var(--space-1); font-size: var(--fs-caption); font-variant-numeric: tabular-nums; color: var(--color-ink-mute); }
.modal__list { display: flex; flex-direction: column; gap: var(--space-2); }
.modal__coderow {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  width: 100%; min-height: var(--tap-min); padding: var(--space-3) var(--space-4);
  background: var(--color-surface-sunk); border: var(--border-card); border-radius: var(--radius-cell);
}
.modal__code-label { font-size: var(--fs-body); color: var(--color-ink); }
.modal__code { font-family: var(--font-mono); font-size: var(--fs-mono-code); font-weight: 700; color: var(--color-red-text); }
.modal__msg { text-align: center; color: var(--color-red-text); font-size: var(--fs-small); padding: var(--space-2); }
/* The loading state is not an error — same slot, neutral colour. */
.modal__msg--loading { color: var(--color-ink-mute); }

/* Connections row (§3.8) — purely structural: two columns, the entry's operator
   on the left over its terse "destination · platform" meta line, the booked time
   on the right over the estimate when one differs. Every row lives inside a
   .modal__congroup card (below), which owns the visible framing — a row never
   carries its own border/fill, so a same-entry imminent pair (two stacked rows)
   and a multi-TOC destination's divided entries all read as one shared card. */
.modal__connrow { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); }
.modal__conn-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.modal__conn-name { font-size: var(--fs-body); font-weight: 700; color: var(--color-ink); }
.modal__conn-meta { font-size: var(--fs-caption); color: var(--color-ink-mute); }
.modal__conn-times {
  display: flex; flex-direction: column; align-items: flex-end; gap: 2px;
  flex: 0 0 auto; font-family: var(--font-mono); font-variant-numeric: tabular-nums;
}
.modal__conn-std { font-size: var(--fs-body); font-weight: 700; color: var(--color-ink); }
/* Only rendered when the estimate has actually moved off the booked time. */
.modal__conn-etd { font-size: var(--fs-caption); font-weight: 700; color: var(--color-amber-text); }
.modal__conn-state { font-size: var(--fs-caption); font-family: var(--font-sans); color: var(--color-ink-mute); }
.modal__connrow--empty { opacity: 0.72; }
.modal__connrow--empty .modal__conn-name { font-weight: 600; color: var(--color-ink-soft); }

/* Connections group card (§3.8) — every destination gets one, whatever its
   TOC count: same outer treatment .modal__connrow used to carry alone (sunk
   fill, card border/radius, ≥44px row height via --tap-min below), now owned
   here so it wraps the header too. A hairline divider sits under the header and
   between entries; the (at most two) stacked service rows within one entry get
   no divider between them, since they read as a single connection's pair of
   times, not two connections. */
.modal__congroup {
  display: flex; flex-direction: column; gap: var(--space-2);
  width: 100%; padding: var(--space-3) var(--space-4);
  background: var(--color-surface-sunk); border: var(--border-card); border-radius: var(--radius-cell);
}
.modal__congroup-head { font-size: var(--fs-body); font-weight: 700; color: var(--color-ink); }
.modal__congroup-div { height: 1px; flex: 0 0 auto; background: var(--color-hairline); }
.modal__congroup .modal__connrow { min-height: var(--tap-min); }

.modal__foot { display: flex; justify-content: center; margin-top: var(--space-4); }

.spinner {
  display: inline-block; width: 16px; height: 16px;
  border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%;
  animation: rn-spin 0.7s linear infinite;
}
@keyframes rn-spin { to { transform: rotate(360deg); } }

/* =========================================================================
   3.9 Dispatch guidance diagram (§3.9 — switcher, unit gaps, carriages underbar)
   ========================================================================= */
.dispatch__linebody { margin-top: var(--space-3); }
.dispatch__curved-note {
  margin-top: 0; margin-bottom: var(--space-4);
  display: flex; align-items: center; gap: var(--space-3);
  background: var(--color-surface-sunk);
  border: 2px solid var(--color-ink);
  border-radius: var(--radius-card);
  padding: var(--space-3) var(--space-4);
}
.dispatch__curve-icon { width: 32px; height: 32px; flex: 0 0 auto; transform: rotate(45deg); color: var(--color-ink); }
.dispatch__curved-lines { display: flex; flex-direction: column; gap: var(--space-1); }

/* Switcher: line/direction segmented control + formation pill tablist. Both need
   JS; with JS off, every line + formation renders stacked (graceful, offline). */
.dispatch__lines { margin-bottom: var(--space-4); max-width: 100%; flex-wrap: wrap; gap: var(--space-2); }
/* .dispatch__lines.segmented out-specifies the later `.segmented{display:inline-flex}`
   so the control is truly hidden without JS (graceful stacked fallback). Unlike the
   generic 2-option .segmented (route direction toggle, always fits one row), a
   station can have 3+ lines with long labels ("P5, 6 (DS/US)") that wrap on narrow
   screens — a single continuous pill clipped with overflow:hidden looks broken once
   wrapped, so each option gets its own pill instead of sharing one outer border. */
.dispatch__lines.segmented, .dispatch__forms { display: none; }
.has-js .dispatch__lines.segmented { display: inline-flex; border: none; border-radius: 0; overflow: visible; }
.has-js .dispatch__forms { display: flex; }
.dispatch__lines.segmented .dispatch__linetab {
  border: 2px solid var(--color-ink); border-radius: var(--radius-pill);
}
.dispatch__lines.segmented .dispatch__linetab + .dispatch__linetab { border-left: 2px solid var(--color-ink); }

.dispatch__forms {
  flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-4);
}
.dispatch__formtab {
  min-height: var(--tap-min); padding: var(--space-1) var(--space-3);
  display: inline-flex; align-items: center;
  border: 2px solid var(--color-ink); border-radius: var(--radius-pill);
  background: var(--color-surface-sunk); color: var(--color-ink);
  font-family: var(--font-mono); font-size: var(--fs-mono-label);
  text-transform: uppercase; letter-spacing: 0.08em;
  transition: background 0.1s ease, color 0.1s ease;
}
.dispatch__formtab[aria-selected="true"] { background: var(--color-ink); color: #fff; }
.dispatch__formtab:focus-visible { outline: 3px solid var(--color-focus-ring); outline-offset: 2px; }

/* Line heading: the no-JS label for each line. In JS + multi-line the segmented
   control carries it, so it is hidden. Single-line keeps its heading. */
.dispatch__lineheading {
  font-size: var(--fs-body-strong); font-weight: 700; color: var(--color-ink-soft);
  margin-bottom: var(--space-3);
}
.has-js .dispatch--multiline .dispatch__lineheading { display: none; }
.has-js .dispatch__line:not(.is-active) { display: none; }
.has-js .dispatch__panel:not(.is-active) { display: none; }
.dispatch__line + .dispatch__line { margin-top: var(--space-5); }
.dispatch__panel + .dispatch__panel { margin-top: var(--space-4); }

.dispatch__label {
  font-family: var(--font-mono); font-size: var(--fs-mono-label);
  text-transform: uppercase; letter-spacing: 0.12em; color: var(--color-ink);
  margin-bottom: var(--space-2);
}

/* Train: full-width, cars shrink to fit — NEVER horizontally scrolls (§3.9). Units
   share width in proportion to their car count; a fixed gap sits between units. */
.dispatch__train { width: 100%; }
.dispatch__cars, .dispatch__bar { display: flex; width: 100%; align-items: stretch; }
.dispatch__unit, .dispatch__barunit { display: flex; flex: 1 1 0; min-width: 0; }
.dispatch__gap { flex: 0 0 8px; }

.dispatch__car {
  flex: 1 1 0; min-width: 0; height: 28px;
  border: 2px solid var(--color-car-border);
  display: flex; align-items: center; justify-content: center;
}
.dispatch__car + .dispatch__car { border-left: none; }
.dispatch__car--g { background: var(--color-green-solid); }
.dispatch__car--a { background: var(--color-amber-solid); }
.dispatch__car--r { background: var(--color-red-solid); }
.dispatch__arrow { width: 0; height: 0; }
.dispatch__arrow--left  { border-right: 9px solid rgba(0,0,0,0.55); border-top: 6px solid transparent; border-bottom: 6px solid transparent; }
.dispatch__arrow--right { border-left: 9px solid rgba(0,0,0,0.55);  border-top: 6px solid transparent; border-bottom: 6px solid transparent; }

/* Underbar: mirrors the car geometry so it ends exactly after the accommodated
   carriage (§3.9). Filled cells + bridged unit-gaps carry the grey bar + gold edge. */
.dispatch__bar { margin-top: var(--space-1); }
.dispatch__barcell { flex: 1 1 0; min-width: 0; height: 6px; }
.dispatch__barcell.is-on,
.dispatch__gap--bar.is-on {
  background: var(--color-platform-bar); border-top: 2px solid var(--color-platform-edge);
}

/* =========================================================================
   3.10 POI trail — native flat/bordered redesign. One continuous 2px --color-ink
   trunk that connects to the station cards above/below (see .route-seq__trail);
   compact POI rows hang off it. junction_link renders as a branch spur, no circle.
   ========================================================================= */
.poi { position: relative; padding: var(--space-3) 0; }
.poi__trunk { position: absolute; left: 19px; top: 0; bottom: 0; width: 2px; background: var(--color-ink); z-index: 0; }
.poi__row { position: relative; z-index: 1; display: grid; grid-template-columns: 40px 1fr; column-gap: var(--space-3); align-items: center; padding: var(--space-1) 0; }
.poi__node {
  width: 40px; height: 40px; border-radius: var(--radius-node);
  border: var(--border-card); background: var(--color-surface); color: var(--color-ink);
  display: flex; align-items: center; justify-content: center;
}
.poi__node .icon { width: 18px; height: 18px; }
.poi__name { font-size: var(--fs-small); font-weight: 600; color: var(--color-ink-soft); }
/* junction_link — a horizontal spur off the trunk (x=20px) to a bordered label box. */
.poi__spur { grid-column: 1; align-self: center; margin-left: 20px; width: calc(20px + var(--space-3)); height: 2px; background: var(--color-ink); }
.poi__branch-box {
  grid-column: 2; justify-self: start; align-self: center;
  display: inline-block; padding: var(--space-1) var(--space-3);
  border: var(--border-card); border-radius: var(--radius-cell); background: var(--color-surface);
  font-size: var(--fs-small); font-weight: 600; color: var(--color-ink-soft);
}
.poi__branch-box--link { color: var(--color-blue-text); text-decoration: underline; }
.poi__branch-box--link:active { filter: brightness(0.96); }

/* =========================================================================
   3.12a Announcement tags (tag chip — §006 rework)
   ========================================================================= */
.taglist {
  list-style: none; display: flex; flex-wrap: wrap; gap: var(--space-2);
  margin: 0; padding: 0;
}
.tag {
  display: inline-flex; align-items: center; min-height: 32px;
  padding: var(--space-1) var(--space-3);
  background: var(--color-surface-sunk); border: 2px solid var(--color-ink);
  border-radius: var(--radius-pill);
  font-size: var(--fs-caption); font-weight: 700; color: var(--color-ink);
}

/* =========================================================================
   3.12b Approach landmarks
   ========================================================================= */
.approach__dir + .approach__dir { margin-top: var(--space-3); }
.approach__label { font-size: var(--fs-body-strong); font-weight: 700; }
.approach__text { font-size: var(--fs-body); }
.approach__dir--dim, .approach__dir--dim .approach__label { color: var(--color-ink-mute); }

/* =========================================================================
   Form fields (§3.13)
   ========================================================================= */
.field { display: flex; flex-direction: column; gap: var(--space-1); }
.field__label { font-size: var(--fs-caption); font-weight: 700; color: var(--color-ink-soft); }
.field__wrap { position: relative; }
.field__control {
  width: 100%; min-height: var(--tap-min); padding: var(--space-3);
  background: var(--color-surface); border: var(--border-card); border-radius: var(--radius-cell);
  font-size: var(--fs-body);
}
.field__icon {
  position: absolute; left: var(--space-3); top: 50%; transform: translateY(-50%);
  width: 18px; height: 18px; color: var(--color-ink-mute); pointer-events: none;
}
.field__control--icon { padding-left: calc(var(--space-3) * 2 + 18px); }
.field--error .field__control { border-color: var(--color-red-border); }
.field__error { font-size: var(--fs-small); color: var(--color-red-text); }
textarea.field__control { min-height: 96px; resize: vertical; }

/* =========================================================================
   Flash messages (§3.13)
   ========================================================================= */
.flash-stack { display: flex; flex-direction: column; gap: var(--space-2); }
.flash {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  padding: var(--space-3) var(--space-4); border: 2px solid; border-radius: var(--radius-cell);
  font-weight: 600;
}
.flash__dismiss { flex: 0 0 auto; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; color: inherit; }
.flash__dismiss .icon { width: 18px; height: 18px; }
.flash--info,    .flash--notice  { background: var(--color-flash-info-fill);  border-color: var(--color-blue-border);  color: var(--color-flash-info-text); }
.flash--ok,      .flash--success { background: var(--color-flash-ok-fill);    border-color: var(--color-green-border); color: var(--color-flash-ok-text); }
.flash--warn,    .flash--warning { background: var(--color-flash-warn-fill);  border-color: var(--color-amber-border); color: var(--color-flash-warn-text); }
.flash--error                    { background: var(--color-flash-error-fill); border-color: var(--color-red-border);   color: var(--color-flash-error-text); }

/* =========================================================================
   Feedback widget (§3.13). The action row also hosts the page's single Edit
   button when the viewer can edit (§4.5 public-page rule) — side by side with
   "Report an issue", same secondary style.
   ========================================================================= */
.feedback__actions { display: flex; gap: var(--space-3); flex-wrap: wrap; }
.feedback__form { margin-top: var(--space-3); display: flex; flex-direction: column; gap: var(--space-3); }
.feedback__form[hidden] { display: none; }

/* Internal admin-only notes on a feedback report (admin queue). Nested inside the
   already-sunk .editrow, so it uses the lighter surface + a lock label to read as a
   distinct, "not public" panel. */
.fbnotes {
  margin-top: var(--space-3);
  padding: var(--space-3);
  background: var(--color-surface);
  border: var(--border-inner); border-radius: var(--radius-cell);
  display: flex; flex-direction: column; gap: var(--space-2);
}
.fbnotes__label { display: inline-flex; align-items: center; gap: var(--space-1); font-weight: 600; }
.fbnotes__icon { width: .85em; height: .85em; }
.fbnotes__empty { margin: 0; }
.fbnotes__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.fbnote { padding: var(--space-2); background: var(--color-surface-sunk); border-radius: var(--radius-cell); }
.fbnote__meta { margin-bottom: 2px; }
.fbnote__body { overflow-wrap: anywhere; }
.fbnotes__form { display: flex; flex-direction: column; gap: var(--space-2); align-items: flex-start; }
.fbnotes__form textarea { width: 100%; }

/* =========================================================================
   Offline / sync status chip (§3.13)
   ========================================================================= */
.sync-chip {
  display: inline-flex; align-items: center; gap: var(--space-2);
  padding: var(--space-1) var(--space-3); border: 2px solid; border-radius: var(--radius-pill);
  font-size: var(--fs-caption); font-weight: 600;
}
.sync-chip__dot { width: 8px; height: 8px; border-radius: 50%; background: currentColor; flex: 0 0 auto; }
.sync-chip--synced  { background: var(--color-green-fill); border-color: var(--color-green-border); color: var(--color-green-text); }
.sync-chip--offline { background: var(--color-amber-fill); border-color: var(--color-amber-border); color: var(--color-amber-text); }
/* Transient state while a sync is in flight — neutral ink, not a status colour. */
.sync-chip--syncing { background: var(--color-surface-sunk); border-color: var(--color-ink); color: var(--color-ink-soft); }

/* =========================================================================
   Segmented control (route direction toggle §4.2)
   ========================================================================= */
.segmented { display: inline-flex; border: 2px solid var(--color-ink); border-radius: var(--radius-pill); overflow: hidden; }
.segmented__opt { min-height: var(--tap-min); padding: var(--space-2) var(--space-5); font-weight: 700; color: var(--color-ink); }
.segmented__opt[aria-pressed="true"] { background: var(--color-ink); color: #fff; }
.segmented__opt + .segmented__opt { border-left: 2px solid var(--color-ink); }

/* =========================================================================
   Auth card (§4.4)
   ========================================================================= */
.auth-banner {
  max-width: 420px;
  width: 100%;
  margin: 0 auto;
  padding: var(--space-4);
  background: var(--color-amber-fill);
  border: 2px solid var(--color-amber-border);
  border-radius: var(--radius-card);
  color: var(--color-amber-text);
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  box-sizing: border-box;
}
@media (min-width: 600px) {
  .auth-banner {
    padding: var(--space-5);
  }
}
.auth-banner__title {
  font-size: var(--fs-heading);
  font-weight: 800;
  margin: 0;
}
.auth-banner__text {
  font-size: var(--fs-body);
  line-height: 1.45;
  margin: 0;
}

.auth-card { max-width: 420px; margin: 0 auto; display: flex; flex-direction: column; gap: var(--space-4); }
.auth-card form { display: flex; flex-direction: column; gap: var(--space-3); }
.auth-card p { color: var(--color-ink-soft); }

.help-faq { display: flex; flex-direction: column; gap: var(--space-4); }
.help-item { }
.help-question { font-size: var(--fs-heading); font-weight: 800; color: var(--color-ink); margin-bottom: var(--space-2); }
.help-answer { font-size: var(--fs-body); color: var(--color-ink-soft); margin: 0; }

/* =========================================================================
   Styleguide scaffolding
   ========================================================================= */
.sg-nav {
  position: sticky; top: var(--header-h); z-index: 10;
  background: var(--color-surface); border: var(--border-card); border-radius: var(--radius-card);
  padding: var(--space-3) var(--space-4);
  display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-3);
}
.sg-nav a { font-size: var(--fs-caption); font-weight: 700; }
.sg-section { display: flex; flex-direction: column; gap: var(--space-4); scroll-margin-top: calc(var(--header-h) + 60px); }
.sg-section > h2 {
  font-size: var(--fs-display); font-weight: 900; letter-spacing: 0.005em;
  padding-bottom: var(--space-2); border-bottom: 2px solid var(--color-ink);
}
.sg-label { font-size: var(--fs-caption); font-weight: 700; color: var(--color-ink-mute); text-transform: uppercase; letter-spacing: 0.04em; }
.sg-row { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: flex-start; }
.sg-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--space-3); }
.sg-block { display: flex; flex-direction: column; gap: var(--space-2); }
.sg-note { font-size: var(--fs-small); color: var(--color-ink-mute); }

/* =========================================================================
   Stations index — simple browse list (Phase 4). Search arrives in Phase 5.
   ========================================================================= */
.station-list { list-style: none; display: flex; flex-direction: column; gap: var(--space-2); }
/* Gap between a station-search field and its results (dashboard, §4.3). The
   drawer's equivalent gap comes from .drawer__search's flex `gap` instead, so
   this is scoped to the dashboard's plain-card layout to avoid stacking both —
   same --space-2 token in each case keeps the two visually consistent. */
.card > [data-station-search] + .station-list,
.card > [data-station-search] + .station-list + .t-small { margin-top: var(--space-2); }
.station-list__item {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  min-height: var(--tap-min); padding: var(--space-3) var(--space-4);
  background: var(--color-surface-sunk); border: var(--border-card); border-radius: var(--radius-cell);
  color: var(--color-ink); text-decoration: none;
  transition: filter 100ms ease;
}
.station-list__item:active { filter: brightness(0.96); }
.station-list__name { font-size: var(--fs-body-strong); font-weight: 700; }
.station-list__type {
  font-size: var(--fs-caption); font-weight: 600; color: var(--color-ink-mute); text-transform: uppercase;
}

/* =========================================================================
   Dashboard route index (§4.3) + route page shell (§4.2)
   ========================================================================= */
.route-index { list-style: none; display: flex; flex-direction: column; gap: var(--space-2); }
.route-index__item {
  display: flex; flex-direction: column; gap: 2px;
  min-height: var(--tap-min); padding: var(--space-3) var(--space-4);
  background: var(--color-surface-sunk); border: var(--border-card); border-radius: var(--radius-cell);
  color: var(--color-ink); text-decoration: none;
  transition: filter 100ms ease;
}
.route-index__item:active { filter: brightness(0.96); }
.route-index__name { font-size: var(--fs-body-strong); font-weight: 700; }
.route-index__desc { font-size: var(--fs-small); color: var(--color-ink-soft); }
.route-index__meta { font-size: var(--fs-caption); font-weight: 600; color: var(--color-ink-mute); }

/* Dashboard route "folders" (category tree, §4.3). Collapsible headers wrapping
   nested folders + the route-index cards; uncategorised routes render as a bare
   .route-index at the top level. */
.route-folders { display: flex; flex-direction: column; gap: var(--space-2); }
.route-folder { display: flex; flex-direction: column; }
.route-folder__toggle {
  display: flex; align-items: center; gap: var(--space-2);
  width: 100%; min-height: var(--tap-min); padding: var(--space-3) var(--space-4);
  border: var(--border-card); border-radius: var(--radius-cell);
  background: var(--color-surface-sunk); color: var(--color-ink);
  cursor: pointer; text-align: left; font: inherit;
}
.route-folder__toggle:hover { filter: brightness(0.98); }
.route-folder__icon { width: 18px; height: 18px; flex: 0 0 auto; color: var(--color-ink-mute); }
.route-folder__name { flex: 1 1 auto; font-size: var(--fs-body-strong); font-weight: 700; }
.route-folder__count {
  flex: 0 0 auto; min-width: 22px; padding: 0 6px; text-align: center;
  font-size: var(--fs-caption); font-weight: 700; color: var(--color-ink-mute);
  background: var(--color-surface); border: var(--border-card); border-radius: var(--radius-pill);
}
.route-folder__chev { width: 16px; height: 16px; flex: 0 0 auto; color: var(--color-ink-mute); transition: transform 0.15s ease; }
.route-folder__toggle[aria-expanded="true"] .route-folder__chev { transform: rotate(180deg); }
.route-folder__body {
  display: flex; flex-direction: column; gap: var(--space-2);
  padding: var(--space-2) 0 var(--space-1) var(--space-3);
  margin-left: var(--space-3); border-left: 2px solid var(--color-surface-sunk);
}
.route-folder__body[hidden] { display: none; }
.route-folder__empty { margin: 0; padding: var(--space-1) 0; }

/* =========================================================================
   Nearest-station locator (dashboard §4.3). Two equal-width, side-by-side
   buttons (the .btn min-height keeps the 44px tap floor) + an inline status
   line for the error / no-station / locating states.
   ========================================================================= */
.locator__heading {
  font-size: var(--fs-body-strong); font-weight: 800; color: var(--color-ink);
  margin-bottom: var(--space-3);
}
.locator__actions { display: flex; gap: var(--space-2); }
/* Both buttons share the tightened contact/codes sizing so the pair reads as one
   control (the .btn min-height keeps the 44px tap floor). */
.locator__btn {
  flex: 1 1 0; min-width: 0;
  padding: var(--space-2) var(--space-2); font-size: var(--fs-small);
}
.locator__msg {
  margin-top: var(--space-3); font-size: var(--fs-small); color: var(--color-ink-soft);
  text-align: center;
}
.locator__msg--error { color: var(--color-red-text); }
/* Chooser rows carry a station name + CRS + rough distance. */
.locator-choice { justify-content: space-between; gap: var(--space-3); text-align: left; }
.locator-choice__name { font-weight: 700; }
.locator-choice__meta {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-size: var(--fs-caption); color: var(--color-ink-mute); white-space: nowrap;
}
/* Editor lat/lng pair (Overview): two small fields side by side. */
.coord-fields { display: flex; gap: var(--space-3); }
.coord-fields > .field { flex: 1 1 0; min-width: 0; }

/* Map picker (Overview coords) — JS-only; the fields above still work without it. */
.mappicker {
  display: none; height: 260px; margin-top: var(--space-2);
  border-radius: var(--radius-cell); overflow: hidden; border: var(--border-inner);
  position: relative; z-index: 1;
}
.has-js .mappicker { display: block; }

/* =========================================================================
   Editors (§4.5, Phase 6). Same design language, denser. Progressive
   enhancement: .js-only is hidden until app.js marks <body class="has-js">;
   .no-js-only is the fallback shown only without JS.
   ========================================================================= */
.js-only { display: none; }
.has-js .js-only { display: inline-flex; }
.has-js .no-js-only { display: none; }

.editor-head {
  display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap;
  margin-bottom: var(--space-4);
}
.editor-head__title { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; flex: 1 1 auto; }

.pub-badge {
  display: inline-flex; align-items: center; padding: 2px var(--space-2);
  border: 2px solid; border-radius: var(--radius-pill);
  font-size: var(--fs-caption); font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em;
}
.pub-badge--on  { background: var(--color-green-fill); border-color: var(--color-green-border); color: var(--color-green-text); }
.pub-badge--off { background: var(--color-surface-sunk); border-color: var(--color-ink-mute); color: var(--color-ink-mute); }

/* Custom note section (§4.1, feedback C): a plain white card. Line breaks in the
   content are preserved by nl2br on render; word-wrap keeps long lines in bound. */
.note__content { white-space: normal; overflow-wrap: anywhere; }

/* Single-page station editor (§4.5 / §006): anchor nav + stacked section cards.
   Feedback B: the whole page is ONE form with ONE sticky Save button — no
   per-section forms/saves. */
.editnav {
  display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-4);
}

/* Validation-error summary re-rendered above the form on a failed save (no partial
   write happened — the transaction never opened). Preserves submitted input. */
.editor-errors {
  border: 2px solid var(--color-red-border); background: var(--color-red-fill);
  color: var(--color-red-text); border-radius: var(--radius-cell);
  padding: var(--space-3) var(--space-4); margin-bottom: var(--space-4);
}
.editor-errors__title { font-weight: 800; margin-bottom: var(--space-1); }
.editor-errors ul { margin: 0; padding-left: var(--space-5); }
.editor-errors li { font-size: var(--fs-small); }

/* Sticky single Save bar (feedback B): fixed to the bottom of the viewport, above
   all content, with a clear ≥44px primary Save. Sits inside the centred column. */
.editor-savebar {
  position: sticky; bottom: 0; z-index: 20;
  margin: var(--space-6) calc(-1 * var(--page-pad-x)) 0;
  padding: var(--space-3) var(--page-pad-x);
  background: var(--color-surface);
  border-top: 2px solid var(--color-ink);
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
}
.editor-savebar__hint { font-size: var(--fs-small); color: var(--color-ink-mute); }
.editor-savebar .btn { min-height: var(--tap-min); padding: 0 var(--space-4); }
.editor-savebar .btn--primary { padding: 0 var(--space-6); }
@media (min-width: 600px) {
  .editor-savebar { margin-left: 0; margin-right: 0; border-radius: var(--radius-cell) var(--radius-cell) 0 0; }
}
/* The rowset container replaces the old per-section <form>; add-row + rows live
   inside the single page form now. */
.rowset { display: flex; flex-direction: column; gap: var(--space-3); }
.editnav__link {
  display: inline-flex; align-items: center; min-height: 36px;
  padding: var(--space-1) var(--space-3);
  background: var(--color-surface-sunk); border: 2px solid var(--color-ink);
  border-radius: var(--radius-pill);
  font-size: var(--fs-caption); font-weight: 700; color: var(--color-ink);
  text-decoration: none; transition: filter 100ms ease;
}
.editnav__link:hover { text-decoration: none; }
.editnav__link:active { filter: brightness(0.96); }

.ecard { scroll-margin-top: calc(var(--header-h) + var(--space-4)); }
.ecard + .ecard { margin-top: var(--space-4); }
.ecard__title {
  font-size: var(--fs-display); font-weight: 900; letter-spacing: 0.005em;
  margin-bottom: var(--space-3);
}
.ecard__hint { font-size: var(--fs-small); color: var(--color-ink-mute); margin-bottom: var(--space-3); }
.esub + .esub { margin-top: var(--space-6); border-top: var(--border-inner); padding-top: var(--space-4); }
.esub__title { font-size: var(--fs-heading); font-weight: 800; margin-bottom: var(--space-2); }

/* ── Platform manager (§022)
   Signals, route indicators, TRTS, curve and traction limits are all facts OF a
   platform, edited in that platform's tab.

   PROGRESSIVE ENHANCEMENT: the markup is a plain stacked block, so without JS every
   platform is visible and editable at once. app.js adds .pfmgr--js, which is what
   turns it into an overlay — so the no-JS editor can never be locked out of it. */
/* No `display` here: the launcher is .js-only, and setting display would override
   `.js-only { display: none }` (same specificity, declared earlier) and leak the
   button into the no-JS layout, where the panel is already inline. `.has-js` below
   turns it on instead. */
.pfmgr__launch {
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  width: 100%;
  padding: var(--space-5) var(--space-5);
  font-size: var(--fs-heading);
  font-weight: 800;
  text-align: left;
  color: var(--color-ink);
  background: var(--color-surface);
  border: var(--border-inner);
  border-radius: var(--radius-card);
  cursor: pointer;
}
.has-js .pfmgr__launch { display: flex; }
.pfmgr__launch:hover { background: var(--color-surface-sunk); }
.pfmgr__launch:focus-visible { outline: 3px solid var(--color-focus-ring); outline-offset: 2px; }
.pfmgr__launch-icon { flex: 0 0 auto; }

/* Without JS the panel is simply the next block on the page. */
.pfmgr { margin-top: var(--space-4); }

/* With JS it becomes a full-screen overlay, shown only while .pfmgr--open. */
.pfmgr--js {
  position: fixed;
  inset: 0;
  z-index: 60;
  margin: 0;
  padding: var(--space-4);
  display: none;
  background: rgba(0, 0, 0, 0.45);
}
.pfmgr--js.pfmgr--open { display: flex; }
body.pfmgr-locked { overflow: hidden; }

.pfmgr__dialog {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 1100px;
  max-height: 100%;
  margin: auto;
  background: var(--color-surface);
  border: var(--border-inner);
  border-radius: var(--radius-card);
  overflow: hidden;
}
/* Un-boxed in the no-JS layout — the surrounding card already provides the frame. */
.pfmgr:not(.pfmgr--js) .pfmgr__dialog { border: 0; border-radius: 0; background: none; }

/* ── Tab strip: Manage | P1 | P2 … | close */
.pfmgr__tabs {
  display: flex;
  align-items: stretch;
  flex-shrink: 0;
  overflow-x: auto;
  border-bottom: 1px solid var(--color-hairline);
  background: var(--color-surface-sunk);
}
.pfmgr:not(.pfmgr--js) .pfmgr__tabs { display: none; }
.pfmgr__tabspacer { flex: 1 1 auto; }
.pfmgr__tab {
  flex: 0 0 auto;
  padding: var(--space-3) var(--space-4);
  font-size: var(--fs-body);
  font-weight: 700;
  color: var(--color-ink-mute);
  background: none;
  border: 0;
  border-right: 1px solid var(--color-hairline);
  cursor: pointer;
  white-space: nowrap;
}
.pfmgr__tab--is-active {
  color: var(--color-ink);
  background: var(--color-surface);
  box-shadow: inset 0 -3px 0 var(--color-ink);
}
.pfmgr__tab:focus-visible { outline: 3px solid var(--color-focus-ring); outline-offset: -3px; }
.pfmgr__close {
  flex: 0 0 auto;
  padding: 0 var(--space-4);
  font-size: 24px;
  line-height: 1;
  color: var(--color-ink-mute);
  background: none;
  border: 0;
  border-left: 1px solid var(--color-hairline);
  cursor: pointer;
}
.pfmgr__close:hover { color: var(--color-ink); }

.pfmgr__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: var(--space-5);
}
.pfmgr__panel { display: flex; flex-direction: column; gap: var(--space-4); }
/* `display: flex` above is an author rule, so by cascade origin it beats the
   browser's own `[hidden] { display: none }` regardless of specificity — without
   this override, app.js setting .hidden on an inactive panel would do nothing and
   every panel would render at once. Same fix repeated below for .editrow. */
.pfmgr__panel[hidden] { display: none; }

/* ── One platform's panel. In the no-JS layout each is a card in a stack; with JS
   only the active tab's panel is shown (the rest carry [hidden]). */
.editrow:has(.pfpanel) { display: block; padding: 0; overflow: hidden; }
.editrow:has(.pfpanel)[hidden] { display: none; }
.pfmgr--js .editrow:has(.pfpanel) { background: none; border: 0; }
.pfpanel { display: flex; flex-direction: column; width: 100%; }

/* Mobile-first: number+delete share a row (.pfpanel__top, space-between), curve
   picker fills its own row below. At --bp-pfhead the three go back to one inline
   row — .pfpanel__top dissolves via display:contents so its children rejoin .head
   directly. Below that width the old single-row layout measured 426px against a
   390px viewport, pushing the delete button off the edge entirely (unreachable
   without horizontal scrolling), because .pfrow__curve-wrap's three nowrap segments
   can't shrink or wrap internally — flex-wrap only wraps whole items, and there was
   nowhere left for one to wrap TO once num+curve+del were all on the header's one row. */
.pfpanel__head {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-3);
  padding: var(--space-4);
  background: var(--color-surface-sunk);
  border-bottom: 1px solid var(--color-hairline);
}
.pfmgr--js .pfpanel__head { border-radius: var(--radius-card) var(--radius-card) 0 0; }
.pfpanel__top { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--space-3); }
.pfpanel__num { flex: 0 0 auto; width: 130px; }
.pfpanel__del { display: flex; align-items: center; }
@media (min-width: 600px) {
  .pfpanel__head { flex-direction: row; align-items: flex-end; flex-wrap: wrap; gap: var(--space-4); }
  .pfpanel__top { display: contents; }
  .pfpanel__del { margin-left: auto; }
}

.pfpanel__cols {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-5);
  padding: var(--space-4);
}
@media (min-width: 860px) {
  .pfpanel__cols { grid-template-columns: 1fr 1fr; }
}
.pfpanel__col { min-width: 0; display: flex; flex-direction: column; gap: var(--space-4); }
/* Sub-headings already separate the groups inside a column; drop the extra rule. */
.pfpanel__col .esub + .esub { margin-top: 0; border-top: 0; padding-top: 0; }
/* Cards FLOW (wrap) rather than stack full-width. .pfpanel__cols is one column
   until 860px (see above), so on a phone-to-tablet-width single column a stacked
   card had nothing to stretch for and just read as sparse — a short two-field card
   spread across the panel's entire width. flex-wrap lets several sit side by side
   wherever there's room, one-per-row on a narrow phone, with no extra breakpoint. */
.pftrac-list { display: flex; flex-wrap: wrap; gap: var(--space-3); }

/* ── Per-traction block (§015): one per class calling at the station. The class
   badge labels the block; its SDO dropdown and max-length dropdown sit side by side
   below. Both are always shown — the two facts are independent, so a class can carry
   a length cap and an SDO requirement at the same time. */
.pftrac {
  flex: 1 1 260px;
  border: 1px solid var(--color-hairline);
  border-radius: var(--radius-chip);
  padding: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.pftrac__head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.pftrac__class {
  font-family: var(--font-mono, monospace);
  font-weight: 700;
  font-size: var(--fs-caption);
  letter-spacing: 0.04em;
}
.pftrac__fields {
  display: flex;
  gap: var(--space-3);
}
.pftrac__fields > * {
  flex: 1 1 0;
  min-width: 0;
}

/* ── Action bar (footer strip) */
/* ── Curve picker wrapper. Stacked (label above pill) on mobile so the pill keeps
   its natural auto width — the pill's three options are white-space:nowrap inside
   an overflow:hidden track (see .segmented-radio below), so stretching it to fill
   a narrow container and dividing evenly by three was tried and rejected: "Negative"
   is long enough that a flex-shrunk third of the pill clips its own text. Auto width
   never has that problem, at any container width. */
.pfrow__curve-wrap {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
}
.pfrow__curve-label {
  font-size: var(--fs-caption);
  font-weight: 700;
  color: var(--color-ink-mute);
  white-space: nowrap;
}
@media (min-width: 600px) {
  .pfrow__curve-wrap { flex-direction: row; align-items: center; gap: var(--space-3); }
}

.custom-tag__label { width: 100%; }
@media (min-width: 560px) {
  .custom-tag__label { max-width: 400px; }
}

/* ── Segmented radio — inputs are sr-only, span is the visible pill */
.segmented-radio {
  display: inline-flex;
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  border: 1.5px solid var(--color-hairline);
  overflow: hidden;
}
.segmented-radio__opt {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 5px var(--space-3);
  font-size: var(--fs-caption);
  font-weight: 700;
  color: var(--color-ink-mute);
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
  transition: background 0.14s, color 0.14s;
}
.segmented-radio__opt + .segmented-radio__opt {
  border-left: 1.5px solid var(--color-hairline);
}
.segmented-radio__opt:has(input:checked) {
  color: #fff;
  font-weight: 800;
}
.segmented-radio__opt[data-curve="none"]:has(input:checked) {
  background: var(--color-ink-soft);
}
.segmented-radio__opt[data-curve="positive"]:has(input:checked) {
  background: var(--color-blue-border);
}
.segmented-radio__opt[data-curve="negative"]:has(input:checked) {
  background: var(--color-amber-border);
}

/* Announcement preset tag picker — checkbox chips shaped like the public tag chip. */
.tagpick { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.tagpick__opt {
  display: inline-flex; align-items: center; gap: var(--space-2);
  min-height: 36px; padding: var(--space-1) var(--space-3);
  background: var(--color-surface); border: 2px solid var(--color-ink);
  border-radius: var(--radius-pill);
  font-size: var(--fs-caption); font-weight: 700; cursor: pointer;
}
.tagpick__opt:has(input:checked) { background: var(--color-surface-sunk); }
.tagpick__opt input { width: 18px; height: 18px; }

/* Forms */
.editor-form { display: flex; flex-direction: column; gap: var(--space-3); }
.editor-form[hidden] { display: none; }
.editor-form--inline { gap: var(--space-3); }
@media (min-width: 600px) {
  .editor-form--inline { flex-direction: row; align-items: flex-end; flex-wrap: wrap; }
  .editor-form--inline .field { flex: 1 1 160px; }
}
.editor-actions { display: flex; gap: var(--space-3); flex-wrap: wrap; margin-top: var(--space-2); }

.checkfield {
  display: flex; align-items: center; gap: var(--space-2);
  min-height: var(--tap-min); font-size: var(--fs-body); font-weight: 600; cursor: pointer;
}
.checkfield input { width: 20px; height: 20px; }
.checkfield--inline { min-height: auto; }

/* Compact labelled control used inside editor rows */
.efield { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.efield[hidden] { display: none; }
.efield__lab { font-size: var(--fs-caption); font-weight: 700; color: var(--color-ink-soft); }
.efield textarea.field__control { min-height: 60px; }
/* Fixed-width codes (connection CRS): cap the control rather than stretch a
   3-character field across a whole grid column. */
.efield--narrow .field__control { max-width: 9ch; text-transform: uppercase; }
.efield--narrow .field__control::placeholder { text-transform: none; }
/* Connection TOC: a short comma-separated list ("ALL, LM, VT"), wider than a
   single code but still capped rather than stretched to fill the grid column. */
.efield--toclist .field__control { max-width: 20ch; text-transform: uppercase; }
.efield--toclist .field__control::placeholder { text-transform: none; }

/* Row collection */
.rows { display: flex; flex-direction: column; gap: var(--space-3); }
.editrow {
  display: grid; grid-template-columns: 1fr; gap: var(--space-3);
  padding: var(--space-3); background: var(--color-surface-sunk);
  border: var(--border-inner); border-radius: var(--radius-cell);
}
@media (min-width: 560px) {
  .editrow { grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); align-items: end; }
  /* One wide station name plus a short CRS and a slightly wider TOC list, plus
     the drag grip (§3.13) as its own leading column. The default auto-fit track
     would hand the 3-character CRS as much width as the name and wrap the row
     onto two lines well before it needs to. */
  .ecard--connections .editrow { grid-template-columns: auto minmax(160px, 1fr) auto auto auto; }
}
/* Below 560px .editrow falls back to the single-column stack above, so the grip
   just becomes the row's first block — still a full 44px tap target. */
.ecard--connections .draghandle { align-self: center; }
.editrow__tools {
  display: flex; align-items: flex-end; gap: var(--space-3); flex-wrap: wrap;
}
.editrow__tools .efield { flex: 0 0 90px; }
.editrow__inline { display: contents; }
.editrow__del-form { display: flex; align-items: flex-end; }
.editrow__del input { width: 20px; height: 20px; }

.usage-tag {
  display: inline-flex; align-items: center; padding: 2px var(--space-2);
  border-radius: var(--radius-pill); background: var(--color-surface);
  border: var(--border-inner); font-size: var(--fs-caption); font-weight: 600; color: var(--color-ink-mute);
}

/* Start-signal aspect multi-pick */
.aspectpick { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.aspectpick__opt {
  display: inline-flex; align-items: center; gap: var(--space-1);
  min-height: var(--tap-min); padding: 0 var(--space-3);
  border: 2px solid var(--color-ink); border-radius: var(--radius-chip);
  font-size: var(--fs-caption); font-weight: 700; cursor: pointer;
}
.aspectpick__opt input { width: 18px; height: 18px; }

/* Dispatch car painter — reuses the diagram's car look; each car is a tap target
   cycling green -> amber -> red, writing the JSON hidden input. */
.carpaint { display: flex; flex-direction: column; gap: var(--space-2); }
.carpaint__cars { display: flex; flex-wrap: wrap; gap: 0; }
.carpaint__car {
  width: 44px; height: 44px; flex: 0 0 44px;
  border: 2px solid var(--color-car-border);
  display: flex; align-items: center; justify-content: center;
  font-size: var(--fs-caption); font-weight: 800; color: rgba(0,0,0,0.55); cursor: pointer;
}
.carpaint__car + .carpaint__car { border-left: none; }
/* Unit gap in the painter preview (mirrors the diagram's cars-pattern gap). The car
   after a gap re-draws its left border since it starts a new unit. */
.carpaint__gap { flex: 0 0 10px; }
.carpaint__gap + .carpaint__car { border-left: 2px solid var(--color-car-border); }
.carpaint__car--g { background: var(--color-green-solid); }
.carpaint__car--a { background: var(--color-amber-solid); }
.carpaint__car--r { background: var(--color-red-solid); }
.carpaint__car:focus-visible { outline: 3px solid var(--color-focus-ring); outline-offset: 2px; z-index: 1; }

.route-head { margin-bottom: var(--space-4); }
.route-head__desc { margin: var(--space-1) 0 var(--space-3); }
/* Direction toggle fills the column so long endpoint names never overflow it. */
#route-direction { display: flex; width: 100%; }
#route-direction .segmented__opt {
  flex: 1 1 0; min-width: 0; padding-left: var(--space-3); padding-right: var(--space-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.route-seq { display: flex; flex-direction: column; gap: var(--space-4); }
.route-seq__trail { margin-top: calc(-1 * var(--space-4)); margin-bottom: calc(-1 * var(--space-4)); }
.route-seq__trail--empty { margin-top: calc(-1 * var(--space-4)); margin-bottom: calc(-1 * var(--space-4)); height: var(--space-4); position: relative; }
.route-seq__trail--empty::before { content: ""; position: absolute; left: 19px; top: 0; bottom: 0; width: 2px; background: var(--color-ink); }

/* Route editor — interleaved sequence (§4.5). Drag-to-reorder station units and
   POI rows by the grip; no typed order fields. */
.seq { display: flex; flex-direction: column; gap: var(--space-3); }
.sequnit {
  border: var(--border-card); border-radius: var(--radius-cell);
  background: var(--color-surface); overflow: hidden;
}
/* Dragging: the row lifts and follows the cursor (JS sets translateY). A shadow +
   raised stacking order make it read as picked-up; the empty flow slot it leaves is
   the drop placeholder. Shared by every drag-reorder context (§3.13): the route
   editor's sequence and the station editor's Connections rowset. */
.sequnit.is-dragging, .seqrow--poi.is-dragging, .editrow.is-dragging {
  position: relative; z-index: 20;
  box-shadow: 0 10px 24px rgba(30, 30, 30, 0.22);
  cursor: grabbing; will-change: transform;
}
.seqrow { display: flex; align-items: center; gap: var(--space-2); }
.seqrow--station {
  padding: var(--space-2) var(--space-3);
  background: var(--color-surface-sunk); border-bottom: var(--border-inner);
}
.seqrow__station { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.seqrow__station-name { font-size: var(--fs-body); font-weight: 700; }
.seqrow__station-crs { font-size: var(--fs-caption); font-weight: 700; color: var(--color-ink-mute); }
.draghandle {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 32px; min-height: var(--tap-min);
  background: none; border: none; color: var(--color-ink-mute);
  cursor: grab; touch-action: none;
}
.draghandle:active { cursor: grabbing; }
.draghandle .icon { width: 16px; height: 16px; }
.seqrow__del {
  flex: 0 0 auto; width: 40px; height: 40px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 2px solid var(--color-red-border); border-radius: var(--radius-cell);
  background: var(--color-red-fill); color: var(--color-red-text); cursor: pointer;
}
.seqrow__del:active { filter: brightness(0.96); }
.seqrow__del .icon { width: 14px; height: 14px; }
.seqrow__delcheck { display: inline-flex; align-items: center; gap: var(--space-1); font-size: var(--fs-caption); font-weight: 600; }
.seqrow__delcheck input { width: 18px; height: 18px; }
.seg { padding: var(--space-2) var(--space-3) var(--space-3); display: flex; flex-direction: column; gap: var(--space-2); }
.seg__pois { display: flex; flex-direction: column; gap: var(--space-2); min-height: var(--space-2); }
.seqrow--poi {
  padding: var(--space-2); background: var(--color-surface-sunk);
  border: var(--border-inner); border-radius: var(--radius-cell); flex-wrap: wrap;
}
.seqrow--poi .efield { min-width: 0; margin: 0; }
/* Mobile: grip + type on the first line; name (and, for junction_link, link) stack
   full-width below so nothing clips at 375px; the delete button sits on its own line.
   Desktop reflows the whole row inline. */
.seqrow__type { flex: 1 1 auto; min-width: 120px; }
.seqrow__name { flex: 1 1 100%; }
.seqrow__link { flex: 1 1 100%; }
.seqrow__link[hidden] { display: none; }
@media (min-width: 560px) {
  .seqrow__type { flex: 0 0 160px; }
  .seqrow__name { flex: 2 1 140px; }
  .seqrow__link { flex: 1 1 130px; }
}
.seg__add { align-self: flex-start; }
.seq-add { display: flex; gap: var(--space-3); align-items: flex-end; flex-wrap: wrap; margin-top: var(--space-4); }
.seq-add .efield { flex: 1 1 220px; }

/* Route-editor quiz section (§4.5 / §4.6): authored custom MCQs. One question card
   per row; up to four options, each with a "correct" radio on its left. */
.qrow {
  border: var(--border-card); border-radius: var(--radius-cell);
  background: var(--color-surface); padding: var(--space-3);
  display: flex; flex-direction: column; gap: var(--space-3); margin-bottom: var(--space-3);
}
.qrow__head { display: flex; align-items: flex-start; gap: var(--space-2); }
.qrow__head .efield { flex: 1 1 auto; margin: 0; }
.qrow__opts { display: flex; flex-direction: column; gap: var(--space-2); }
.qrow__optlabel { font-size: var(--fs-caption); font-weight: 700; color: var(--color-ink-mute); }
.qopt { display: flex; align-items: center; gap: var(--space-2); }
.qopt__correct { flex: 0 0 auto; display: inline-flex; align-items: center; gap: var(--space-1); font-size: var(--fs-caption); font-weight: 600; white-space: nowrap; }
.qopt__correct input { width: 18px; height: 18px; }
.qopt .efield { flex: 1 1 auto; margin: 0; }
.qadd { align-self: flex-start; margin-top: var(--space-2); }

/* =========================================================================
   Quiz mode (§4.6 / docs/IDEAS_PLAN.md §2). A single card that JS re-renders one
   question at a time: MCQ options or a drag-to-order list, marked on Check, then a
   results tally. All states use the fixed green/red semantics (§2.1).
   ========================================================================= */
.quiz__head { display: flex; gap: var(--space-2); flex-wrap: wrap; }
.quiz__stage { margin-top: var(--space-2); }

.quiz__progress { display: flex; flex-direction: column; gap: var(--space-2); margin-bottom: var(--space-3); }
.quiz__count { font-size: var(--fs-caption); font-weight: 700; color: var(--color-ink-mute); text-transform: uppercase; letter-spacing: 0.04em; }
.quiz__bar { height: 6px; border-radius: 999px; background: var(--color-surface-sunk); overflow: hidden; }
.quiz__bar-fill { display: block; height: 100%; background: var(--color-ink); transition: width 0.2s ease; }

.quiz__q { font-size: var(--fs-heading); font-weight: 800; line-height: 1.3; margin-bottom: var(--space-3); outline: none; }
.quiz__q .chip { vertical-align: middle; position: relative; top: -1px; }
.quiz__hint { margin-bottom: var(--space-3); }

/* MCQ options — full-width tappable buttons. */
.quiz__opts { display: flex; flex-direction: column; gap: var(--space-2); }
.quiz__opt {
  width: 100%; text-align: left; min-height: var(--tap-min);
  padding: var(--space-2) var(--space-3);
  border: var(--border-card); border-radius: var(--radius-cell);
  background: var(--color-surface); color: var(--color-ink);
  font-size: var(--fs-body); font-weight: 600; cursor: pointer;
  transition: background 0.1s ease, border-color 0.1s ease;
}
.quiz__opt:hover:not(:disabled) { background: var(--color-surface-sunk); }
.quiz__opt.is-selected { border-color: var(--color-blue-border); background: var(--color-blue-fill); color: var(--color-blue-text); }
.quiz__opt.is-correct { border-color: var(--color-green-border); background: var(--color-green-fill); color: var(--color-green-text); }
.quiz__opt.is-wrong { border-color: var(--color-red-border); background: var(--color-red-fill); color: var(--color-red-text); }
.quiz__opt:disabled { cursor: default; }
.quiz__opt:focus-visible { outline: 3px solid var(--color-focus-ring); outline-offset: 2px; }

/* Order list — drag by the grip (follow-cursor) or the ▲ ▼ buttons. */
.quiz__order { display: flex; flex-direction: column; gap: var(--space-2); }
.quiz__order-item {
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2); background: var(--color-surface);
  border: var(--border-card); border-radius: var(--radius-cell);
}
.quiz__order-item.is-dragging {
  position: relative; z-index: 20;
  box-shadow: 0 10px 24px rgba(30, 30, 30, 0.22); will-change: transform;
}
.quiz__order-item.is-correct { border-color: var(--color-green-border); background: var(--color-green-fill); }
.quiz__order-item.is-wrong { border-color: var(--color-red-border); background: var(--color-red-fill); }
.quiz__order-grip {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 32px; min-height: var(--tap-min);
  background: none; border: none; color: var(--color-ink-mute);
  cursor: grab; touch-action: none;
}
.quiz__order-grip:active { cursor: grabbing; }
.quiz__order-grip .icon { width: 16px; height: 16px; }
.quiz__order-label { flex: 1 1 auto; min-width: 0; font-size: var(--fs-body); font-weight: 700; }
.quiz__order-moves { flex: 0 0 auto; display: inline-flex; gap: var(--space-1); }
.quiz__order-move {
  width: 36px; height: 36px; display: inline-flex; align-items: center; justify-content: center;
  border: var(--border-inner); border-radius: var(--radius-cell);
  background: var(--color-surface-sunk); color: var(--color-ink); cursor: pointer;
}
.quiz__order-move:disabled { opacity: 0.4; cursor: default; }
.quiz__order-move .icon { width: 14px; height: 14px; }

.quiz__explain { margin-top: var(--space-3); }
.quiz__verdict { font-size: var(--fs-body-strong); font-weight: 800; margin-bottom: var(--space-1); }
.quiz__verdict.is-correct { color: var(--color-green-text); }
.quiz__verdict.is-wrong { color: var(--color-red-text); }
.quiz__explain-text { font-size: var(--fs-body); color: var(--color-ink-soft); }

.quiz__actions { display: flex; gap: var(--space-3); flex-wrap: wrap; margin-top: var(--space-4); }

.quiz__results { text-align: center; padding: var(--space-4) 0; }
.quiz__score { font-size: 2.5rem; font-weight: 900; line-height: 1; color: var(--color-ink); }
.quiz__score-pct { margin-top: var(--space-2); }
.quiz__score-msg { margin-top: var(--space-1); }
.quiz__results .quiz__actions { justify-content: center; }

/* =========================================================================
   Admin area (Phase 7). Tables reuse the warning-collapsible inner-cell
   styling (white cells, 1px hairline — DESIGN_SPEC §4.5) inside a card;
   wide tables scroll inside .tablewrap, never the page.
   ========================================================================= */
.tablewrap { overflow-x: auto; }
.admin-table { width: 100%; border-collapse: collapse; }
.admin-table th, .admin-table td {
  padding: var(--space-2) var(--space-2); text-align: left; vertical-align: top;
  border: 1px solid var(--color-hairline); background: var(--color-surface);
}
.admin-table thead th { font-size: var(--fs-caption); font-weight: 600; color: var(--color-ink-mute); }
.admin-table tbody td { font-size: var(--fs-small); }
.pub-badge--danger { background: var(--color-red-fill); border-color: var(--color-red-border); color: var(--color-red-text); }
.rowactions { display: flex; gap: var(--space-2); flex-wrap: wrap; align-items: center; }
.pager { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); margin-top: var(--space-3); }

/* User action select width in admin table */
.user-action-select { width: auto; min-height: unset; font-size: var(--fs-small); padding: var(--space-2) var(--space-3); }

/* =========================================================================
   Admin hub — card grid (§4.5 "Admin hub template"). Each section is a white
   bordered tile: icon, label, at-a-glance count. Two columns from 380px of
   column width (same threshold as other internal grids), one below.
   ========================================================================= */
.hub-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: var(--space-3); }
.hub-card {
  display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-2);
  padding: var(--space-4); min-height: var(--tap-min);
  background: var(--color-surface); border: var(--border-card); border-radius: var(--radius-card);
  color: var(--color-ink); text-decoration: none;
  transition: filter 100ms ease;
}
.hub-card:hover { text-decoration: none; }
.hub-card:active { filter: brightness(0.96); }
.hub-card__icon {
  display: flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: var(--radius-chip);
  background: var(--color-surface-sunk); border: 2px solid var(--color-ink);
}
.hub-card__icon .icon { width: 20px; height: 20px; }
.hub-card__label { font-size: var(--fs-body-strong); font-weight: 700; }
.hub-card__count {
  display: inline-flex; align-items: center; padding: 2px var(--space-2);
  border-radius: var(--radius-pill); background: var(--color-surface-sunk);
  border: var(--border-inner); font-size: var(--fs-caption); font-weight: 600; color: var(--color-ink-mute);
}
.hub-card__count--alert { background: var(--color-red-fill); border: 2px solid var(--color-red-border); color: var(--color-red-text); }

/* Admin content-section list rows (Stations / Routes): name + badges left,
   publish state + Edit right; stacks on narrow screens. */
.admin-list { list-style: none; display: flex; flex-direction: column; }
.admin-list__row {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  flex-wrap: wrap; padding: var(--space-3) 0; min-height: var(--tap-min);
}
.admin-list__row + .admin-list__row { border-top: var(--border-inner); }
.admin-list__main { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; min-width: 0; }
.admin-list__name { font-weight: 700; }
.admin-list__tools { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }

/* Quiz Setup Category Selection */
.quiz-setup__actions-bar {
  display: flex; gap: var(--space-2); margin-bottom: var(--space-3);
}
.quiz-setup__categories {
  display: flex; flex-direction: column; gap: var(--space-2); margin-bottom: var(--space-4);
}
.quiz-setup__cat-card {
  display: flex; align-items: flex-start; gap: var(--space-3);
  padding: var(--space-3); border: var(--border-card); border-radius: var(--radius-cell);
  background: var(--color-surface); cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.quiz-setup__cat-card:hover {
  background: var(--color-surface-sunk);
}
.quiz-setup__checkbox {
  width: 20px; height: 20px; accent-color: var(--color-ink); cursor: pointer; margin-top: 2px;
}
.quiz-setup__cat-info {
  display: flex; flex-direction: column; gap: 2px;
}
.quiz-setup__cat-title {
  font-weight: 700; font-size: var(--fs-body); color: var(--color-ink); display: flex; align-items: center; gap: var(--space-2);
}
.quiz-setup__icon {
  width: 16px; height: 16px; opacity: 0.85;
}
.quiz-setup__cat-desc {
  font-size: var(--fs-small); color: var(--color-ink-mute);
}
.quiz-setup__cat-card--disabled {
  opacity: 0.3;
  cursor: not-allowed;
  background: var(--color-surface-sunk);
  border-color: var(--color-hairline);
}
.quiz-setup__cat-card--disabled:hover {
  background: var(--color-surface-sunk);
}
.quiz-setup__cat-card--disabled .quiz-setup__checkbox {
  cursor: not-allowed;
}
.quiz-setup__cat-card--disabled * {
  color: var(--color-ink-mute) !important;
}
.quiz-setup__subopt {
  display: flex; align-items: center; gap: var(--space-2);
  margin-top: var(--space-1); font-size: var(--fs-small); color: var(--color-ink-mute);
  cursor: pointer;
}
.quiz-setup__subcheckbox {
  width: 20px; height: 20px; accent-color: var(--color-ink); cursor: pointer;
}
.quiz-setup__footer {
  display: flex; gap: var(--space-2);
}

/* Quiz length picker (uses same elements/style as dispatch diagram picker) */
.quiz-length-picker.segmented {
  display: none;
}
.has-js .quiz-length-picker.segmented {
  display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-6);
  max-width: 100%; border: none; border-radius: 0; overflow: visible; background: transparent;
}
.quiz-length-picker.segmented .quiz-length-tab {
  flex: 1; min-width: 100px; min-height: var(--tap-min);
  display: inline-flex; align-items: center; justify-content: center;
  padding: var(--space-2) var(--space-4);
  border: 2px solid var(--color-ink); border-radius: var(--radius-pill);
  background: var(--color-surface); color: var(--color-ink);
  font-family: inherit; font-size: var(--fs-body-strong); font-weight: 700;
  cursor: pointer; transition: background-color 0.1s ease, color 0.1s ease;
}
.quiz-length-picker.segmented .quiz-length-tab[aria-pressed="true"] {
  background: var(--color-ink); color: #fff;
}
.quiz-length-picker.segmented .quiz-length-tab:hover:not(:disabled) {
  background: var(--color-surface-sunk);
}
.quiz-length-picker.segmented .quiz-length-tab[aria-pressed="true"]:hover:not(:disabled) {
  background: var(--color-ink); color: #fff;
}
.quiz-length-picker.segmented .quiz-length-tab:focus-visible {
  outline: 3px solid var(--color-focus-ring); outline-offset: 2px;
}

/* Fallback styling for no-JS */
.quiz-length-picker-fallback {
  display: flex; flex-direction: column; gap: var(--space-2); margin-bottom: var(--space-6);
}
.quiz-length-picker-fallback__option {
  display: flex; align-items: center; gap: var(--space-2); font-size: var(--fs-body);
}

/* ---------------------------------------------------------------------------
   4.7 Settings page — account summary + preference toggles
   --------------------------------------------------------------------------- */

.settings-account__grid {
  display: grid; grid-template-columns: auto 1fr; gap: var(--space-2) var(--space-4);
  margin: 0; align-items: baseline;
}
.settings-account__grid dt {
  font-size: var(--fs-caption); font-weight: 600; color: var(--color-ink-mute);
  text-transform: uppercase; letter-spacing: 0.04em;
}
.settings-account__grid dd {
  margin: 0; font-size: var(--fs-body); color: var(--color-ink); overflow-wrap: anywhere;
}
.settings-account__actions {
  display: flex; flex-wrap: wrap; gap: var(--space-2);
  margin-top: var(--space-4); padding-top: var(--space-4);
  border-top: var(--border-inner);
}

.setting-list {
  display: flex; flex-direction: column; gap: var(--space-2); margin-bottom: var(--space-4);
}
/* One preference: title on the left, its control on the right. The whole row
   is the <label>, so anywhere on it toggles the box (§5 tap targets). */
.setting {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-3); border: var(--border-card); border-radius: var(--radius-cell);
  background: var(--color-surface); cursor: pointer;
  transition: background 0.15s ease;
}
.setting:hover { background: var(--color-surface-sunk); }
.setting__title {
  font-weight: 700; font-size: var(--fs-body); color: var(--color-ink);
  display: flex; align-items: center; gap: var(--space-2); flex: 1 1 auto;
}
.setting__icon { width: 16px; height: 16px; opacity: 0.85; flex: 0 0 auto; }
.setting__checkbox {
  width: 24px; height: 24px; flex: 0 0 auto;
  accent-color: var(--color-ink); cursor: pointer;
}
.setting__checkbox:focus-visible { outline: 3px solid var(--color-focus-ring); outline-offset: 2px; }

.settings-actions { display: flex; gap: var(--space-2); }

/* Drawer note replacing the sync chip when offline saving is switched off (§4.7). */
.drawer__chip-note {
  padding: var(--space-2) var(--space-3); font-size: var(--fs-small); color: var(--color-ink-mute);
}
.drawer__chip-note a { color: inherit; }
