:root {
  color-scheme: dark;
  --bg: #0A0F15;
  --surface: #121A24;
  --text: #E9EDF2;
  --muted: #8B97A5;
  --border: #243040;
  --line: #22C58B;
  --line-ink: #08141F;
  /* applyLineTheme() overwrites these the moment a line is known, but they MUST exist
     statically too. Anything rendered before that runs — the owner dashboard on a cold
     load of #dashboard is the case that bit us — resolves var(--line-edge) to nothing,
     and `background: <nothing>` is transparent, not a fallback. The whole hour chart was
     drawing correctly and invisibly. Values are exactly what applyLineTheme(WIZ_LINE)
     computes: the default brand colour already clears 3:1 and 4.5:1 on this background,
     so edgeOn() returns it unchanged. */
  --line-edge: #22C58B;
  --line-text: #22C58B;
  --line-edge-ink: #08141F;
  --express: #F0A63C;
  --green: #39D98A;
  --amber: #F0A63C;
  --red: #FF6B61;
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5);
  --shadow-md: 0 10px 30px rgba(0, 0, 0, 0.55);
  --board: "Overpass Mono", ui-monospace, "SF Mono", "Cascadia Mono", Consolas, monospace;
  --warn-bg: #33280F; --warn-edge: #46370F; --warn-text: #F2C766;
  --danger-bg: #3E1A18; --danger-edge: #582522; --danger-text: #F5A79F;
  --map-bg: #0A0F15; --map-bg-edge: #17222E; --map-water: #123049; --map-water-edge: #2A5074;
  --map-ink: #FFFFFF; --map-ink-halo: #05090E; --map-term: #AEBAC7;
}
:root[data-theme="light"] {
  color-scheme: light;
  /* --text is #1E2A36 and not a harder black on purpose. #15212D on white measures
     16.31:1, against dark mode's #E9EDF2 on #121A24 at 14.90:1 — light mode was
     running HARDER than dark, which on a white ground reads as a hard black and was
     reported as such. #1E2A36 lands at 14.59:1: the two themes now cost the same to
     read, and it is still 3.2x the 4.5 bar. --muted needed no change (6.11 light
     against 5.89 dark, already matched). */
  --bg: #F2F5F9; --surface: #FFFFFF; --text: #1E2A36; --muted: #59636F; --border: #DCE3EC;
  /* Same defaults for the light theme — see the note above. These were hand-picked once
     and had drifted from what lineTokens(WIZ_LINE, "light") actually returns, so a cold
     load painted one green and the first frame after boot painted another. They are now
     exactly the computed set. */
  --line: #1b9e6f; --line-ink: #08141F;
  --line-edge: #1b9e6f; --line-text: #167e59; --line-edge-ink: #08141F;
  --express: #A8560A; --green: #0E9E68; --amber: #A8560A; --red: #C63A30;
  --shadow-sm: 0 1px 2px rgba(20, 40, 65, 0.08);
  --shadow-md: 0 12px 30px rgba(20, 40, 70, 0.14);
  --warn-bg: #FBF0D3; --warn-edge: #ECD9A0; --warn-text: #7A560F;
  --danger-bg: #FCE6E3; --danger-edge: #F2C3BD; --danger-text: #98271E;
  --map-bg: #E9F0F7; --map-bg-edge: #CCD8E4; --map-water: #C9E1F1; --map-water-edge: #99C1DC;
  --map-ink: #12202E; --map-ink-halo: #FFFFFF; --map-term: #55636F;
}
* { box-sizing: border-box; margin: 0; }
button, select, input { touch-action: manipulation; font: inherit; }
button:focus-visible, select:focus-visible, input:focus-visible {
  outline: 2px solid var(--line); outline-offset: 2px;
}
html { -webkit-text-size-adjust: 100%; }
body {
  background: var(--bg); color: var(--text);
  font: 16px/1.55 "Overpass", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  min-height: 100dvh;
}
#app { max-width: 520px; margin: 0 auto; min-height: 100dvh; display: flex; flex-direction: column; }
.view { display: flex; flex-direction: column; flex: 1; animation: viewIn 0.34s cubic-bezier(0.16, 1, 0.3, 1); }
@keyframes viewIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.hidden { display: none !important; }
.muted { color: var(--muted); }
.small { font-size: 12px; }
.center { text-align: center; padding: 40px 0; }
.error { color: var(--red); font-size: 13px; margin-top: 8px; }

#line-accent, #line-accent-2, #line-accent-3 { height: 4px; background: var(--line); }
.invisible { visibility: hidden !important; }
header {
  background: var(--surface); border-bottom: 1px solid var(--border); padding-bottom: 12px;
  position: sticky; top: 0; z-index: 20;
  /* The sky is painted inside this box; `isolation` keeps its stacking local. */
  isolation: isolate;
}
/* ---- the sky over the platform --------------------------------------------
   A strip of sky above the board, not a wash behind it.
   Full-height was the first attempt and it was wrong. Measured over the dark theme's
   #121A24 surface, a sky strong enough to see drags --muted text under 4.5:1 — golden
   came out at 1.86:1 — and solving for colours that keep it legible flattens every band
   into the same muddy near-black, so you get a contrast bug AND an invisible feature.
   The tension is real and the way out is geometry, not colour: confine the sky to the
   top ~72px, which contains only the accent bar and the header-row controls, and every
   one of those paints its own opaque background. Nothing legible sits on the sky, so the
   sky can be its true colour. e2e-sky asserts the strip ends above the route title. */
/* header is already `position: sticky`, which is a positioning context; only the
   wizard header needs one adding. Setting `relative` on both would kill the stick. */
.wiz-header { position: relative; }
.sky {
  /* 150px, and the number is a consequence of what the strip has to hold rather than of
     where the header's boxes fall.

     It was 60px — the accent bar plus the control row exactly, chosen when the invariant
     was "the strip must stop above the route title", because a full-strength band drags
     --muted text under 4.5:1 and geometry was the only way out. Weather made 60px
     untenable: the controls cover almost all of it, leaving about 12px of open sky, and
     rain in a 12px slot is invisible. So the reach moved past the title and the veil
     below took over the job the geometry was doing. Measured on real composited pixels
     (screenshot -> canvas, worst pixel across the full width, drops included): the route
     title comes out at 12.25:1 dark / 12.65:1 light and the line pill at 5.89 / 6.11.

     It costs no layout and no scroll. The strip is absolute inside a header that is 273px
     tall at every width, so this only paints further down; the 22px reserved band that was
     once needed here is long gone (see .header-row and .icon-btn — the controls are glass,
     so the sky reads THROUGH them and needs no room of its own). */
  position: absolute; top: 0; left: 0; right: 0; height: 150px;
  z-index: 0; pointer-events: none; overflow: hidden;
  background: linear-gradient(180deg, var(--sky-a) 0%, var(--sky-b) 52%, transparent 92%);
  transition: background 1.2s linear;
}
/* The veil: what makes 150px safe. Sky at full strength down to 56%, then a ramp to the
   header's own surface by 90% — so everything the strip reaches past the control row is
   fading into the page it sits on rather than being painted over. It is generated last,
   so it covers the precipitation layers too: rain has to thin out at the bottom for the
   same reason the colour does. */
.sky::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg,
    transparent 0%, transparent 56%,
    color-mix(in srgb, var(--surface) 62%, transparent) 76%,
    var(--surface) 90%);
}
header > *:not(.sky), .wiz-header > *:not(.sky) { position: relative; z-index: 1; }
/* ...but that rule gives every header child its own stacking context, which trapped the
   route picker. `.cs-panel` asks for z-index 60 and could never reach it: scoped inside
   `.header-row`'s z-index 1, it was outranked by `.pill-row` simply for coming later in
   the DOM, so the open dropdown rendered UNDER the line pill and a tap on the top option
   hit the pill instead. e2e-lines has been failing on exactly this. The row that owns a
   dropdown has to outrank its siblings. */
header > .header-row { z-index: 2; }
/* Tints, not slabs — but tints you can actually see.
   The first visible version used opaque hex stops and read as a painted bar. Dropping to
   very low alpha then made it invisible, which was the opposite mistake: most of the strip
   sits behind the header controls, and those paint their own opaque background, so only
   the gaps between them ever showed colour. Glass controls fixed the occlusion and these
   mid-weight alphas fixed the colour.

   Each band is a PAIR OF TOKENS rather than a whole gradient, because the strip now has
   weather layered over it and the geometry of the ramp has to be stated once, on .sky,
   where the veil below can be written against it. The six hues are unchanged. */
.sky-day       { --sky-a: rgba(108,172,228,0.42); --sky-b: rgba(76,132,186,0.16); }
.sky-golden    { --sky-a: rgba(232,150,76,0.46);  --sky-b: rgba(176,102,64,0.18); }
.sky-civil     { --sky-a: rgba(138,128,204,0.46); --sky-b: rgba(166,106,134,0.18); }
.sky-nautical  { --sky-a: rgba(82,106,178,0.46);  --sky-b: rgba(66,74,134,0.18); }
.sky-astro     { --sky-a: rgba(56,76,142,0.46);   --sky-b: rgba(44,56,104,0.18); }
.sky-night     { --sky-a: rgba(42,60,122,0.48);   --sky-b: rgba(30,42,82,0.20); }
/* Light theme: the page is white, so the same hues need a little more alpha to register,
   and day becomes a pale sky rather than a dark one.

   `[data-theme="light"]` ONLY — no `prefers-color-scheme` companion, here or anywhere else
   in this file. This app does not follow the OS: it keeps its own preference, defaults it
   to dark whatever the device says, and applyTheme() stamps the attribute on every boot,
   so an OS-keyed rule can never be the thing that decides. These six shipped briefly with
   a `@media (prefers-color-scheme: light)` twin, which was dead in every state except one
   — the window before applyTheme() runs, where it painted light sky tokens over the dark
   :root palette. That is the only route by which a device's own setting could ever reach
   this app, and it was a bug in all three of its effects. */
:root[data-theme="light"] .sky-day       { --sky-a: rgba(96,158,214,0.50); --sky-b: rgba(150,196,232,0.18); }
:root[data-theme="light"] .sky-golden    { --sky-a: rgba(226,146,72,0.50); --sky-b: rgba(238,190,140,0.18); }
:root[data-theme="light"] .sky-civil     { --sky-a: rgba(126,120,190,0.48); --sky-b: rgba(186,150,178,0.18); }
:root[data-theme="light"] .sky-nautical  { --sky-a: rgba(78,100,168,0.46); --sky-b: rgba(140,152,196,0.18); }
:root[data-theme="light"] .sky-astro     { --sky-a: rgba(52,70,130,0.44); --sky-b: rgba(120,132,180,0.16); }
:root[data-theme="light"] .sky-night     { --sky-a: rgba(38,54,110,0.44); --sky-b: rgba(110,124,172,0.16); }
/* ---- what the sky is DOING, over what time of day it is ---------------------
   The band answers "when"; these answer "what is it like out there". Both are true at
   once — rain at golden hour is an amber sky with rain falling through it — so weather
   layers over the band rather than replacing it. */

/* Overcast takes the colour out of the band. Without this, "Mostly Cloudy" and "Sunny"
   paint the identical sky, which is the same class of error as telling a rider the sun is
   on their right through a downpour. Opacity is set inline from the cloud fraction.

   It desaturates; it does not repaint. At full strength the grey buried the band entirely
   and an overcast evening and an overcast dawn came out as the same flat bar — so the
   alphas are kept low enough that the hour still reads through the weather. */
.sky-cloud {
  position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(150,160,176,0.40) 0%, rgba(140,150,166,0.16) 52%, transparent 92%);
}
:root[data-theme="light"] .sky-cloud { background: linear-gradient(180deg, rgba(172,182,196,0.46) 0%, rgba(200,208,220,0.18) 52%, transparent 92%); }
/* Fog is the one condition that LIFTS the strip rather than darkening it — it is light
   scattered back at you, so it reads as a pale wash across the middle. */
.sky-fog { position: absolute; inset: 0; background: linear-gradient(180deg, transparent 0%, rgba(186,199,214,0.22) 46%, transparent 84%); }
:root[data-theme="light"] .sky-fog { background: linear-gradient(180deg, transparent 0%, rgba(255,255,255,0.34) 46%, transparent 84%); }
/* The drops take their colour from the ground they fall on, and this is not a nicety: a
   pale blue-white drop is the right mark against a dark sky and completely invisible
   against a light one — measured by rendering both, where light-theme rain read as a few
   smudges near the title and nothing else. Rain and snow share the token; what changes
   between themes is which side of the band the mark sits on. */
.sky { --wx-ink: 202, 222, 248; }
:root[data-theme="light"] .sky { --wx-ink: 68, 88, 122; }

/* Precipitation. Three composited planes, each ONE element carrying dozens of background
   gradients — never a div per drop. Nothing here animates a layout or paint property: the
   only thing that moves is a transform, which the compositor owns, so a curtain of rain on
   a phone on a train costs no main-thread work and no measurable battery.

   The mask is what keeps it weather rather than a screen effect: drops fade in below the
   top edge and out before the bottom, so nothing is ever seen starting or stopping. */
.sky-wx {
  position: absolute; inset: 0; overflow: hidden;
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 14%, #000 72%, transparent 100%);
  mask-image: linear-gradient(180deg, transparent 0, #000 14%, #000 72%, transparent 100%);
}
/* Rain does not fall plumb, and a field of exactly vertical marks reads as a loading bar.
   The wrapper is oversized so the rotation can't expose a corner. */
.sky-tilt { position: absolute; inset: -30%; transform: rotate(5deg); }
/* One TILE of overhang above the box, which is what makes the loop seamless rather than a
   sawtooth. The layer must be taller than the distance it travels: at `inset: 0` it is
   exactly one tile tall, so translating it by one tile carries it completely off the top
   and the rain vanishes for most of every cycle. Starting it a tile high and sliding it
   down a tile means the first and last frames are pixel-identical — a true loop — and the
   field covers the strip throughout. */
.wx-l {
  position: absolute; left: 0; right: 0; top: -240px; bottom: 0;
  background-repeat: repeat-y; will-change: transform;
  animation: skyfall var(--wx-dur, 1.4s) linear infinite;
}
/* repeat-y, and the axis is the whole bug. `background-repeat: repeat` tiles a 1.6px-wide
   drop HORIZONTALLY as well, turning each one into a solid band — reported, exactly, as
   "the TV is not able to find channels". A drop repeats down its own column and nowhere
   else. */
.wx-l.snow-a { animation-name: skyblow; }
.wx-l.snow-b { animation-name: skyblow-b; }
/* 240px is PRECIP_TILE in logic.js: one tile of travel per cycle. If that constant moves,
   these three and the -240px above move with it.
   Snow sways, and the sway has to START AND END on the same x or the loop jumps sideways
   once a cycle — which is far more visible on a slow, diffuse flake field than the drift
   itself. The two variants mirror each other so the planes don't move as one sheet. */
@keyframes skyfall   { from { transform: translate3d(0, 0, 0); } to { transform: translate3d(0, 240px, 0); } }
@keyframes skyblow {
  0%   { transform: translate3d(-14px, 0, 0); }
  50%  { transform: translate3d(14px, 120px, 0); }
  100% { transform: translate3d(-14px, 240px, 0); }
}
@keyframes skyblow-b {
  0%   { transform: translate3d(12px, 0, 0); }
  50%  { transform: translate3d(-12px, 120px, 0); }
  100% { transform: translate3d(12px, 240px, 0); }
}
/* On a moving vehicle this is a nausea question, not a taste one. The field still renders;
   it simply hangs still, which is a legible "it is raining" rather than nothing at all. */
@media (prefers-reduced-motion: reduce) {
  .wx-l { animation: none; will-change: auto; }
}

/* Stars: one element, many box-shadows. No DOM per star, nothing animated by default.
   The field spreads to ~50px, up from 39px — the original fifteen were scattered over
   33px, which was the whole of a 60px sky and a tight band across the top of this one.
   It stops there and not lower, and the reason is contrast rather than taste: the route
   title's box begins at 60px, and a star is part of the GROUND behind that text where a
   raindrop is a passing mark over it. Rain crossing a glyph is what rain does; a fixed
   bright dot sitting behind one is a background that fails its ratio. */
.sky-stars {
  position: absolute; top: 6px; left: 10%; width: 2px; height: 2px; border-radius: 50%;
  background: #FFFFFF; opacity: 0.8;
  box-shadow:
    18px 4px 0 -0.5px #FFF, 52px 12px 0 -0.5px #DCE6FF, 96px 3px 0 -0.5px #FFF,
    134px 16px 0 -0.5px #C8D6FF, 168px 7px 0 -0.5px #FFF, 205px 20px 0 -0.5px #E6EEFF,
    246px 5px 0 -0.5px #FFF, 279px 14px 0 -0.5px #D2E0FF, 312px 9px 0 -0.5px #FFF,
    31px 26px 0 -0.5px #E8F0FF, 74px 31px 0 -0.5px #FFF, 118px 27px 0 -0.5px #D8E4FF,
    190px 33px 0 -0.5px #FFF, 231px 29px 0 -0.5px #E0EAFF, 295px 24px 0 -0.5px #FFF,
    8px 38px 0 -0.5px #D6E2FF, 61px 43px 0 -0.5px #FFF, 108px 36px 0 -0.5px #E4ECFF,
    152px 41px 0 -0.5px #FFF, 216px 37px 0 -0.5px #CEDCFF, 263px 44px 0 -0.5px #FFF,
    301px 39px 0 -0.5px #DEE8FF, 42px 21px 0 -0.5px #FFF, 178px 18px 0 -0.5px #D4E0FF,
    258px 34px 0 -0.5px #FFF;
}
/* Only the stars breathe, and only if the reader wants motion at all. This runs on a
   moving vehicle, where animation is a nausea question and not just a taste one. */
@media (prefers-reduced-motion: no-preference) {
  .sky-stars { animation: twinkle 5.5s ease-in-out infinite; }
}
@keyframes twinkle { 0%, 100% { opacity: 0.8; } 50% { opacity: 0.5; } }

/* Tonight's actual moon, drawn from the real phase.
   It was 24px at 0.88 opacity, hard-edged, pinned to the top-right corner — which is
   button-sized, button-bright and in the exact spot a close or profile control lives. It
   was read as something to tap, and it is pointer-events: none. Four changes, each aimed
   at one half of that: smaller than any control, dimmer, a soft glow instead of a cut
   edge, and out of the corner entirely. */
.sky-moon {
  position: absolute; top: 5px; right: 27%; width: 15px; height: 15px; opacity: 0.55;
  filter: drop-shadow(0 0 5px rgba(232, 238, 248, 0.45));
}
.sky-moon svg { display: block; width: 100%; height: 100%; }
/* 12px again. The 34px version existed to open a band of bare sky above the controls,
   because opaque controls left nothing of the strip visible. Glass backgrounds solved
   that properly, so the 22px goes back to the rider. */
.header-row { display: flex; justify-content: space-between; align-items: center; gap: 10px; padding: 12px 16px 0; }
#route-picker {
  border: 1px solid var(--border); background: var(--bg); color: var(--text);
  font-size: 13px; font-weight: 500; flex: 0 1 auto; min-width: 0; max-width: 320px;
  height: 44px; padding: 0 12px; border-radius: 11px; cursor: pointer;
}
.header-actions { display: flex; gap: 8px; flex-shrink: 0; }
.icon-btn {
  display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
  border: 1px solid var(--border); background: var(--surface); color: var(--text);
  border-radius: 11px; width: 44px; height: 44px; cursor: pointer; padding: 0;
  transition: border-color 0.15s, background 0.15s, transform 0.1s;
}
/* ---- glass over the sky ---------------------------------------------------
   Only the controls that sit inside the 64px strip. A translucent background lets the
   sky read through them instead of blocking it, which is what the 22px band was for.
   The alpha is the load-bearing part and the blur is the garnish: `backdrop-filter` is
   unsupported or disabled often enough (and this app targets a car dashboard browser)
   that the background alone has to keep the glyph legible. At 78% of --surface it does,
   in both themes, over every band — e2e-sky measures it rather than trusting this. */
header .header-row .icon-btn,
header .header-row #route-picker,
header .header-row .cs-btn,
.wiz-header .wiz-top .icon-btn {
  background: color-mix(in srgb, var(--surface) 78%, transparent);
  -webkit-backdrop-filter: blur(12px) saturate(1.35);
  backdrop-filter: blur(12px) saturate(1.35);
}
.icon-btn:hover { border-color: var(--line); }
.icon-btn:active { transform: scale(0.94); }
.icon-btn svg { width: 20px; height: 20px; display: block; }
h1 { padding: 10px 16px 0; font-size: 20px; font-weight: 680; letter-spacing: -0.02em; }
h1 .arrow { color: var(--line); }
.tappable-title { cursor: pointer; }
.tappable-title:hover { color: var(--line); }
.tappable-title:focus-visible { outline: 2px solid var(--line); outline-offset: 2px; border-radius: 6px; }

.pill-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 8px 16px 0; }
#line-pill { padding: 0; }
.alert-chip {
  display: inline-flex; align-items: center; font: inherit; font-size: 11.5px; font-weight: 700;
  color: var(--warn-text); background: var(--warn-bg); border: 1px solid var(--warn-edge); border-radius: 999px;
  padding: 4px 11px; cursor: pointer;
}
.alert-chip:hover { filter: brightness(1.12); }
/* Everything on this line is silenced. The chip stays — it is the warning triangle, and
   hiding it was what made "Never show again" a one-way door — but it must not read as a
   live warning, so it drops to the muted palette. */
.alert-chip.hushed { color: var(--muted); background: transparent; border-color: var(--border); }
/* --line-edge, not --line: the raw GTFS hex is ~1.1:1 against a light background on
   UP-NW's yellow, i.e. a focus ring a keyboard user cannot see. */
.alert-chip:focus-visible { outline: 2px solid var(--line-edge); outline-offset: 2px; }

/* ---- the alert list, expanded in place under the header ---- */
.al-list { border-bottom: 1px solid var(--border); }
.al-row {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 10px 16px; border-top: 1px solid var(--border);
  background: var(--warn-bg); color: var(--warn-text); font-size: 13px;
}
.al-row.silenced { background: transparent; color: var(--muted); }
.al-txt { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.al-desc { font-size: 12px; opacity: 0.92; }
/* margin-left:auto, not space-between: the machine-translation tag is absent on most
   alerts, and space-between would throw the button to the left whenever it is. */
.al-again { margin-left: auto; flex-shrink: 0; align-self: center; }
.al-head {
  padding: 9px 16px 5px; font-size: 11px; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--muted); border-top: 1px solid var(--border);
}
.al-none { padding: 12px 16px; font-size: 13px; color: var(--muted); }

.toasts {
  position: fixed; top: calc(8px + env(safe-area-inset-top)); left: 50%; transform: translateX(-50%);
  z-index: 300; width: min(480px, calc(100vw - 24px)); display: flex; flex-direction: column; gap: 8px;
  pointer-events: none;
}
.toast {
  pointer-events: auto; display: flex; gap: 10px; align-items: flex-start;
  background: var(--warn-bg); color: var(--warn-text); border: 1px solid var(--warn-edge); border-radius: 12px;
  padding: 11px 12px; box-shadow: var(--shadow-md);
  opacity: 0; transform: translateY(-12px); transition: opacity 0.35s ease, transform 0.35s ease;
}
.toast.in { opacity: 1; transform: translateY(0); }
.toast.out { opacity: 0; transform: translateY(-12px); }
.toast-ico { flex-shrink: 0; font-size: 15px; line-height: 1.3; }
.toast-body { flex: 1; min-width: 0; font-size: 13px; }
.toast-body b { display: block; font-weight: 700; }
/* Metra writes a paragraph where a line would do. Two lines and a tap for the rest, so a
   stack of these can't bury the board underneath them. */
.toast-body { cursor: pointer; }
.toast-body span {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
  color: var(--warn-text); opacity: 0.82; margin-top: 2px;
}
.toast.open .toast-body span { -webkit-line-clamp: unset; overflow: visible; }
.toast-x { flex-shrink: 0; border: none; background: transparent; color: var(--warn-text); opacity: 0.72; font-size: 13px; cursor: pointer; padding: 2px 4px; }
.toast-acts { display: flex; gap: 14px; margin-top: 8px; }
.toast-hide { border: none; background: transparent; padding: 0; color: var(--warn-text); opacity: 0.85; font: inherit; font-size: 12px; font-weight: 700; text-decoration: underline; cursor: pointer; }
.toast-hide.never { opacity: 0.62; font-weight: 600; }
.toast-hide:hover { opacity: 1; }
.toast.toast-update { background: var(--surface); color: var(--text); border-color: var(--border); align-items: center; }
.toast.toast-update .toast-body span { color: var(--muted); opacity: 1; }
.toast-refresh { flex: none; background: var(--line); color: var(--line-ink); border: none; border-radius: 8px; padding: 6px 14px; font-size: 13px; font-weight: 600; cursor: pointer; }
@media (prefers-reduced-motion: reduce) { .toast { transition: opacity 0.2s ease; } .toast.in, .toast.out { transform: none; } }
.line-pill {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 12px; font-weight: 600; color: var(--muted);
  background: var(--bg); border: 1px solid var(--border); border-radius: 999px;
  padding: 4px 11px; max-width: 100%;
}
/* Swatches keep the line's real colour — showing it IS the job, so correcting it would
   be a lie about the line. What they get instead is a hairline ring, so the shape reads
   even when the fill doesn't: HC is 1.50:1 on a dark page and UP-NW 1.16:1 on a light one.
   They sit beside the line's name in every case, so the colour is never the only cue. */
.line-pill .lp-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; box-shadow: 0 0 0 1px var(--text); }
.line-pill .lp-txt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.controls { display: flex; flex-direction: column; gap: 8px; padding: 12px 16px 2px; }
.seg {
  display: flex; gap: 2px; padding: 3px;
  background: var(--bg); border: 1px solid var(--border); border-radius: 12px;
}
.seg button {
  flex: 1 1 0; min-width: 0; border: none; background: transparent; color: var(--muted);
  font: inherit; font-size: 13px; font-weight: 500; padding: 11px 10px; border-radius: 9px;
  cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  transition: background 0.15s, color 0.15s;
}
.seg button:not(.on):hover { color: var(--text); }
.seg button.on {
  background: var(--line); color: var(--line-ink); font-weight: 600;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
}
.dir-context { display: flex; align-items: center; gap: 8px; padding: 9px 16px 0; font-size: 13px; }
.dir-context .dc-arrow { color: var(--line); font-weight: 800; font-size: 15px; line-height: 1; flex-shrink: 0; }
.dir-context .dc-main { color: var(--text); font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dir-context .dc-tag {
  margin-left: auto; flex-shrink: 0; font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--muted); border: 1px solid var(--border); border-radius: 999px; padding: 3px 9px;
}

#alerts { padding: 0; }
.alert {
  display: flex; gap: 10px; padding: 10px 16px; font-size: 13px;
  background: var(--warn-bg); color: var(--warn-text); border-bottom: 1px solid var(--warn-edge);
}
.alert::before { content: "⚠"; }
.alert.severe { background: var(--danger-bg); color: var(--danger-text); border-bottom-color: var(--danger-edge); }
.banner { padding: 9px 16px; font-size: 13px; background: var(--warn-bg); color: var(--warn-text); border-bottom: 1px solid var(--warn-edge); display: flex; align-items: center; gap: 10px; }
.banner.offline { background: transparent; color: var(--muted); border-bottom: 1px solid var(--border); }
.banner.offline.stale { background: var(--warn-bg); color: var(--warn-text); border-bottom-color: var(--warn-edge); }
.banner-msg { flex: 1; min-width: 0; }
.banner-retry { flex: none; background: transparent; border: 1px solid currentColor; color: inherit; border-radius: 7px; padding: 3px 11px; font-size: 12px; font-weight: 600; cursor: pointer; opacity: 0.85; }
.banner-retry:hover { opacity: 1; }
.banner-retry:disabled { opacity: 0.5; cursor: default; }
.conn-error { text-align: center; padding: 48px 20px; }
.conn-emoji { font-size: 34px; margin-bottom: 10px; }
.conn-title { font-size: 16px; font-weight: 650; margin-bottom: 4px; }
.conn-error .primary { margin-top: 16px; }
.rt-stale { color: var(--amber); font-weight: 600; }

.stops-toggle {
  display: flex; align-items: center; justify-content: center; gap: 6px; width: 100%;
  margin-top: 14px; border: none; background: transparent; color: var(--muted);
  font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer; padding: 4px;
}
.stops-toggle:hover { color: var(--text); }
.stops-toggle .caret, .trip-row .caret { display: inline-block; transition: transform 0.2s; font-size: 11px; }
.trip-row .caret { color: var(--muted); font-size: 10px; line-height: 1; }
.stops-wrap.open .stops-toggle .caret, .trip.open .trip-row .caret { transform: rotate(180deg); }
.stops-collapse { max-height: 0; overflow: hidden; transition: max-height 0.35s ease; }
.stops-wrap.open .stops-collapse, .trip.open .stops-collapse { max-height: 1400px; }

.rs { margin: 6px 0 4px; }
.rs-head { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.rs-head-title { flex: 1; min-width: 0; font-size: 12px; font-weight: 600; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rs-head-title .arrow { color: var(--line); }
.rs-status { flex-shrink: 0; font-size: 11px; font-weight: 700; }
.rs-status.live { color: var(--green); } .rs-status.est { color: var(--line); } .rs-status.pre { color: var(--muted); font-weight: 600; }
.rs-rev {
  flex-shrink: 0; border: 1px solid var(--border); background: var(--bg); color: var(--muted);
  border-radius: 8px; width: 30px; height: 26px; font-size: 14px; cursor: pointer; line-height: 1;
}
.rs-rev:hover { border-color: var(--line); color: var(--text); }
.rs-body { position: relative; max-height: 300px; overflow-y: auto; -webkit-overflow-scrolling: touch; }
.rs-stop { display: flex; align-items: center; height: 34px; gap: 4px; padding-right: 10px; }
.rs-rail { position: relative; width: 26px; flex-shrink: 0; height: 100%; display: flex; justify-content: center; }
/* Full strength: the rail is the spine the whole list hangs off, and compositing the
   line colour at half opacity put every one of the eleven lines under 3:1. Hierarchy
   against the dots comes from width (3px vs 10-14px), not from fading it to nothing. */
.rs-rail::before { content: ""; position: absolute; top: 0; bottom: 0; width: 3px; background: var(--line-edge); }
.rs-stop:first-child .rs-rail::before { top: 50%; }
.rs-stop:last-child .rs-rail::before { bottom: 50%; }
.rs-dot { position: relative; z-index: 1; width: 9px; height: 9px; border-radius: 50%; align-self: center; background: var(--bg); border: 2px solid var(--line-edge); }
.rs-stop.off .rs-dot { border-color: var(--muted); width: 7px; height: 7px; }
.rs-stop.off .rs-rail::before { opacity: 0.25; }
.rs-name { flex: 1; min-width: 0; font-size: 15px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rs-stop.off .rs-name { color: var(--muted); }
.rs-stop.from .rs-dot, .rs-stop.to .rs-dot { width: 13px; height: 13px; border-width: 3px; }
.rs-stop.from .rs-dot { background: var(--line-edge); }
.rs-stop.to .rs-dot { background: var(--bg); box-shadow: 0 0 0 3px var(--border); }
.rs-stop.from .rs-name, .rs-stop.to .rs-name { font-weight: 700; }
.rs-tag { flex-shrink: 0; font-size: 9px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--line-ink); background: var(--line); border-radius: 999px; padding: 1px 7px; }
.rs-time { flex-shrink: 0; font-family: var(--board); font-variant-numeric: tabular-nums; font-size: 12px; color: var(--green); }
.rs-stop.off .rs-time { color: var(--muted); font-family: inherit; font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.04em; }
.rs-train { position: absolute; left: 0; width: 26px; transform: translateY(-50%); display: flex; justify-content: center; pointer-events: none; z-index: 3; }
/* The ring carries the contrast against the page; the white core then reads against the
   ring by construction, because --line-edge is at least 3:1 away from the background. */
.rs-train-dot { width: 15px; height: 15px; border-radius: 50%; background: var(--line-edge-ink); border: 3px solid var(--line-edge);
  box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.10), 0 1px 4px rgba(0, 0, 0, 0.55); }

.trip { border-bottom: 1px solid var(--border); }
.trip-row { width: 100%; text-align: left; background: transparent; border: none; cursor: pointer; }
.trip-row.row { border-bottom: none; }
.trip-row .right { display: flex; align-items: center; justify-content: flex-end; gap: 6px; }
.trip.open { background: rgba(255, 255, 255, 0.02); }
.trip .stops-collapse { padding: 0 4px 10px; }

main#content { padding: 16px; flex: 1; }
.direction-head { font-size: 14px; font-weight: 600; margin: 14px 0 6px; }
.direction-head .arrow { color: var(--line); }

.hero {
  background: var(--surface); border: 1px solid var(--border); border-radius: 16px;
  padding: 18px; margin-bottom: 12px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
  display: flex; flex-direction: column;
}

.chip {
  display: inline-block; font-size: 10px; font-weight: 700; letter-spacing: 0.08em;
  padding: 3px 9px; border-radius: 999px; text-transform: uppercase;
}
.chip.E { background: transparent; border: 1px solid var(--express); color: var(--express); font-weight: 600; }
.chip.L { background: transparent; border: 1px solid var(--border); color: var(--muted); font-weight: 600; }
/* Top-aligned, not centred: the countdown block is much taller than the chip row, and
   centring it dragged the chips down into the middle of the card.
   It WRAPS, and that is load-bearing. The flapboard is 145px at MM:SS but 197px past the
   hour (five cards, two colons — see the note on countdownHtml), and at 320px the card has
   250px of content. That leaves the left column 41px against a 56px "LOCAL" chip, which
   cannot wrap because it is one word. Rather than shrink the board or pick a breakpoint,
   let the row break: below the min-width the countdown takes a line of its own and the
   left column gets the full width. Wrapping is rare — 320px AND an hour-plus countdown. */
.hero-top { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: flex-start; gap: 12px; }
.hero-tl { display: flex; flex-direction: column; gap: 6px; flex: 1 1 0; min-width: 84px; }
/* min-width, not 0: 84px is the widest single unbreakable token in the column (the
   class chip in Polish). Below that the head wraps instead of overflowing. */
.hero-tl .hero-meta { margin-top: 0; }
.hero-chips { flex: 1; min-width: 0; display: flex; align-items: center; flex-wrap: wrap; gap: 6px; padding-top: 2px; }
.hero-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; font-size: 12px; color: var(--muted); margin-top: 6px; }
.hero-meta .trip-dur { display: inline-flex; align-items: center; gap: 3px; }
.hero-meta .trip-dur::before { content: "·"; margin-right: 8px; opacity: 0.55; }
.hero-meta .jt-ico { opacity: 0.85; }
.hm-train { white-space: nowrap; }
/* ---- conditions: the weather, and which side the sun will be on ----
   A full-width row of its own below the live location, not two chips in the chip column.
   The chip column is 99-209px on a phone (the countdown is flex-shrink:0 at 145px), which
   cannot hold "Sun on the left" in English at 320px or in Polish at 360px. Here the widest
   string in any dictionary has room to spare at every width.
   An aside, never a warning: --muted, and nothing has gone wrong. */
.cond-row {
  display: flex; align-items: center; flex-wrap: wrap; gap: 3px 14px;
  margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--border);
  font-size: 12px; color: var(--muted);
}
.cond-row .wx-rain { opacity: 0.9; }
.cond-row[hidden] { display: none; }
/* The separator belongs to the sun, and only exists once the forecast has actually landed
   — the weather span is present but empty until then. */
.cond-row .wx:not(:empty) + .sun-toggle::before { content: "·"; margin-right: 8px; opacity: 0.55; }
.sun-toggle {
  display: inline-flex; align-items: center; gap: 5px;
  background: none; border: 0; padding: 4px 0; margin: -4px 0;
  color: var(--muted); font: inherit; font-size: 12.5px; font-weight: 500;
  cursor: pointer; text-align: left;
}
.sun-toggle:hover { color: var(--text); }
.sun-toggle:focus-visible { outline: 2px solid var(--line-edge); outline-offset: 3px; border-radius: 5px; }
.sun-toggle .sun-ico { color: var(--amber); font-size: 13px; line-height: 1; }
.sun-toggle .sun-caret { font-size: 9px; opacity: 0.75; transition: transform 0.2s ease; }
.sun-toggle[aria-expanded="true"] .sun-caret { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .sun-toggle .sun-caret { transition: none; } }
.sun-why {
  margin-top: 8px; padding: 9px 11px; border-radius: 10px;
  background: var(--bg); border: 1px solid var(--border);
  font-size: 12.5px; line-height: 1.45; color: var(--muted);
}
.sun-why[hidden] { display: none; }
/* Not decoration — without it the sun hint survives the rain. `[hidden]` is a UA rule of
   the lowest possible specificity, so any class that sets `display` outranks it, and
   .sun-toggle sets `inline-flex`. loadWeather() withdraws the hint by setting `hidden`
   when the forecast says wet or overcast; the explainer obeyed (it had this rule) and the
   button did not, so the half a rider actually reads stayed on screen through a downpour
   — the exact thing the gate was added to stop. */
.sun-toggle[hidden] { display: none; }
.live-loc { display: inline-flex; align-items: center; gap: 6px; margin-top: 7px;
  font-size: 12.5px; font-weight: 700; color: var(--green); }
.live-pip { width: 8px; height: 8px; border-radius: 50%; background: var(--green);
  box-shadow: 0 0 0 3px rgba(57, 217, 138, 0.18); animation: pulse 1.6s ease-in-out infinite; }
/* The board's corner. Left-aligning it under the chips (4bf2b8c) made the card one
   long left rail of text with nothing holding the right-hand side, which is exactly how
   it read. Right-aligned it balances the chips and the flap cards read as an object. */
.hero-countdown { flex-shrink: 0; margin-left: auto; display: flex; flex-direction: column; align-items: flex-end; gap: 5px; text-align: right; }
.cd-label {
  font-size: 11px; font-weight: 600; letter-spacing: 0.05em;
  color: var(--muted); opacity: 0.75; line-height: 1; transition: color 0.25s;
}
.cd-label.soon { color: var(--amber); opacity: 1; }
.cd-label.now { color: var(--red); opacity: 1; animation: pulse 1.4s ease-in-out infinite; }
.cd-label.gone { color: var(--muted); opacity: 0.9; }
.flapboard.departed { opacity: 0.5; filter: grayscale(0.35); }
/* How far the flapboard can be trusted, directly under it. Ordinary case and weight:
   the colour is carried by the pip, so the line is there when looked for and quiet when
   not. It replaced a status pill up in the chip row, where "Scheduled" — the word that
   says these times are NOT tracked — was one grey pill among grey pills. */
.cd-src {
  font-size: 11px; font-weight: 500; letter-spacing: 0.01em;
  display: inline-flex; align-items: center; gap: 5px; line-height: 1.3; margin-top: 3px;
  text-align: right;
}
/* The words stay --muted in every state and the PIP carries the colour. A glance finds
   it; a tired eye is not dragged to it. It also degrades the right way round: when the
   news is bad the pip is amber or red, which is exactly where the eye should go. */
.cd-src { color: var(--muted); --pip: var(--muted); }
.cd-src.st-ontime { --pip: var(--green); }
.cd-src.st-late   { --pip: var(--amber); }
.cd-src.st-early  { --pip: #5c9be6; }
.cd-src.st-cancel { --pip: var(--red); }
.cd-src.live { color: var(--green); }
.cd-src.est { color: var(--muted); }
/* The pip pulses, and that is the point of it: a still dot says "here is a status", a
   breathing one says "this is being reported right now". A rider has no other cue that the
   number above is live rather than a timetable reading. It is the ONLY thing on the card
   that moves, which is what keeps it a signal rather than noise. */
.cd-pip { width: 6px; height: 6px; border-radius: 50%; background: var(--pip); flex: none;
  animation: pulse 1.8s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) { .cd-pip { animation: none; } }
/* Hollow when the state is worth colouring but not live — a delay carried over from the
   timetable rather than reported by a train. Without this, "Delayed" would be muted words
   with no colour at all, because there is no live pip to carry it. */
/* Hollow AND still. This is the state where a delay is carried over from the timetable
   rather than reported by a train, so pulsing it would claim exactly the liveness the
   pulse exists to signal. One line to change if you would rather it always breathed. */
.cd-pip.hollow { background: transparent; box-shadow: inset 0 0 0 1.5px var(--pip); animation: none; }
.cd-cancel { color: var(--red); font-weight: 700; font-size: 14px; padding-top: 6px; }
.flapboard { display: inline-flex; gap: 5px; align-items: center; }
.flip-colon {
  font-family: var(--board); font-weight: 700; font-size: 24px; color: #9aa0a8; padding: 0 1px;
}
.flip-card {
  position: relative; width: 27px; height: 44px; border-radius: 7px;
  background: #2b2f36; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
  perspective: 200px;
}
.flip-card::after {
  content: ""; position: absolute; left: 3px; right: 3px; top: 50%;
  height: 1px; margin-top: -0.5px; background: rgba(0, 0, 0, 0.28);
  z-index: 4; pointer-events: none;
}
.flip-card > div {
  position: absolute; left: 0; width: 100%; height: 22px; overflow: hidden;
  background-image: linear-gradient(180deg, #3f444d, #33373f);
}
.flip-card > div span {
  display: block; width: 100%; text-align: center;
  font: 800 30px/44px var(--board); font-variant-numeric: tabular-nums;
  color: #f8f6f0; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}
.flip-card .top, .flip-card .flip-top { top: 0; border-radius: 7px 7px 0 0; }
.flip-card .bottom, .flip-card .flip-bottom { bottom: 0; border-radius: 0 0 7px 7px; background-image: linear-gradient(180deg, #2e323a, #23262d); }
.flip-card .bottom span, .flip-card .flip-bottom span { transform: translateY(-22px); }
.flip-card .flip-top { transform-origin: bottom center; backface-visibility: hidden; z-index: 3; }
.flip-card .flip-bottom { transform-origin: top center; backface-visibility: hidden; z-index: 3; transform: rotateX(90deg); }
.flip-card .flip-top, .flip-card .flip-bottom { display: none; }
.flip-card.flipping .flip-top { display: block; animation: flap-top 0.34s ease-in forwards; }
.flip-card.flipping .flip-bottom { display: block; animation: flap-bottom 0.34s ease-out forwards; }
@keyframes flap-top { 0% { transform: rotateX(0deg); } 50%, 100% { transform: rotateX(-90deg); } }
@keyframes flap-bottom { 0%, 50% { transform: rotateX(90deg); } 100% { transform: rotateX(0deg); } }
@media (prefers-reduced-motion: reduce) {
  .flip-card.flipping .flip-top, .flip-card.flipping .flip-bottom { animation: none; display: none; }
}
/* ---- the times, as a grid ------------------------------------------------
   Departure, arrow, arrival. One row on time; when the train is late the scheduled pair
   is struck above the live pair and the columns hold them in line, so the delay reads off
   BOTH ends of the journey without a number naming it.

   It is full width under the head and not beside the countdown, and that is measured, not
   preferred: the block needs 185px in English and 214px in Spanish ("p.m." is four
   characters where "PM" is two), while the space beside a 145px flex-shrink:0 countdown is
   93px at 320 and 163px at 390. Given the whole card it has 252px at the narrowest.

   Nothing here is `nowrap`. The old .times was, which is why an over-wide string did not
   wrap or clip but drew itself straight over the flapboard. */
.times-grid {
  display: grid; grid-template-columns: auto auto auto;
  justify-content: start; column-gap: 12px; row-gap: 1px; margin: 14px 0 0;
  font-family: var(--board); font-variant-numeric: tabular-nums;
}
.times-grid .tg-now { font-size: 22px; font-weight: 600; letter-spacing: -0.01em; }
.times-grid .tg-was {
  font-size: 13px; font-weight: 400; color: var(--muted);
  text-decoration: line-through; text-decoration-thickness: 1px; opacity: 0.75;
}
.times-grid .tg-arrow { color: var(--muted); font-size: 16px; opacity: 0.65; align-self: center; }
.times-grid .tg-d1 { grid-area: 1 / 1; }
.times-grid .tg-a1 { grid-area: 1 / 3; }
.times-grid .tg-d2 { grid-area: 2 / 1; }
.times-grid .tg-a2 { grid-area: 2 / 3; }
.times-grid .tg-one { grid-area: 1 / 2; }
/* Spanning both rows and centred between them: the arrow belongs to the journey, not to
   either pair of times, and on the struck row alone it reads as struck through too. */
.times-grid .tg-span { grid-area: 1 / 2 / 3 / 3; }

.delay { color: var(--amber); font-size: 13px; font-weight: 600; }
.cancelled-tag { color: var(--red); font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; }
.live-dot { color: var(--green); font-size: 11px; }
.live-dot::before { content: "●"; margin-right: 4px; animation: pulse 1.8s ease-in-out infinite; display: inline-block; }

/* `.status-pill` and the bare `.st-*` rules are gone. Those set a tinted BACKGROUND on an
   unscoped class name — safe while the only thing wearing it was the pill, and a live bug
   the moment the status moved under the countdown, which inherited a filled green bubble
   for the fact that nothing is wrong. Reported. `.mini-status` had been quietly picking up
   the same tints. State colour now lives in one place per component. */
.mini-status .pdot { width: 6px; height: 6px; border-radius: 50%; display: inline-block; background: currentColor; }
.mini-status.st-cancel { color: var(--red); }
.mini-status.st-sched { color: var(--muted); }
.mini-status { display: inline-flex; align-items: center; gap: 3px; font-size: 11px; font-weight: 600; }
.mini-status.st-ontime { color: var(--green); }
.mini-status.st-late { color: var(--amber); }
.mini-status.st-early { color: #5c9be6; }
.mini-status .pdot { width: 5px; height: 5px; }
.row .dep .was {
  display: block; font-size: 11px; color: var(--muted);
  text-decoration: line-through; font-weight: 400;
}

.wx-inner {
  font-size: 12.5px; color: var(--muted); cursor: help;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

.pulse { animation: pulse 1.6s ease-in-out infinite; }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.25; } }
@media (prefers-reduced-motion: reduce) { .pulse, .live-dot::before, .cd-label.now, .cd-pip { animation: none; } }

.hint { font-size: 13px; color: var(--express); margin: 0 0 12px; padding: 0 2px; }

/* A flag stop is the one thing on the card that changes what the rider has to DO, so it
   sits on the hero and not in a footnote. Amber, not red: nothing is wrong, but standing
   in the wrong place means the train goes past. */
.flag-note {
  display: flex; flex-direction: column; gap: 4px;
  margin-top: 10px; padding: 9px 11px; border-radius: 10px;
  background: var(--warn-bg); border: 1px solid var(--warn-edge); color: var(--warn-text);
  font-size: 12.5px; line-height: 1.45; font-weight: 600;
}
.tp-flag {
  font-size: 9px; font-weight: 800; letter-spacing: 0.06em; text-transform: uppercase;
  padding: 2px 7px; border-radius: 999px; cursor: help;
  background: var(--warn-bg); color: var(--warn-text); border: 1px solid var(--warn-edge);
}

.datebar { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin: 2px 0 16px; }
.chip-day {
  background: var(--bg); border: 1px solid var(--border); color: var(--muted);
  border-radius: 999px; padding: 6px 14px; font-size: 13px; font-weight: 500; cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.chip-day:hover:not(.on) { color: var(--text); }
.chip-day.on { background: var(--line); border-color: var(--line); color: var(--line-ink); font-weight: 600; }

#social { padding: 0 16px 4px; }
details.social {
  background: var(--surface); border: 1px solid var(--border); border-radius: 14px; overflow: hidden;
}
details.social summary {
  padding: 12px 14px; font-size: 13px; font-weight: 600; cursor: pointer; list-style: none;
}
details.social summary::-webkit-details-marker { display: none; }
details.social summary::after { content: " ▾"; color: var(--muted); font-weight: 400; }
details.social[open] summary::after { content: " ▴"; }
details.social summary a { color: var(--line); text-decoration: none; }
.social-body { padding: 0 12px 12px; }
.social-fallback { margin-top: 8px; }
.social-fallback a { color: var(--line); }

.list { border-top: 1px solid var(--border); }
.list.tt .right { color: var(--muted); }
.list.tt .right .dur { display: block; font-size: 11px; color: var(--muted); margin-top: 2px; }

.list.tt .row.express {
  box-shadow: inset 3px 0 0 var(--express);
  background: linear-gradient(90deg, rgba(240, 166, 60, 0.12), rgba(240, 166, 60, 0));
  padding-left: 10px;
}
.list.tt .row.express .dep { color: var(--express); }
.list.tt .row.express .meta .chip.E { margin-right: 4px; }
.row {
  display: grid; grid-template-columns: 82px 1fr auto; gap: 10px; align-items: center;
  padding: 12px 4px; border-bottom: 1px solid var(--border);
  font-family: var(--board); font-variant-numeric: tabular-nums; font-size: 14px;
}
.row .dep { font-weight: 600; }
.row .meta {
  font-size: 12px; color: var(--muted); font-family: -apple-system, "Segoe UI", Roboto, sans-serif;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.row .right { text-align: right; font-size: 13px; color: var(--green); }
.row .right .delay { display: block; }
.row .right .jt { display: inline-flex; align-items: center; gap: 4px; color: var(--muted); font-weight: 500; }
.jt-ico { opacity: 0.85; flex-shrink: 0; }
.row.cancelled .dep, .row.cancelled .meta { text-decoration: line-through; }
.row.cancelled .right { color: var(--red); }

footer {
  padding: 10px 16px calc(14px + env(safe-area-inset-bottom));
  font-size: 11px; color: var(--muted); display: flex; justify-content: space-between;
  border-top: 1px solid var(--border);
}

.setup-header { padding-bottom: 16px; position: static; }
.sec, .prefs section { scroll-margin-top: 12px; }
.setup-header h1 { padding-top: 14px; }
.setup-header p { padding: 4px 16px 0; }
#view-setup main { padding: 20px 16px; flex: 1; }
#view-setup h2 { font-size: 15px; font-weight: 650; letter-spacing: -0.01em; margin: 0 0 10px; }
.card {
  background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
  padding: 16px; box-shadow: var(--shadow-sm);
}

.brand { display: flex; align-items: center; gap: 10px; padding: 14px 16px 0; }
.brand-mark { border-radius: 7px; box-shadow: var(--shadow-sm); }
.brand h1 { padding: 0; }

.sec { margin-bottom: 28px; }
.sec > .muted, .prefs section > .muted { margin-bottom: 12px; }
.sec > * { transition: opacity 0.35s ease; }
#add-route { margin-top: 2px; }

.prefs { margin-top: 4px; padding-top: 22px; border-top: 1px solid var(--border); }
.prefs section { margin-bottom: 24px; }
.prefs section > .muted { margin-bottom: 12px; }
.tag {
  font-size: 10px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--muted); background: var(--bg); border: 1px solid var(--border);
  border-radius: 999px; padding: 2px 8px; vertical-align: middle; margin-left: 6px;
}
.save-note { text-align: center; margin: 8px 0 0; }
label { display: block; font-size: 13px; color: var(--muted); margin-bottom: 14px; }
label input:not([type=checkbox]):not([type=radio]):not(.cs-native),
label select:not(.cs-native) {
  display: block; width: 100%; margin-top: 5px; min-height: 50px; padding: 13px 12px; font-size: 16px;
  background: var(--bg); color: var(--text); border: 1px solid var(--border); border-radius: 10px;
}
label.check { display: flex; gap: 10px; align-items: flex-start; color: var(--text); font-size: 14px; }
label.check input { width: auto; margin-top: 3px; }

.cs { position: relative; display: block; }
.cs-native { display: none; }
label .cs { margin-top: 5px; }
.cs-btn {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  width: 100%; text-align: left; cursor: pointer;
  background: var(--bg); color: var(--text);
  border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px; font-size: 15px;
}
.cs-btn .cs-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cs-btn .cs-placeholder { color: var(--muted); }
.cs-btn .cs-caret { color: var(--muted); flex-shrink: 0; font-size: 12px; }
.cs-panel {
  position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 60;
  max-height: 300px; overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18); padding: 4px;
}
/* Opened upwards when there's more room above the button than below — enhanceSelect
   decides, and also caps max-height, so the panel is always fully on screen. */
.cs-panel.cs-above { top: auto; bottom: calc(100% + 4px); }
.cs-opt {
  padding: 12px; border-radius: 8px; font-size: 15px; cursor: pointer;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cs-opt:hover { background: var(--bg); }
.cs-opt.sel { background: var(--line); color: var(--line-ink); }
.cs-opt.cs-empty { color: var(--muted); cursor: default; }

.header-row .cs { flex: 1 1 auto; min-width: 0; }
.header-row .cs-btn { height: 36px; padding: 0 10px; font-size: 13px; font-weight: 500; }

.cs-time { display: flex; align-items: center; gap: 6px; margin-top: 5px; }
.cs-time .cs { flex: 1 1 0; min-width: 0; margin-top: 0; }
.cs-time-colon { color: var(--muted); font-weight: 700; }

.datebar .cs { margin-left: auto; flex: 0 0 auto; }
.datebar .cs-btn { width: auto; padding: 6px 12px; font-size: 13px; }
.datebar .cs .cs-panel {
  left: auto; right: 0; width: max-content; min-width: 100%;
  max-width: min(300px, calc(100vw - 32px));
}
button.primary {
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--line); color: var(--line-ink); border: none; border-radius: 11px;
  min-height: 52px; padding: 14px 24px; font-size: 15px; font-weight: 600; cursor: pointer;
  transition: transform 0.1s, filter 0.15s;
}
button.primary:hover { filter: brightness(1.08); }
button.primary:active { transform: scale(0.98); }
button.ghost {
  background: transparent; color: var(--muted); border: 1px solid var(--border);
  border-radius: 9px; padding: 9px 14px; font-size: 12px; cursor: pointer;
  transition: border-color 0.15s, background 0.15s, color 0.15s;
}
button.ghost:active { transform: scale(0.98); }
button.ghost.danger { color: var(--red); }
button.ghost.danger.confirm { background: #C0392B; border-color: #C0392B; color: #fff; }
button.wide { width: 100%; }
button.ghost.wide { display: flex; align-items: center; justify-content: center; width: 100%; min-height: 52px; padding: 15px; font-size: 15px; font-weight: 500; color: var(--text); }
button.ghost.wide:hover { border-color: var(--line); }
button.ghost.wide.danger { color: var(--red); }
button.ghost.wide.danger:hover { border-color: var(--red); }
button.ghost.wide.danger.confirm { background: #C0392B; border-color: #C0392B; color: #fff; }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0 14px; }
.brief-fields { display: grid; grid-template-columns: 1fr; }
.actions { margin: 26px 0 30px; display: flex; align-items: center; gap: 12px; }
.route-item {
  display: flex; align-items: center; gap: 10px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
  padding: 14px; min-height: 56px; margin-bottom: 8px;
}
.route-item.active { border-color: var(--line); }
.route-item .ri-dot { width: 10px; height: 10px; border-radius: 3px; flex-shrink: 0; box-shadow: 0 0 0 1px var(--text); }
.route-item .ri-main { flex: 1; min-width: 0; cursor: pointer; }
.route-item .name { font-weight: 600; font-size: 14px; }
.route-item .sub { font-size: 12px; color: var(--muted); }
.route-item .ri-go { color: var(--line); font-size: 22px; line-height: 1; cursor: pointer; padding: 0 2px; }

.setup-top { display: flex; justify-content: flex-end; padding: 10px 16px 0; }
#setup-done { font-weight: 600; }

.sec.locked { pointer-events: none; }
.sec.locked > *:not(.lock-hint):not(h2) { opacity: 0.45; }
.sec.locked > h2 { opacity: 0.7; }
.lock-hint {
  display: none; color: var(--muted); margin: 0 0 12px;
  padding-left: 10px; border-left: 2px solid var(--border);
}
.sec.locked .lock-hint { display: block; }
.ok { display: flex; align-items: center; gap: 6px; color: var(--green); font-weight: 600; margin-top: 10px; }
.ok::before {
  content: ""; width: 15px; height: 15px; flex-shrink: 0; background: var(--green);
  -webkit-mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.42z"/></svg>') center/contain no-repeat;
  mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.42z"/></svg>') center/contain no-repeat;
}
.just-unlocked .card { animation: unlockring 1.4s ease-out; }
@keyframes unlockring {
  0% { box-shadow: 0 0 0 3px rgba(29, 158, 117, 0.5); }
  100% { box-shadow: var(--shadow-sm); }
}
@media (prefers-reduced-motion: reduce) {
  .sec > *, .just-unlocked .card { transition: none; animation: none; }
}

.tab-live {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  background: var(--green); margin-right: 6px; vertical-align: middle;
  animation: pulse 1.8s ease-in-out infinite;
}
/* The tab fills with the line colour when active, so the "live" dot has to be readable
   ON it — white vanished completely on UP-NW's yellow, which is how this was found. */
.seg button.on .tab-live { background: var(--line-ink); }

#view-wizard {
  --bg: #0A0F15; --surface: #0E151E; --text: #E9EDF2;
  --muted: #7E8A97; --border: #223041;
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5); --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.6);
  --map-bg: #0A0F15; --map-bg-edge: #17222E; --map-water: #123049; --map-water-edge: #2A5074;
  --map-ink: #FFFFFF; --map-ink-halo: #05090E; --map-term: #AEBAC7;
  background: var(--bg); color: var(--text);
}
.wiz-top { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px 0; }
.wiz-spacer { width: 36px; }
.wiz-brand { display: flex; align-items: center; gap: 8px; min-width: 0; }
.wiz-brand img { border-radius: 6px; display: block; }
.wiz-brand span { font-size: 14px; font-weight: 650; letter-spacing: -0.01em; }
.wiz-dots { display: flex; gap: 7px; justify-content: center; padding: 8px 0 6px; }
.wiz-dots span {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--border); transition: background 0.25s;
}
.wiz-dots span.on { background: var(--line-edge); }
.wiz-header { padding-bottom: 0; }
/* An <h2>, not an <h1>: the page's heading belongs to #site-info, so a crawl
   doesn't come away thinking this page is about choosing a language. */
.wiz-header #wiz-title { padding: 8px 16px 0; font-size: 21px; font-weight: 680; letter-spacing: -0.02em; text-align: center; }
.wiz-header p { padding: 3px 16px 0; font-size: 13px; text-align: center; }
#wiz-body { flex: 1; padding-bottom: 24px; }
#wiz-filter {
  display: block; width: calc(100% - 32px); margin: 12px 16px 0; padding: 10px 12px;
  font-size: 15px; background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: 10px;
}
.wiz-list { padding: 12px 16px 0; display: grid; grid-template-columns: 1fr; gap: 8px; }
@media (min-width: 400px) { .wiz-list { grid-template-columns: 1fr 1fr; } }
.wiz-list .center { grid-column: 1 / -1; }
.wiz-row {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  background: var(--surface); color: var(--text); font: inherit;
  border: 1px solid var(--border); border-radius: 14px; box-shadow: var(--shadow-sm);
  padding: 10px 12px; min-height: 54px; cursor: pointer;
  transition: border-color 0.15s, transform 0.1s;
}
.wiz-row:hover { border-color: var(--line); }
.wiz-row.hl { background: rgba(255, 255, 255, 0.03); transform: translateY(-1px); }
.wiz-row:active { transform: scale(0.985); }
.wiz-bar { width: 6px; align-self: stretch; border-radius: 3px; flex-shrink: 0; }
.wiz-txt { display: flex; flex-direction: column; min-width: 0; }
.wiz-row .t { font-weight: 600; font-size: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wiz-row .s { font-size: 12px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wiz-go { margin-left: auto; color: var(--muted); font-size: 18px; line-height: 1; flex-shrink: 0; }
@media (prefers-reduced-motion: reduce) { .wiz-row:active { transform: none; } }

.wiz-saved, .wiz-section-label, .wiz-listbar, .wiz-nomatch { grid-column: 1 / -1; }
.wiz-nomatch { padding: 2px 2px 4px; }
.wiz-saved { display: flex; flex-direction: column; gap: 8px; }
.wiz-section-label { font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); margin: 6px 2px 0; }
.wiz-add-label { margin-top: 14px; }
.wiz-row.saved { border-color: var(--line); }
.wiz-row.saved .wiz-bar { width: 6px; }
.wiz-reset { width: 100%; margin-top: 4px; padding: 9px; font-size: 13px; }
.wiz-reset.confirm { background: #C0392B; border-color: #C0392B; color: #fff; }
.wiz-suggest { grid-column: 1 / -1; display: flex; align-items: center; gap: 12px;
  background: var(--surface); border: 1px solid var(--line); border-radius: 14px; padding: 12px 14px; box-shadow: var(--shadow-sm); }
.wiz-suggest-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.wiz-suggest-name { font-weight: 700; font-size: 15px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wiz-suggest .primary { flex-shrink: 0; }
.wiz-row.suggested { border-color: var(--line); }
.wiz-listbar { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin: 2px 2px 2px; }
.wiz-rev { border: 1px solid var(--border); background: var(--surface); color: var(--muted); border-radius: 8px; padding: 5px 11px; font-size: 12px; font-weight: 600; cursor: pointer; }
.wiz-rev:hover { border-color: var(--line); color: var(--text); }

.wiz-map { margin: 10px 10px 0; border-radius: 16px; overflow: hidden;
  background: var(--map-bg); border: 1px solid var(--map-bg-edge); position: relative; }
.lmap-load { min-height: 260px; display: flex; align-items: center; justify-content: center;
  color: var(--muted); font-size: 13px; text-align: center; padding: 24px; }
.lmap { width: 100%; height: auto; display: block; max-height: 66vh; }
.lmap-cam { transform-origin: 0 0; transition: transform 1.4s cubic-bezier(0.22, 1, 0.32, 1); will-change: transform; }
.lmap-lake { fill: var(--map-water); stroke: var(--map-water-edge); stroke-width: 1.4; stroke-opacity: 0.7; }
.lmap-line .lmap-hit { fill: none; stroke: #000; stroke-opacity: 0; stroke-width: 15; cursor: pointer; }
.lmap-line .lmap-glow { fill: none; stroke: var(--c); stroke-opacity: 0.2; stroke-width: 8.5; stroke-linejoin: round; stroke-linecap: round; pointer-events: none; }
.lmap-line .lmap-core { fill: none; stroke: var(--c); stroke-width: 2.8; stroke-linejoin: round; stroke-linecap: round; pointer-events: none; transition: stroke-width 0.4s ease; }
.lmap-line .lmap-stn { fill: var(--map-bg); stroke: var(--c); stroke-width: 1.2; pointer-events: none; transition: r 0.35s cubic-bezier(0.16, 1, 0.3, 1); }
.lmap-line .lmap-lbl { display: none; fill: var(--map-ink); font-weight: 700; font-family: "Overpass", sans-serif;
  font-size: calc(16px * var(--px, 1) * var(--inv, 1)); paint-order: stroke; stroke: var(--map-ink-halo); stroke-width: calc(4px * var(--px, 1) * var(--inv, 1)); pointer-events: none; }
.lmap-line .lmap-term { fill: var(--map-term); font-weight: 700; font-family: "Overpass", sans-serif;
  font-size: calc(15px * var(--px, 1) * var(--inv, 1)); paint-order: stroke; stroke: var(--map-ink-halo); stroke-width: calc(3.5px * var(--px, 1) * var(--inv, 1)); pointer-events: none; }
.lmap-line.hovering .lmap-core { stroke-width: 4.6; stroke-dasharray: 16 12; animation: lmapFlow 1.4s linear infinite; }
.lmap-line.hovering .lmap-glow { stroke-opacity: 0.6; stroke-width: 12; }
@keyframes lmapFlow { from { stroke-dashoffset: 0; } to { stroke-dashoffset: -28; } }

.lmap-line.on .lmap-lbl { display: block; }
.lmap-lake-lbl {
  fill: var(--map-term); opacity: 0.4; font-family: "Overpass", sans-serif; font-weight: 700;
  font-size: calc(13px * var(--px, 1)); letter-spacing: 0.16em; pointer-events: none; text-transform: uppercase;
}
.lmap-compass {
  display: none;
  position: absolute; top: 12px; left: 12px; width: 38px; height: 38px; z-index: 5;
  opacity: 0.55; pointer-events: none; color: var(--map-term);
  transition: transform 1.4s cubic-bezier(0.22, 1, 0.32, 1);
}
.lmap-compass svg { width: 100%; height: 100%; display: block; }
.lmap-compass .lc-ring { fill: none; stroke: currentColor; stroke-width: 1.4; opacity: 0.5; }
.lmap-compass .lc-needle { fill: var(--red); }
.lmap-compass .lc-s { fill: currentColor; opacity: 0.6; }
.lmap-compass .lc-n { fill: currentColor; font: 700 11px "Overpass", sans-serif; }
@media (prefers-reduced-motion: reduce) { .lmap-compass { transition: none; } }
.lmap:not(.focus) .lmap-line.hovering .lmap-lbl:not(.culled) { display: block; }
.lmap-line .lmap-lbl.culled { display: none; }
.lmap.focus .lmap-line.on .lmap-stn:hover + .lmap-lbl { display: block; }
.lmap-line.on .lmap-term { display: none; }
.lmap-line.hovering .lmap-term { display: none; }

.lmap.focus .lmap-line { opacity: 0.06; transition: opacity 0.6s ease; }
.lmap.focus .lmap-line.on { opacity: 1; }
.lmap.focus .lmap-line.on .lmap-core { stroke-width: 4; }
.lmap.focus .lmap-line.on .lmap-glow { stroke-opacity: 0.4; stroke-width: 12; }
.lmap.focus .lmap-line.on .lmap-stn { r: 3.4; cursor: pointer; pointer-events: auto; }
.lmap.focus .lmap-line.on .lmap-stn.endpoint:not(.from):not(.to) { r: 5; fill: var(--c); stroke: var(--map-bg); stroke-width: 1.6; }
.lmap.focus .lmap-line.on .lmap-lbl { pointer-events: auto; cursor: pointer; }
.lmap-line .lmap-stn.from { fill: var(--c); stroke: var(--map-ink); stroke-width: 2; r: 4.5; }
.lmap-line .lmap-stn.to { fill: var(--map-ink); stroke: var(--c); stroke-width: 2.5; r: 4.5; }
.lmap.focus .lmap-line.on .lmap-stn.from,
.lmap.focus .lmap-line.on .lmap-stn.to { animation: pinPulse 1.9s ease-in-out infinite; }
.lmap.focus .lmap-line.on .lmap-stn.from { animation-delay: 0.35s; }
@keyframes pinPulse { 0%, 100% { r: 4.5; } 50% { r: 6.4; } }
.lmap-line .lmap-lbl.pick { display: block; fill: var(--map-ink); font-weight: 800; }
#wiz-map .lmap.focus .lmap-line.on .lmap-stn.from {
  fill: var(--c); stroke: #fff; stroke-width: 4; filter: drop-shadow(0 0 5px var(--c));
  animation: wizHomePulse 1.8s ease-in-out infinite;
}
@keyframes wizHomePulse { 0%, 100% { r: 6.5; } 50% { r: 8.8; } }
#wiz-map .lmap.focus .lmap-line.on .lmap-lbl.pick { font-weight: 800; paint-order: stroke; }
#wiz-map .lmap-stn.just-picked { animation: wizPop 0.5s ease-out; }
@keyframes wizPop { 0% { r: 11; } 60% { r: 5.4; } 100% { r: 6.4; } }
.wiz-toast {
  position: fixed; inset: 0; z-index: 200;
  display: flex; align-items: center; justify-content: center;
  background: rgba(8, 14, 22, 0.30);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  opacity: 0; pointer-events: none; transition: opacity 0.3s ease;
}
.wiz-toast.show { opacity: 1; }
.wiz-toast-pill {
  background: var(--line); color: var(--line-ink, #fff);
  padding: 15px 26px; border-radius: 18px; text-align: center;
  font-size: 17px; font-weight: 700; letter-spacing: -0.01em; max-width: min(82vw, 340px);
  box-shadow: 0 22px 60px rgba(0, 0, 0, 0.5); border: 1px solid rgba(255, 255, 255, 0.16);
  transform: scale(0.84); transition: transform 0.36s cubic-bezier(0.22, 1, 0.36, 1);
}
.wiz-toast.show .wiz-toast-pill { transform: scale(1); }
@media (prefers-reduced-motion: reduce) {
  #wiz-map .lmap-stn.just-picked,
  #wiz-map .lmap.focus .lmap-line.on .lmap-stn.from { animation: none; }
  .wiz-toast-pill { transition: none; transform: none; }
}
.lmap-hub { fill: var(--map-bg); stroke: var(--map-ink); stroke-width: 2.2; pointer-events: none; }
.lmap.focus .lmap-hub, .lmap.focus .lmap-hub-lbl { display: none; }
.lmap-hub-lbl { fill: var(--map-ink); font-weight: 800; font-family: "Overpass", sans-serif; font-size: calc(13.5px * var(--px, 1) * var(--inv, 1)); paint-order: stroke; stroke: var(--map-ink-halo); stroke-width: calc(3px * var(--px, 1)); letter-spacing: 0.10em; }
.lmap-train-halo { fill: #2FD53B; opacity: 0.25; animation: lmapPulse 1.6s ease-in-out infinite; }
.lmap-train { fill: var(--map-ink); stroke: #2FD53B; stroke-width: 2.4; }
@keyframes lmapPulse { 0%, 100% { opacity: 0.25; } 50% { opacity: 0.07; } }

.lmap.focus { touch-action: none; cursor: grab; }
.lmap.focus:active { cursor: grabbing; }
.lmap-ctrls { display: none; position: absolute; right: 10px; bottom: 10px; z-index: 5; flex-direction: column; gap: 6px; }
.lmap-focused .lmap-ctrls { display: flex; }
.lmap-ctrls button {
  width: 44px; height: 44px; border-radius: 10px; border: 1px solid var(--border);
  background: var(--surface); backdrop-filter: blur(6px); color: var(--text);
  font-size: 19px; font-weight: 700; line-height: 1; cursor: pointer;
}
.lmap-ctrls button:hover { border-color: var(--line); color: var(--line); }
.lmap-ctrls button:active { transform: scale(0.94); }
.lmap-hint {
  display: none; position: absolute; left: 10px; bottom: 12px; z-index: 5; pointer-events: none;
  font-size: 11px; color: var(--map-term); background: var(--surface); border: 1px solid var(--map-bg-edge); padding: 4px 10px; border-radius: 999px;
}
.lmap-caption {
  display: none; position: absolute; left: 10px; top: 10px; z-index: 6; pointer-events: none;
  align-items: center; gap: 8px; max-width: calc(100% - 20px);
  padding: 6px 12px 6px 10px; border-radius: 999px;
  background: var(--surface); border: 1px solid var(--map-bg-edge); box-shadow: var(--shadow-sm);
  font-size: 12px; white-space: nowrap; overflow: hidden;
}
.lmap-caption.show { display: inline-flex; }
.lmap-cap-dot { width: 11px; height: 11px; border-radius: 50%; flex-shrink: 0; }
.lmap-cap-name { font-weight: 700; color: var(--text); flex-shrink: 0; }
.lmap-cap-route { color: var(--map-term); min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.lmap-focused .lmap-hint { display: block; }
@media (prefers-reduced-motion: reduce) {
  .lmap-train-halo, .lmap-line.hovering .lmap-core,
  .lmap.focus .lmap-line.on .lmap-stn.from, .lmap.focus .lmap-line.on .lmap-stn.to { animation: none; }
  .lmap.focus .lmap-line, .lmap-cam, .lmap-line .lmap-stn, .lmap-line .lmap-core { transition: none; }
  .view { animation: none; }
}

.feat {
  grid-column: 1 / -1; display: flex; gap: 12px; align-items: flex-start;
  background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
  padding: 14px; box-shadow: var(--shadow-sm);
}
.feat svg { flex-shrink: 0; width: 22px; height: 22px; color: var(--line); margin-top: 1px; }
.feat > div { flex: 1; min-width: 0; }
.feat b { display: block; font-size: 14px; }
.feat > div > span { display: block; font-size: 13px; color: var(--muted); margin-top: 2px; }
.feat label.check { margin: 10px 0 0; font-size: 13px; }
.feat .feat-cfg { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border); }
.feat .feat-cfg label { margin-bottom: 10px; }
.sched-hint { display: block; margin-top: 4px; font-size: 11px; color: var(--muted); }
.sched-hint:empty { display: none; }
.center-note { grid-column: 1 / -1; text-align: center; margin-top: 4px; }
.feat-actions { grid-column: 1 / -1; display: flex; gap: 10px; margin-top: 6px; }
.feat-actions .primary { flex: 1; }

#nudge:not(:empty) { padding: 12px 16px 0; }
.nudge {
  background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
  padding: 14px; box-shadow: var(--shadow-sm);
  display: flex; flex-direction: column; gap: 12px;
}
.nudge b { font-size: 14px; }
.nudge-txt span { display: block; font-size: 13px; color: var(--muted); margin-top: 3px; }
.nudge-btns { display: flex; gap: 8px; }
.nudge-btns .primary { padding: 9px 16px; font-size: 13px; }
.nudge-btns .ghost { padding: 9px 14px; font-size: 13px; }

.sheet-backdrop {
  position: fixed; inset: 0; z-index: 100;
  background: rgba(16, 24, 40, 0.48);
  display: flex; align-items: flex-end; justify-content: center;
}
.sheet {
  background: var(--surface); width: 100%; max-width: 520px;
  border-radius: 18px 18px 0 0; box-shadow: var(--shadow-md);
  max-height: 88dvh; overflow-y: auto; -webkit-overflow-scrolling: touch;
  padding: 18px 16px calc(20px + env(safe-area-inset-bottom));
  animation: sheet-in 0.22s ease;
}
@media (min-width: 560px) {
  .sheet-backdrop { align-items: center; padding: 20px; }
  .sheet { border-radius: 18px; }
}
.sheet-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 2px; }
.sheet-head h2 { font-size: 17px; font-weight: 650; letter-spacing: -0.01em; }
.sheet > .muted { margin-bottom: 14px; }
html.sheet-open { overflow: hidden; }
.sheet.cs-open { overflow: visible; }

.install-steps { margin: 10px 0 4px; padding-left: 20px; display: flex; flex-direction: column; gap: 9px; }
.install-steps li { font-size: 14px; line-height: 1.5; }
.install-steps .ig { display: inline-flex; vertical-align: -3px; }
.install-steps .ig svg { width: 15px; height: 15px; }
.install-banner {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(12px + env(safe-area-inset-bottom));
  width: calc(100% - 24px); max-width: 496px; z-index: 90;
  display: flex; align-items: center; gap: 10px; justify-content: space-between;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: 14px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.28); padding: 11px 12px 11px 14px;
}
.install-banner .ib-txt { font-size: 13px; line-height: 1.35; }
.install-banner .ib-btns { display: flex; align-items: center; gap: 4px; flex-shrink: 0; }
.install-banner .ib-how {
  background: var(--line); color: var(--line-ink); border: none; border-radius: 9px;
  padding: 8px 14px; font-size: 13px; font-weight: 600; cursor: pointer;
}
.install-banner .ib-x {
  background: transparent; color: var(--muted); border: none;
  font-size: 15px; cursor: pointer; padding: 6px 8px; line-height: 1;
}
@keyframes sheet-in { from { transform: translateY(20px); opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .sheet { animation: none; }
  .icon-btn:active, button.primary:active, button.ghost:active, .tp-refresh:active { transform: none; }
}

.site-foot { margin-top: 6px; }
button.built-by {
  display: block; width: 100%; margin: 0; padding: 14px 16px 0;
  background: none; border: 0; cursor: pointer; font: inherit;
  font-size: 12px; color: var(--muted); text-align: center;
}
button.built-by .bb-em { color: var(--line); font-weight: 500; }
button.built-by:hover .bb-em { text-decoration: underline; }
button.built-by:focus-visible { outline: 2px solid var(--line); outline-offset: 2px; border-radius: 6px; }

.about-by { padding: 4px 0 0; font-size: 13px; color: var(--muted); text-align: center; }
.about-by a { color: var(--line); text-decoration: none; font-weight: 500; }
.about-by a:hover { text-decoration: underline; }
.about-credit { text-align: center; font-size: 10px; color: var(--muted); opacity: 0.6; margin-top: 6px; }
.visits { margin: 16px 0 6px; text-align: center; position: relative; }
.visits-label {
  font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--muted); margin-bottom: 12px;
}
.visits-head { display: flex; align-items: center; justify-content: center; gap: 5px; margin-bottom: 12px; }
.visits-head .visits-label { margin-bottom: 0; }
.visits-info {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; padding: 0; border: none; border-radius: 50%;
  background: transparent; color: var(--muted); cursor: pointer; opacity: 0.85;
  -webkit-tap-highlight-color: transparent; transition: color 0.15s, opacity 0.15s;
}
.visits-info:hover, .visits-info[aria-expanded="true"] { opacity: 1; color: var(--text); }
.visits-info svg { width: 16px; height: 16px; display: block; }
.visits-tip {
  position: absolute; left: 50%; top: 30px; transform: translateX(-50%);
  z-index: 30; width: min(300px, calc(100vw - 56px)); text-align: left;
  background: var(--bg); border: 1px solid var(--border); border-radius: 12px;
  padding: 12px 14px; box-shadow: 0 14px 36px rgba(0, 0, 0, 0.45);
}
.visits-tip::before {
  content: ""; position: absolute; top: -6px; left: 50%; transform: translateX(-50%) rotate(45deg);
  width: 11px; height: 11px; background: var(--bg);
  border-left: 1px solid var(--border); border-top: 1px solid var(--border);
}
.visits-tip p { margin: 0 0 8px; font-size: 12px; line-height: 1.5; color: var(--muted); }
.visits-tip p:last-child { margin-bottom: 0; }
.visits-tip b { color: var(--text); font-weight: 600; }
.visits-flap { justify-content: center; }
.visits-spark { display: block; width: 160px; height: 34px; margin: 16px auto 4px; overflow: visible; }
.visits-total { margin-top: 10px; }
.visits-total b { color: var(--text); font-variant-numeric: tabular-nums; font-weight: 700; }

/* ---- owner dashboard ----
   The rider views are phone-shaped on purpose; the dashboard is a desktop tool,
   so `body.dash-mode` releases #app's 520px cap and everything below lays out to
   the viewport (capped at --dash-max so text lines stay readable on ultrawides). */
body.dash-mode #app { max-width: none; }
#view-dashboard { background: var(--bg); --dash-max: 1500px; }
.dash-header { padding: 14px 16px 10px; border-bottom: 1px solid var(--border); position: sticky; top: 0; background: var(--bg); z-index: 5; }
.dash-header > * { max-width: var(--dash-max); margin-left: auto; margin-right: auto; }
.dash-top { display: flex; align-items: center; gap: 10px; }
.dash-top h1 { flex: 1; font-size: 20px; font-weight: 650; padding: 0; letter-spacing: -0.01em; }
#dash-updated { margin-top: 6px; }
#dash-body { padding: 18px 16px 40px; flex: 1; overflow-y: auto; }
.dash-wrap { max-width: var(--dash-max); margin: 0 auto; }
.dash-gate { max-width: 340px; margin: 44px auto; text-align: center; background: var(--surface); border: 1px solid var(--border); border-radius: 16px; padding: 22px; }
.dash-gate h2 { font-size: 17px; margin-bottom: 6px; }
.dash-gate input { margin: 14px 0 12px; text-align: center; }

.dash-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 12px; }
.dash-stat { background: var(--surface); border: 1px solid var(--border); border-radius: 14px; padding: 14px 16px; }
.dash-stat .num { font-size: 26px; font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: -0.02em; }
.dash-stat .lbl { font-size: 12.5px; font-weight: 600; margin-top: 2px; }
.dash-stat .def { font-size: 11px; line-height: 1.4; color: var(--muted); margin-top: 5px; }

/* Masonry via multi-column: these cards are independent and wildly different
   heights, so a row-based grid strands big empty gaps under the short ones.
   Column flow packs them tight. `inline-block` + width:100% is what actually
   makes break-inside stick across engines. */
.dash-grid { columns: 380px 3; column-gap: 16px; margin-top: 16px; }
.dash-sec {
  background: var(--surface); border: 1px solid var(--border); border-radius: 16px;
  padding: 16px 18px 14px; min-width: 0;
  break-inside: avoid; -webkit-column-break-inside: avoid;
  display: inline-block; width: 100%; margin: 0 0 16px; vertical-align: top;
}
.dash-sec.wide { display: block; }
.dash-stats + .dash-sec.wide { margin-top: 16px; }
.dash-sec.wide + .dash-sec.wide { margin-top: 16px; }
.dash-sec.wide + .dash-grid { margin-top: 0; }
.dash-sec > h2 { font-size: 14px; font-weight: 650; }
.dash-note { font-size: 11.5px; line-height: 1.5; color: var(--muted); margin: 5px 0 12px; }
.dash-sec > h2 + .dt { margin-top: 12px; }
.dash-foot { font-size: 11px; line-height: 1.6; color: var(--muted); margin: 22px 0 0; max-width: 76ch; }
@media (min-width: 1100px) { #dash-body { padding: 22px 24px 48px; } }

.dash-chip { width: 10px; height: 10px; border-radius: 3px; flex: none; }
.dash-ell { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; min-width: 0; }

/* data tables: filter / sort / paginate */
.dt-tools { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.dt-search {
  flex: 1; min-width: 0; margin: 0; height: 34px; padding: 0 11px; font-size: 13px;
  background: var(--bg); color: var(--text); border: 1px solid var(--border); border-radius: 9px;
}
.dt-search:focus { outline: none; border-color: var(--line); }
.dt-meta { font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
.dt-scroll { overflow-x: auto; }
.dt-table { width: 100%; border-collapse: collapse; font-size: 13px; table-layout: fixed; }
.dt-table th, .dt-table td { padding: 8px 10px; text-align: left; border-bottom: 1px solid var(--border); }
.dt-table td { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dt-table th {
  font-size: 11px; font-weight: 650; letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--muted); white-space: nowrap; user-select: none;
}
.dt-table th:first-child, .dt-table td:first-child { padding-left: 0; }
.dt-table th:last-child, .dt-table td:last-child { padding-right: 0; }
.dt-table tbody tr:last-child td { border-bottom: none; }
.dt-table tbody tr:hover td { background: var(--bg); }
.dt-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.dt-table td.num { font-weight: 650; }
.dt-rank { width: 1%; color: var(--muted); font-variant-numeric: tabular-nums; font-size: 11.5px; }
.dt-sortable { cursor: pointer; }
.dt-sortable:hover, .dt-sortable.on { color: var(--text); }
.dt-sortable:focus-visible { outline: 2px solid var(--line); outline-offset: -2px; border-radius: 4px; }
.dt-arrow { margin-left: 4px; font-size: 9px; }
.dt-arrow.dim { opacity: 0.3; }
.dt-none td { color: var(--muted); padding: 18px 0; text-align: center; }
.dt-table td > .dash-chip { display: inline-block; vertical-align: middle; margin-right: 7px; }
.dt-table td > .dash-ell { display: inline-block; vertical-align: middle; max-width: calc(100% - 18px); }
/* the flex row lives in a wrapper, not on the <td> — a flex table cell drops out
   of the row's height/baseline calculation and the row visibly misaligns */
.dt-sharewrap { display: flex; align-items: center; gap: 8px; }
.dt-bar { flex: 1; height: 6px; background: var(--border); border-radius: 999px; overflow: hidden; min-width: 40px; }
.dt-bar-fill { display: block; height: 100%; border-radius: 999px; }
.dt-pct { font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; width: 34px; text-align: right; }
.dt-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; margin-top: 11px; }
.dt-pager { display: flex; align-items: center; gap: 6px; }
.dt-pg {
  width: 28px; height: 28px; border: 1px solid var(--border); background: var(--bg); color: var(--text);
  border-radius: 8px; cursor: pointer; font-size: 15px; line-height: 1; padding: 0;
}
.dt-pg:hover:not(:disabled) { border-color: var(--line); }
.dt-pg:disabled { opacity: 0.35; cursor: default; }
.dt-sizes { display: flex; gap: 4px; }
.dt-size {
  min-width: 30px; height: 28px; padding: 0 7px; font-size: 11.5px; cursor: pointer;
  border: 1px solid var(--border); background: var(--bg); color: var(--muted); border-radius: 8px;
}
.dt-size:hover { border-color: var(--line); }
.dt-size.on { background: var(--line); border-color: var(--line); color: var(--line-ink); font-weight: 650; }
@media (max-width: 640px) {
  .dt-table th, .dt-table td { padding: 8px 6px; }
  .dt-foot { justify-content: space-between; }
}

.dash-hours { display: flex; align-items: flex-end; gap: 3px; height: 120px; padding-top: 4px; }
.dash-hbar { flex: 1; display: flex; align-items: flex-end; height: 100%; }
.dash-hbar-fill { width: 100%; background: var(--line-edge); border-radius: 3px 3px 0 0; min-height: 2px; }
.dash-hours-axis { display: flex; justify-content: space-between; font-size: 10px; color: var(--muted); margin-top: 6px; }
@media (min-width: 1100px) { .dash-hours { height: 170px; gap: 5px; } }

/* ---- dashboard: the day view ---- */
.dash-day-head { display: flex; align-items: center; gap: 10px; margin: 0 0 10px; font-weight: 650; }
.dash-day-head .ghost { padding: 4px 12px; font-size: 16px; line-height: 1; }
/* Matches .dt-pg:disabled, the dashboard's existing "there is no further page" look. */
.dash-day-head .ghost:disabled { opacity: 0.35; cursor: default; }
.dash-day-head .ghost:not(:disabled):hover { border-color: var(--line-edge); }
/* The trend strip doubles as the picker, so the bars are real buttons: a bar you can
   click has to be reachable by keyboard too, and a title alone is not a control. */
.dash-days { display: flex; align-items: flex-end; justify-content: flex-start; gap: 2px; height: 90px; padding-top: 4px; }
.dash-dbar {
  /* Capped, or a day or two of history renders as one enormous slab rather than a
     chart — which is exactly how it looks on the first day after this ships. */
  flex: 1; min-width: 3px; max-width: 42px; display: flex; align-items: flex-end; height: 100%;
  background: none; border: 0; padding: 0; cursor: pointer; border-radius: 3px 3px 0 0;
}
.dash-dbar-fill { width: 100%; background: var(--line-edge); border-radius: 3px 3px 0 0; min-height: 2px; opacity: 0.5; }
.dash-dbar:hover .dash-dbar-fill { opacity: 0.8; }
.dash-dbar.on .dash-dbar-fill { opacity: 1; }
/* The selected day has to be findable at a glance in ninety bars, and opacity alone
   isn't enough on a short bar — give it a full-height column behind the fill. */
.dash-dbar.on { background: var(--border); }
.dash-dbar:focus-visible { outline: 2px solid var(--line-edge); outline-offset: 1px; }
.dash-days-axis { display: flex; justify-content: space-between; font-size: 10px; color: var(--muted); margin: 6px 0 14px; }
.dash-stats.day { margin-bottom: 6px; }
.dash-h3 { font-size: 12px; text-transform: uppercase; letter-spacing: 0.07em; color: var(--muted); margin: 16px 0 8px; }
.dash-day-grid { display: grid; grid-template-columns: 1fr; gap: 0 26px; }
@media (min-width: 760px) { .dash-day-grid { grid-template-columns: 1fr 1fr; } }
@media (min-width: 1100px) { .dash-day-grid { grid-template-columns: repeat(4, 1fr); } }
.dash-mini { list-style: none; margin: 0; padding: 0; }
.dash-mini li {
  position: relative; display: flex; align-items: center; gap: 8px;
  padding: 5px 8px; font-size: 13px; border-radius: 6px; overflow: hidden;
}
.dash-mini .dm-bar { position: absolute; inset: 0 auto 0 0; background: var(--line-edge); opacity: 0.16; }
.dash-mini .dm-lbl { position: relative; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dash-mini .dm-num { position: relative; font-variant-numeric: tabular-nums; font-weight: 650; }

.fare-link {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  width: 100%; margin: 0; padding: 14px 16px 2px;
  background: none; border: 0; cursor: pointer; font: inherit;
  font-size: 13px; font-weight: 600; color: var(--line);
}
.fare-link .fare-ico { color: var(--line); flex-shrink: 0; display: block; }
.fare-link:hover { text-decoration: underline; }
.fare-link:focus-visible { outline: 2px solid var(--line); outline-offset: 2px; border-radius: 6px; }

.fare-route { margin: 2px 0 14px; }
.fare-table { width: 100%; border-collapse: collapse; }
.fare-table td { padding: 11px 0; border-bottom: 1px solid var(--border); vertical-align: top; }
.fare-table tr:last-child td { border-bottom: 0; }
.fare-table .fare-name { font-size: 15px; }
.fare-table .fare-name b { font-weight: 600; }
.fare-table .fare-sub { display: block; font-size: 11px; color: var(--muted); margin-top: 2px; }
.fare-table .fare-amt {
  text-align: right; white-space: nowrap; font-family: var(--board);
  font-variant-numeric: tabular-nums; font-weight: 700; font-size: 16px; padding-left: 12px;
}
.fare-table .fare-amt.na { color: var(--muted); font-weight: 400; }
.fare-subhead {
  font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--muted); margin: 18px 0 2px;
}
.fare-tip {
  margin: 16px 0 4px; padding: 12px 14px; border-radius: 12px;
  background: var(--bg); border: 1px solid var(--border); border-left: 3px solid var(--line);
  font-size: 13px; line-height: 1.5; color: var(--text);
}
.fare-tip b { color: var(--line); }
.fare-foot { margin: 12px 0 0; }

.disclaimer {
  padding: 6px 16px calc(14px + env(safe-area-inset-bottom));
  font-size: 10px; line-height: 1.5; color: var(--muted); text-align: center;
}

/* ================= language ================= */

/* Settings: three-way switch, same shape as the theme switch. Native names are
   short enough to sit side by side at every width we support. */
.lang-seg button { font-weight: 600; }

/* Wizard step 0. Full-width rows rather than the two-up line grid — there are
   only three, and a tap target that reads as a sentence beats a dense grid when
   the rider may not yet be reading a language they know. */
.wiz-langs { grid-column: 1 / -1; display: flex; flex-direction: column; gap: 8px; }
.wiz-lang { position: relative; }
.wiz-lang .t { font-size: 17px; }
.wiz-lang.on { border-color: var(--line); box-shadow: 0 0 0 1px var(--line); }
.wiz-lang .wiz-check {
  margin-left: auto; flex-shrink: 0; width: 22px; height: 22px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--line); color: var(--line-ink); font-size: 12px; font-weight: 700;
  opacity: 0; transform: scale(0.7); transition: opacity 0.18s, transform 0.18s;
}
.wiz-lang.on .wiz-check { opacity: 1; transform: scale(1); }
.wiz-lang-tag {
  flex-shrink: 0; margin-left: auto; font-size: 10px; font-weight: 700;
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted);
  border: 1px solid var(--border); border-radius: 999px; padding: 3px 9px;
}
.wiz-lang-tag + .wiz-check { margin-left: 8px; }
@media (prefers-reduced-motion: reduce) { .wiz-lang .wiz-check { transition: none; } }

/* Metra publishes alerts in English; when the Worker machine-translates one we
   label it rather than pass it off as official wording. */
.toast-mt {
  display: block; margin-top: 6px; font-size: 10.5px; font-style: italic;
  opacity: 0.75; cursor: help;
}

/* ---- room for longer strings ----
   Spanish renders a 12-hour clock with "a.m."/"p.m.", which is 3–4 characters
   wider than the English "AM"/"PM" the departure column was sized for. Polish
   uses a 24-hour clock and is narrower, so it keeps the default. */
html[lang="es"] .row { grid-template-columns: 96px 1fr auto; }
/* Polish shows a 24-hour clock ("21:46"), so the departure column can give the
   width back to the middle column — where the longer Polish status words live. */
html[lang="pl"] .row { grid-template-columns: 62px 1fr auto; }
html[lang="es"] .times { font-size: 20px; }
html[lang="es"] .times .to { font-size: 14px; }

/* The direction tag ("reversed · until …") grows in both translations; let it
   shrink and ellipsis instead of pushing the row wider than the header. */
.dir-context .dc-tag { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 55%; }

/* "Departs in" is two short words in English and up to four elsewhere; keep the
   countdown label on one line and let it use the width it needs. */
.cd-label { white-space: nowrap; }
.hero-top { gap: 10px; }
.hero-countdown { min-width: 0; }

/* Longer status words ("con retraso", "opóźniony") must wrap within the chip row
   rather than widen the card. */
.hero-chips { min-width: 0; }
.chip { white-space: nowrap; }

/* Board/arrive tags are longer in both translations; cap them so the station
   name keeps priority. */
.rs-tag { max-width: 34%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The hero time line is nowrap by design (a departure shouldn't wrap mid-clock),
   so on the narrowest phones it has to step down instead of overflowing. */
@media (max-width: 380px) {
  .times { font-size: 19px; }
  .times .to { font-size: 13px; }
  html[lang="es"] .times { font-size: 17px; }
  html[lang="es"] .times .to { font-size: 12px; }
  html[lang="es"] .row { grid-template-columns: 88px 1fr auto; }
}

/* ---- always-rendered site content (#site-info) ----------------------------
   Sits below the app, outside #app, so no view switch can hide it and #app's
   100dvh keeps it below the fold. It carries the page's <h1>, its prose, and
   the only links the homepage makes to /about, /how-it-works, /lines and /faq
   — a cold render (crawler, or JS off) would otherwise show just the language
   step. Suppressed in the installed app and on the owner dashboard, where a
   page footer is only noise; every browser context still shows it in full. */
.site-info {
  --si-link: #33D69B;
  border-top: 1px solid var(--border); background: var(--surface);
  padding: 30px 0 calc(34px + env(safe-area-inset-bottom));
}
:root[data-theme="light"] .site-info { --si-link: #0B6F50; }
.si-wrap { max-width: 560px; margin: 0 auto; padding: 0 20px; }
.si-title { padding: 0; font-size: 19px; font-weight: 700; line-height: 1.35; letter-spacing: -0.01em; }
.si-lede { margin-top: 10px; font-size: 14.5px; line-height: 1.65; color: var(--muted); }
.site-info h2 { margin: 26px 0 8px; font-size: 15px; font-weight: 650; letter-spacing: -0.01em; }
.si-list { margin: 0; padding-left: 20px; font-size: 14px; line-height: 1.6; color: var(--muted); }
.si-list li { margin: 5px 0; }
.si-note { margin-top: 12px; font-size: 14px; }
.si-lines { list-style: none; margin: 12px 0 0; padding: 0; }
.si-lines li {
  display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap;
  font-size: 14px; padding: 7px 0; border-bottom: 1px solid var(--border);
}
.si-lines li:last-child { border-bottom: none; }
.si-dot { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; align-self: center; box-shadow: 0 0 0 1px var(--text); }
.si-route { color: var(--muted); font-size: 12.5px; }
.si-nav { display: flex; flex-wrap: wrap; gap: 8px 18px; margin-top: 24px; font-size: 14px; }
.site-info a { color: var(--si-link); font-weight: 600; text-decoration: none; }
.site-info a:hover { text-decoration: underline; }
.site-info a:focus-visible { outline: 2px solid var(--si-link); outline-offset: 3px; border-radius: 4px; }
.si-legal { margin-top: 20px; font-size: 11.5px; line-height: 1.6; color: var(--muted); }
body.dash-mode .site-info { display: none; }
@media (display-mode: standalone), (display-mode: fullscreen), (display-mode: minimal-ui) {
  .site-info { display: none; }
}

/* ---- trip view: one train's whole journey ---- */
/* A full-height sheet rather than the short .sheet used for fares and About: the
   content is a 25-row stop list that a rider scrolls while standing on a moving
   train, so it gets the whole screen and its own scroll container. */
.trip-backdrop {
  position: fixed; inset: 0; z-index: 120;
  background: rgba(10, 15, 21, 0.6);
  display: flex; align-items: flex-end; justify-content: center;
}
.trip-sheet {
  background: var(--bg); width: 100%; max-width: 520px;
  height: 100dvh; display: flex; flex-direction: column;
  animation: sheet-in 0.22s ease;
}
@media (min-width: 560px) {
  .trip-backdrop { align-items: center; padding: 20px; }
  .trip-sheet { height: min(92dvh, 860px); border-radius: 18px; overflow: hidden; box-shadow: var(--shadow-md); }
}
.tp-head {
  display: flex; align-items: center; gap: 10px; flex-shrink: 0;
  padding: calc(10px + env(safe-area-inset-top)) 14px 10px;
  background: var(--surface); border-bottom: 1px solid var(--border);
}
.tp-titles { flex: 1; min-width: 0; }
.tp-title { font-size: 15px; font-weight: 700; letter-spacing: -0.01em; }
.tp-sub { font-size: 12px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tp-sub .tp-arrow { color: var(--line); }
.tp-pill {
  flex-shrink: 0; display: inline-flex; align-items: center; gap: 5px;
  font-size: 11px; font-weight: 700; padding: 3px 9px; border-radius: 999px;
}
.tp-pill .pdot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; animation: pulse 1.6s ease-in-out infinite; }
.tp-pill.ontime { color: var(--green); background: rgba(57, 217, 138, 0.15); }
.tp-pill.late { color: var(--amber); background: rgba(240, 166, 60, 0.16); }
.tp-pill.early { color: #5c9be6; background: rgba(92, 155, 230, 0.16); }
.tp-pill.sched { color: var(--muted); background: var(--border); }
.tp-pill.cancel { color: var(--red); background: rgba(255, 107, 97, 0.16); }
.tp-body { flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch; padding: 14px 14px calc(24px + env(safe-area-inset-bottom)); }
.tp-empty { color: var(--muted); text-align: center; padding: 40px 0 14px; font-size: 14px; }
.tp-retry { display: block; margin: 0 auto; background: transparent; border: 1px solid var(--border); color: var(--text); border-radius: 999px; padding: 8px 18px; font: inherit; font-size: 13px; cursor: pointer; }

/* The answer the rider opened this for, above everything else on screen. */
.tp-mine {
  background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
  padding: 14px 16px; margin-bottom: 12px;
}
.tp-mine.cancel { border-color: var(--danger-edge); background: var(--danger-bg); }
.tp-mine-lead { font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); }
.tp-mine.cancel .tp-mine-lead { color: var(--danger-text); font-size: 15px; font-weight: 700; letter-spacing: 0; text-transform: none; }
.tp-mine-name { font-size: 22px; font-weight: 700; letter-spacing: -0.02em; margin-top: 3px; }
.tp-mine-sub { font-size: 13px; color: var(--muted); margin-top: 4px; }
.tp-mine.cancel .tp-mine-sub { color: var(--danger-text); }
.tp-mine-sub .tp-cd { color: var(--green); font-weight: 700; font-variant-numeric: tabular-nums; }
.tp-was { color: var(--muted); opacity: 0.75; }
.tp-trend { margin-top: 7px; font-size: 12.5px; font-weight: 600; }
.tp-trend.worse { color: var(--amber); }
.tp-trend.better { color: var(--green); }

.tp-warn {
  border-radius: 10px; padding: 9px 12px; margin-bottom: 8px; font-size: 13px; line-height: 1.45;
  background: var(--warn-bg); border: 1px solid var(--warn-edge); color: var(--warn-text);
}
.tp-warn.stall { background: var(--danger-bg); border-color: var(--danger-edge); color: var(--danger-text); font-weight: 600; }
/* The sun hint is an aside, not a warning: it never uses the amber/red banner palette,
   because nothing has gone wrong. --line-text, not --line, so it clears 4.5:1 as words. */
.tp-sun { margin: 0 0 8px; font-size: 13px; color: var(--line-text); }
.tp-warn.offline { background: transparent; border-color: var(--border); color: var(--muted); }

/* The stop list. The train marker is a row of its own, not an overlay: long station
   names wrap on a narrow phone, so anything positioned by row-height arithmetic drifts
   out of place on exactly the devices this runs on. */
.tp-stops { list-style: none; margin: 0; padding: 0; }
.tp-stop { display: flex; align-items: center; gap: 6px; min-height: 40px; padding: 3px 2px; }
.tp-rail { position: relative; width: 28px; flex-shrink: 0; align-self: stretch; display: flex; justify-content: center; }
/* -3px: .tp-stop has 3px of vertical padding and align-self:stretch fills only the
   content box, so every row boundary left a gap and the rail read as a dashed line. */
.tp-rail::before { content: ""; position: absolute; top: -3px; bottom: -3px; width: 3px; background: var(--line-edge); }
.tp-stops > li:first-child .tp-rail::before { top: 50%; }
.tp-stops > li:last-child .tp-rail::before { bottom: 50%; }
.tp-dot {
  position: relative; z-index: 1; align-self: center;
  width: 10px; height: 10px; border-radius: 50%; background: var(--bg); border: 2px solid var(--line-edge);
}
.tp-name { flex: 1; min-width: 0; font-size: 15px; display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.tp-times { flex-shrink: 0; display: flex; align-items: baseline; gap: 7px; font-family: var(--board); font-variant-numeric: tabular-nums; font-size: 13px; }
.tp-delta { font-size: 11.5px; font-weight: 700; }
.tp-delta.late { color: var(--amber); }
.tp-delta.early { color: #5c9be6; }
.tp-tag {
  font-size: 9px; font-weight: 800; letter-spacing: 0.07em; text-transform: uppercase;
  padding: 2px 7px; border-radius: 999px; background: var(--line); color: var(--line-ink);
}
/* Behind you: still there, deliberately quiet. */
.tp-stop.past { opacity: 0.45; }
.tp-stop.past .tp-rail::before { opacity: 0.25; }
.tp-stop.next .tp-name { font-weight: 700; }
.tp-stop.next .tp-dot { width: 13px; height: 13px; border-width: 3px; background: var(--line-edge); }
/* The station the train is standing in. Never dimmed — it is not behind them — and given
   the same weight as `.next`, which it usually also is; the exception is a stop the feed
   says is being skipped while the train sits in it, and that must not go grey either. */
.tp-stop.at { opacity: 1; }
.tp-stop.at .tp-name { font-weight: 700; }
.tp-stop.at .tp-dot { width: 13px; height: 13px; border-width: 3px; background: var(--line-edge); }
.tp-stop.mine { opacity: 1; }
.tp-stop.mine .tp-name { font-weight: 700; }
.tp-stop.mine .tp-dot { width: 14px; height: 14px; border-width: 3px; box-shadow: 0 0 0 3px var(--border); }
.tp-stop.skipped .tp-name { color: var(--muted); text-decoration: line-through; }
.tp-stop.skipped .tp-dot { border-color: var(--muted); width: 7px; height: 7px; border-width: 2px; }
.tp-skip { font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); font-family: inherit; }
/* Stations an express runs through. They're on the list so the marker has somewhere to
   move between and the rider can match what's out of the window against the screen —
   quiet enough that the list still reads as this train's four stops, not twenty-six. */
.tp-stop.passing { min-height: 26px; }
.tp-stop.passing .tp-name { font-size: 12.5px; color: var(--muted); opacity: 0.55; }
.tp-stop.passing .tp-dot { width: 5px; height: 5px; border-width: 0; background: var(--line-edge); opacity: 0.4; }
.tp-stop.passing.past { opacity: 0.6; }
.tp-skip.dash { font-family: var(--board); opacity: 0.5; }
/* Reading a timetable, not watching a train. In the live view the run-through stations
   are deliberately faint so the list still reads as this train's four stops — but that
   puts them near 2.8:1, and in the Schedule view *which stations this train passes* is
   the entire question the rider tapped in to answer. They stay visibly secondary (smaller,
   muted, dashed time, tiny dot) while clearing the 4.5:1 text bar. */
.trip-sheet.schedule-only .tp-stop.passing .tp-name { opacity: 1; font-size: 13px; }
.trip-sheet.schedule-only .tp-stop.passing .tp-dot { opacity: 0.7; }
.trip-sheet.schedule-only .tp-skip.dash { opacity: 0.75; }
/* Same padding as .tp-stop, or the marker sits 2px left of every station dot. */
.tp-here { display: flex; align-items: center; gap: 6px; min-height: 38px; padding: 3px 2px; }
/* The lamp sticks out of its row on purpose — it's the one thing on this screen that
   moves, and it should look like it. Overflow is visible so the beam can spill over the
   rail without pushing the list around. */
/* --line-edge, not --line: the ring, the halo and the beam are all currentColor, and on
   a light-theme UP-NW journey the raw line colour left the one moving thing on the
   screen at 1.16:1 against the page — an invisible marker in an invisible list. */
.tp-train { position: relative; z-index: 3; align-self: center; width: 26px; height: 26px; display: block; color: var(--line-edge); }
.tp-train svg { width: 100%; height: 100%; display: block; overflow: visible; }
/* Two filled discs rather than a stroked ring: same 16px bubble with a 3.5px collar as
   the board's route strip draws (.rs-train-dot), and nothing that scales with the stroke. */
.lh-ring { fill: currentColor; }
.lh-core { fill: var(--line-edge-ink); }
.lh-halo { fill: currentColor; opacity: 0; }
.lh-beam { fill: url(#lh-beam-fade); opacity: 0; }
/* fill-box, not view-box: it's supported back to Safari 11, and the two-lobe path is
   symmetric about the lamp on purpose so 50% 50% lands exactly on it. */
.lh-beam { transform-box: fill-box; transform-origin: 50% 50%; }

/* Live: the lamp is lit and sweeping. An estimate gets no light at all — a beam over a
   guess is a claim about where the train is that we haven't got, and the two states must
   never be confusable at a glance. */
.tp-here.live .lh-beam { opacity: 1; animation: lhSweep 5.6s linear infinite; }
.tp-here.live .lh-halo { animation: lhGlow 5.6s ease-in-out infinite; }
.tp-here.live .lh-ring { filter: drop-shadow(0 0 3px color-mix(in srgb, var(--line-edge) 70%, transparent)); }
.tp-here.est .lh-ring { fill: var(--muted); }
.tp-here.est .lh-core { fill: var(--bg); }
@keyframes lhSweep { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
/* Two lobes, so the halo brightens twice per revolution — once as each one comes round. */
@keyframes lhGlow {
  0%, 50%, 100% { opacity: 0.3; }
  25%, 75% { opacity: 0.08; }
}
@media (prefers-reduced-motion: reduce) {
  /* opacity is 0 at rest, so "animation: none" alone would put the light out. */
  .tp-here.live .lh-beam { animation: none; opacity: 1; }
  .tp-here.live .lh-halo { animation: none; opacity: 0.22; }
}
.tp-here-lbl { font-size: 11.5px; font-weight: 700; color: var(--line-text); }
.tp-here.est .tp-here-lbl { color: var(--muted); font-weight: 600; }
.tp-more { padding: 2px 0 6px 34px; }
.tp-more button {
  background: transparent; border: 1px solid var(--border); color: var(--muted);
  border-radius: 999px; padding: 5px 12px; cursor: pointer; font: inherit; font-size: 12px;
}
.tp-more button:hover { border-color: var(--line); color: var(--text); }

/* On-board prompt on the live card: the one tap that reframes the app. */
.onboard-ask {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 12px; padding: 11px 13px; border-radius: 12px;
  background: color-mix(in srgb, var(--line) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--line) 40%, transparent);
}
.onboard-ask .oa-q { flex: 1; min-width: 130px; font-size: 13.5px; font-weight: 650; }
.onboard-ask button { border-radius: 999px; padding: 7px 14px; font: inherit; font-size: 12.5px; font-weight: 650; cursor: pointer; }
.onboard-ask .oa-yes { background: var(--line); color: var(--line-ink); border: none; }
.onboard-ask .oa-no { background: transparent; color: var(--muted); border: 1px solid var(--border); }
.onboard-bar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 0 0 12px; padding: 10px 13px; border-radius: 12px;
  background: var(--surface); border: 1px solid var(--line);
}
.onboard-bar .ob-badge {
  font-size: 10px; font-weight: 800; letter-spacing: 0.07em; text-transform: uppercase;
  padding: 3px 8px; border-radius: 999px; background: var(--line); color: var(--line-ink);
}
.onboard-bar .ob-txt { flex: 1; min-width: 120px; font-size: 13px; font-weight: 600; }
.onboard-bar button { border-radius: 999px; padding: 6px 13px; font: inherit; font-size: 12.5px; font-weight: 650; cursor: pointer; border: 1px solid var(--border); background: transparent; color: var(--text); }
.onboard-bar .ob-open { background: var(--line); color: var(--line-ink); border-color: var(--line); }

/* A trip row is a button now, so it must not inherit button chrome. */
.tt-row { width: 100%; text-align: left; background: transparent; border: none; border-bottom: 1px solid var(--border); color: inherit; font: inherit; cursor: pointer; }
.tt-row:hover { background: color-mix(in srgb, var(--line) 7%, transparent); }
.tt-row .caret { color: var(--muted); font-size: 11px; margin-left: 6px; }

.track-btn {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  width: 100%; margin-top: 10px; padding: 10px 14px;
  border-radius: 11px; border: 1px solid var(--border); background: transparent;
  color: var(--text); font: inherit; font-size: 13.5px; font-weight: 650; cursor: pointer;
}
.track-btn:hover { border-color: var(--line); }
.track-btn:active { transform: scale(0.985); }
.track-btn .tb-arrow { color: var(--line); }

/* Alerts on the trip sheet: headline only until tapped. Native <details>, so it needs
   no script, works offline and is keyboard-navigable for free. */
.tp-alert {
  border-radius: 10px; margin-bottom: 8px; font-size: 13px; line-height: 1.45;
  background: var(--warn-bg); border: 1px solid var(--warn-edge); color: var(--warn-text);
}
.tp-alert > summary {
  padding: 9px 12px; cursor: pointer; font-weight: 600; list-style: none;
  display: flex; align-items: baseline; gap: 7px; flex-wrap: wrap;
}
.tp-alert > summary::-webkit-details-marker { display: none; }
.tp-alert > summary::after { content: "▾"; margin-left: auto; opacity: 0.7; font-size: 11px; }
.tp-alert[open] > summary::after { content: "▴"; }
.tp-alert > p { padding: 0 12px 10px; margin: 0; font-weight: 400; }
.tp-alert.mine { background: var(--danger-bg); border-color: var(--danger-edge); color: var(--danger-text); }
.tp-alert-tag {
  font-size: 9px; font-weight: 800; letter-spacing: 0.06em; text-transform: uppercase;
  padding: 2px 7px; border-radius: 999px; background: currentColor; flex-shrink: 0;
}
.tp-alert.mine .tp-alert-tag { color: var(--danger-bg); background: var(--danger-text); }
/* The bar: what the line is saying that isn't about this train, and when we last heard
   from the Worker. Both are small on purpose — neither is the answer anyone opened this
   for, and the stop list has to stay on the phone. */
.tp-bar { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin: -2px 0 10px; min-height: 28px; }
.tp-abtn, .tp-refresh {
  position: relative;
  display: inline-flex; align-items: center; gap: 6px; min-height: 36px; padding: 0 13px;
  background: transparent; border: 1px solid var(--border); border-radius: 999px;
  color: var(--muted); font: inherit; font-size: 12px; font-weight: 600; cursor: pointer;
}
/* 36px of visible pill, 44px of hit area. The overlay buys the reachable target without
   spending the vertical space on it — the stop list still has to fit the phone. */
.tp-abtn::after, .tp-refresh::after { content: ""; position: absolute; inset: -4px -6px; }
.tp-abtn { color: var(--warn-text); border-color: var(--warn-edge); background: var(--warn-bg); }
.tp-abtn.on { border-style: dashed; }
/* This reads as a control now, not a caption. It was a --muted 12px pill with a 14px
   glyph, and riders took it for a timestamp. It keeps its rank — the answer they opened
   the sheet for is the stop list, not this — but a control has to look pressable.
   Colour comes from --line-edge, the token guaranteed 3:1 against the page, NOT --line:
   the raw GTFS hex is 1.09:1 on light-theme UP-NW, i.e. an outline that isn't there. */
.tp-refresh { color: var(--text); font-size: 13px; font-weight: 650; border-color: var(--line-edge); }
.tp-refresh svg { width: 17px; height: 17px; flex-shrink: 0; color: var(--line-edge); }
.tp-abtn:hover { border-color: var(--line-edge); }
/* Hover must not go BACKWARDS from the resting state. --line here would erase the outline
   on a light line colour, which is worse than the muted border it replaced. */
.tp-refresh:hover { border-color: var(--line-text); }
.tp-refresh:active { transform: scale(0.96); }
.tp-when { font-variant-numeric: tabular-nums; }
.tp-refresh.spin svg { animation: tpSpin 0.9s linear infinite; }
@keyframes tpSpin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .tp-refresh.spin svg { animation: none; opacity: 0.5; } }

/* ---- find your train ---- */
.find-btn {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  width: 100%; margin: 16px 0 4px; padding: 11px 14px;
  border-radius: 11px; border: 1px dashed var(--border); background: transparent;
  color: var(--muted); font: inherit; font-size: 13px; font-weight: 600; cursor: pointer;
}
.find-btn:hover { border-color: var(--line); color: var(--text); border-style: solid; }
.onboard-bar.resume { border-color: var(--border); }
.onboard-bar.resume .ob-badge { background: var(--border); color: var(--muted); }

.fd-sec {
  font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted);
  margin: 16px 2px 8px;
}
.tp-body > .fd-sec:first-child { margin-top: 2px; }
.fd-row {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  padding: 12px 14px; margin-bottom: 8px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
  color: inherit; font: inherit; cursor: pointer;
}
.fd-row:hover { border-color: var(--line); }
.fd-row.closest { border-color: var(--line); background: color-mix(in srgb, var(--line) 10%, var(--surface)); }
.fd-no { font-weight: 700; font-size: 14px; flex-shrink: 0; }
.fd-meta { flex: 1; min-width: 0; font-size: 12px; color: var(--muted); display: flex; flex-direction: column; gap: 2px; }
.fd-next { color: var(--text); opacity: 0.75; }
.fd-right { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.fd-tag {
  font-size: 9px; font-weight: 800; letter-spacing: 0.06em; text-transform: uppercase;
  padding: 3px 8px; border-radius: 999px; background: var(--line); color: var(--line-ink);
}
.fd-late { font-size: 12px; font-weight: 700; color: var(--amber); }
.fd-go { color: var(--muted); font-size: 16px; }
.fd-locate {
  display: block; width: 100%; margin-bottom: 10px; padding: 10px 14px;
  background: var(--line); color: var(--line-ink); border: none; border-radius: 11px;
  font: inherit; font-size: 13px; font-weight: 650; cursor: pointer;
}
.fd-note { color: var(--muted); font-size: 13px; padding: 4px 2px 10px; }

/* A train that has just left this platform: present, dated, never counted down to. */
.gone-card {
  border: 1px dashed var(--border); border-radius: 12px;
  padding: 10px 13px; margin-bottom: 12px; background: transparent;
}
.gone-top { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.gone-tag {
  font-size: 9px; font-weight: 800; letter-spacing: 0.07em; text-transform: uppercase;
  padding: 3px 8px; border-radius: 999px; background: var(--border); color: var(--muted);
}
.gone-no { font-size: 13px; font-weight: 600; color: var(--muted); }
.gone-time { margin-left: auto; font-family: var(--board); font-size: 13px; color: var(--muted); text-decoration: line-through; }
.gone-card .onboard-ask { margin-top: 10px; }

/* Kept in the DOM for the screen reader, off the screen for everyone else. */
.sr-only {
  position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}
.gone-card.cancel { border-style: solid; border-color: var(--danger-edge); background: var(--danger-bg); }
.gone-card.cancel .gone-no, .gone-card.cancel .gone-time { color: var(--danger-text); }
.gone-tag.cancel { background: var(--red); color: #fff; }
.gone-note { margin-top: 7px; font-size: 13px; font-weight: 600; color: var(--danger-text); }
