:root {
  --parchment: #f1e4c6;
  --panel: #f8f0da;
  --heritage-green: #1e4638;
  --heritage-green-dark: #12251b;
  --maroon: #7a2e27;
  --brass: #a97b2e;
  --brass-light: #d9ae5c;
  /* --brass is 3.0:1 on parchment and 3.8:1 behind white text, so it fails
     both the 3:1 floor for focus indicators (WCAG 1.4.11) and the 4.5:1
     floor for small text. This is the same brass taken far enough down to
     clear both: 4.3:1 on parchment, 5.4:1 behind white. Used for focus
     rings and for anything with text sitting on it. */
  --brass-dark: #8a6320;
  --ink: #2b2013;
  /* Darkened from #8f7c58 (only ~3.6:1 on parchment, below WCAG AA) to
     ~5.9:1 — this colour carries a lot of secondary text and the site is
     aimed at visitors who may not see low-contrast type well. */
  --muted: #6a5a3c;
  --border: #ddcda2;
  /* Warm near-white for form fields — pure white read as a harsh hole
     punched in the parchment, but the field still needs to sit lighter
     than the panel it's on to read as something you can type in. */
  --field-bg: #fdf9ee;
  --error: #8c2f2f;
  --error-bg: #f6dede;
  --success: #1e4638;
  --success-bg: #dfe9df;

  /* Headcode palette — identifies which train on the diagram belongs to
     which timetable column, the way real locomotives carried numbered
     discs for their working. Cycled by service, not by direction. */
  --headcode-0: #1e4638;
  --headcode-1: #7a2e27;
  --headcode-2: #2c4a6e;
  --headcode-3: #5c2a4a;
  /* Darkened from #a85c1e, which only reached 4.4:1 on parchment. */
  --headcode-4: #8f4a14;
}

* { box-sizing: border-box; }

/* Off-screen until focused, then pinned to the top-left where a keyboard
   user expects the first stop in the tab order to appear. Not display:none
   — that would take it out of the tab order entirely, which defeats it. */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: 0.6rem 1rem;
  background: var(--heritage-green-dark);
  color: var(--parchment);
  font-weight: 600;
  border-radius: 0 0 4px 0;
  text-decoration: none;
}
.skip-link:focus {
  left: 0;
}

/* The skip link's target. Focusing a <main> would otherwise draw a ring
   round the entire page, which reads as a rendering fault rather than a
   cue. The visitor knows where they are — the scroll and the next Tab say
   so. */
.site-main:focus { outline: none; }

/* Any author `display` (.button, the grid lists) beats the UA's
   [hidden] rule, so elements toggled via the hidden attribute stayed
   visible. This makes `hidden` mean hidden everywhere. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--parchment);
  color: var(--ink);
  font-family: "Lora", Georgia, "Palatino Linotype", serif;
  /* 17px base rather than 16 — the audience is heritage-railway visitors,
     who skew older, and every rem-based size on the page scales with it. */
  font-size: 17px;
  line-height: 1.65;
}

body {
  background-image:
    radial-gradient(ellipse at top left, rgba(169, 123, 46, 0.09), transparent 50%),
    radial-gradient(ellipse at bottom right, rgba(30, 70, 56, 0.07), transparent 50%);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

h1, h2, h3 {
  font-family: "Playfair Display", Georgia, serif;
  font-weight: 700;
  /* --heritage-green rather than --heritage-green-dark. The dark is near
     enough to black that nobody reads it as green, so the brand colour was
     doing no work anywhere below the header. This is 8.4:1 on parchment,
     comfortably past AA, and puts a green marker at every section break —
     which is most of what makes the page scan as one thing. */
  color: var(--heritage-green);
  margin: 0 0 0.5em;
}

h1 { font-size: 2.2rem; font-weight: 800; }
h2 { font-size: 1.5rem; font-weight: 700; }
h3 { font-size: 1.15rem; font-weight: 600; }

a { color: var(--maroon); }
a:hover { color: var(--brass); }

p { margin: 0 0 0.75em; }

/* ------------------------------------------------------------- ornament */

.ornament-rule {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.7rem;
  margin: 0.9rem 0 1.6rem;
  color: var(--brass);
  font-size: 0.8rem;
  line-height: 1;
}
.ornament-rule::before, .ornament-rule::after {
  content: "";
  height: 1px;
  flex: 1 1 auto;
  max-width: 90px;
  background: var(--brass);
}

/* Ornate poster frame — public-facing pages only (see body.page--poster) */

.page--poster .site-main {
  position: relative;
  /* Matched top and bottom — the framed box was sitting flush against the
     footer while having clear space under the header. */
  margin-top: 2.25rem;
  margin-bottom: 2.25rem;
  padding: 2.75rem 2.25rem 3.75rem;
}

.page--poster .site-main::before {
  content: "";
  position: absolute;
  inset: 6px;
  border: 2px solid var(--brass);
  outline: 1px solid var(--brass);
  outline-offset: 6px;
  pointer-events: none;
}

/* ---------------------------------------------------------------- header */

.site-header {
  background: var(--heritage-green);
  border-bottom: 4px solid var(--brass);
  color: var(--parchment);
}

.site-header__inner {
  max-width: 1100px;
  margin: 0 auto;
  padding: 0.85rem 1.5rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.brand {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-family: "Playfair Display", serif;
  font-size: 1.4rem;
  font-weight: 800;
  color: var(--parchment);
  text-decoration: none;
  letter-spacing: 0.02em;
}

.brand:hover { color: var(--brass-light); }
.brand__mark { color: var(--brass-light); }
/* The header is dark green, where the browser's own dark focus ring is
   nearly invisible. Light brass rather than --brass-dark for the same
   reason: on this background the contrast runs the other way. */
.brand:focus-visible {
  outline: 2px solid var(--brass-light);
  outline-offset: 3px;
  border-radius: 3px;
}

/* Subordinate to the name, but kept the same colour rather than dimmed —
   the header's parchment-on-green is a deliberate high-contrast pairing
   and fading it would undo that. */
.brand__tld {
  font-size: 0.6em;
  font-weight: 400;
  letter-spacing: 0.01em;
}

.site-nav {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  font-weight: 500;
}

.site-nav a, .site-nav .link-button {
  color: var(--parchment);
}

.site-nav a:hover, .site-nav .link-button:hover {
  color: var(--brass-light);
}

/* ------------------------------------------------------------------ main */

.site-main {
  flex: 1;
  max-width: 1100px;
  width: 100%;
  margin: 0 auto;
  padding: 2rem 1.5rem 3rem;
}

/* Mirrors the header: same green, same 4px brass rule, facing the other
   way. The page is bookended rather than fading out into parchment, so
   reaching the bottom reads as the end of the site and not the end of a
   section. .site-main is flex:1, so on a short page this still sits on the
   bottom edge of the viewport instead of floating mid-screen. */
.site-footer {
  text-align: center;
  padding: 1.5rem;
  background: var(--heritage-green);
  color: var(--parchment);
  font-size: 0.85rem;
  border-top: 4px solid var(--brass);
}

/* ------------------------------------------------------------------ hero */

.hero {
  text-align: center;
  padding: 2.5rem 1rem 3rem;
  border-bottom: 2px solid var(--border);
  margin-bottom: 2.5rem;
}

.hero h1 { font-size: 2.6rem; }

.hero__lead {
  max-width: 640px;
  margin: 0 auto;
  color: var(--muted);
  font-size: 1.05rem;
}

/* ------------------------------------------------------------ what's on */

/* Deliberately the same brass left edge as the "Coming up" strip on a
   railway page: a visitor who has seen one should recognise the other as
   the same kind of thing, one line's diary against the whole site's. */

.whats-on { margin-bottom: 2.5rem; }

.whats-on h2 { margin-bottom: 0.9rem; }

.whats-on__grid {
  display: grid;
  /* Narrower than the railway cards below, so this reads as the lighter of
     the two and doesn't push the directory off a phone screen. */
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 0.75rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.whats-on__card {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  /* The li is the grid item and stretches; without this a card with a
     short name sits shorter than its neighbours in the same row. */
  height: 100%;
  background: var(--panel);
  border: 1px solid var(--border);
  border-left: 3px solid var(--brass);
  border-radius: 4px;
  padding: 0.7rem 0.9rem;
  text-decoration: none;
  color: var(--ink);
}

.whats-on__card:hover { border-color: var(--brass); }

.whats-on__card:focus-visible {
  outline: 2px solid var(--brass-dark);
  outline-offset: 2px;
}

.whats-on__when {
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--brass-dark);
}

/* Sits inside the date line, so it has to undo the label styling above it
   rather than inherit small caps for a plain count. */
.whats-on__extra {
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: var(--muted);
}

.whats-on__name { font-weight: 600; }

.whats-on__where { font-size: 0.9rem; color: var(--muted); }

.whats-on__more {
  margin: 0.75rem 0 0;
  color: var(--muted);
  font-size: 0.9rem;
}

/* Six cards stacked one per row ran to a full phone screen on their own,
   putting the railway list — the thing most people came for — two screens
   down. Two columns halves that without hiding anything: the text on these
   cards is short enough to take the narrower measure. */
@media (max-width: 640px) {
  .whats-on__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .whats-on__card { padding: 0.6rem 0.7rem; }
  .whats-on__name { font-size: 0.95rem; }
  .whats-on__where { font-size: 0.85rem; }
  /* On a card this narrow the count can't share the date's line without
     pushing "Sat 26 Sep" onto two, so it takes its own. */
  .whats-on__extra { display: block; }
}

/* ------------------------------------------------------------- railways */

.railway-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 1.25rem;
}

.railway-card {
  display: block;
  background: var(--panel);
  border: 1px solid var(--border);
  border-top: 3px solid var(--brass);
  border-radius: 6px;
  padding: 1.25rem;
  text-decoration: none;
  color: var(--ink);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.railway-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.1);
}

/* These are the only interactive elements on the home page and the 404, so
   without this a keyboard user has nothing at all to follow there. */
.railway-card:focus-visible {
  outline: 2px solid var(--brass-dark);
  outline-offset: 3px;
}

.railway-card h3 { margin-bottom: 0.35em; }
.railway-card__tagline { color: var(--muted); font-size: 0.92rem; }
.railway-card__cta { color: var(--maroon); font-weight: 600; font-size: 0.9rem; }

.railway-header {
  text-align: center;
  margin-bottom: 2rem;
  padding-bottom: 1.25rem;
  border-bottom: 2px solid var(--border);
}

.railway-header__tagline {
  color: var(--maroon);
  font-weight: 600;
  font-style: italic;
  font-size: 1.05rem;
}

.railway-header__description {
  max-width: 640px;
  margin: 0 auto;
  color: var(--muted);
}

/* The locomotive definitions (partials/_loco_sprite.html). Nothing in here
   renders in place — <symbol> never does — but an <svg> with no size still
   takes a line box, which put a stray gap above whatever followed it. */
.loco-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

/* --------------------------------------------------------------- diagram */

.diagram-section { margin-bottom: 2.5rem; }

.line-diagram {
  position: relative;
  background: var(--panel);
  border: 1px solid var(--brass);
  border-radius: 4px;
  padding: 4rem 2.5rem 4.25rem;
  min-height: 170px;
  /* A train near either end of the line hangs its tooltip past the diagram
     edge, which widened the whole page and gave it a horizontal scrollbar.
     `clip` rather than `hidden` so the vertical axis stays visible. */
  overflow-x: clip;
}

.line-diagram::before {
  content: "";
  position: absolute;
  inset: 6px;
  border: 1px solid var(--border);
  border-radius: 2px;
  pointer-events: none;
}

/* The track, the station layer and the train layer are all pinned to the
   exact same box, so a station dot and a train marker at the same
   percentage land on precisely the same point of the rails. Switching
   that shared box from horizontal to vertical (see the mobile block at
   the end of this file) re-orients the whole diagram at once. */
.line-diagram__track,
.line-diagram__stations,
.line-diagram__trains {
  position: absolute;
  left: 2.5rem;
  right: 2.5rem;
  top: 50%;
  height: 22px;
  transform: translateY(-50%);
}

/* An actual length of track — two brass rails with sleepers crossing
   between them — so a train reads as running on rails, not floating on a
   bar. The sleepers are deliberately low-contrast: at full strength they
   competed with the locomotive silhouettes riding over them and made the
   trains hard to pick out. */
.line-diagram__track {
  background: repeating-linear-gradient(
    90deg,
    rgba(91, 70, 48, 0.3) 0, rgba(91, 70, 48, 0.3) 3px,
    transparent 3px, transparent 23px
  );
}

.line-diagram__track::before,
.line-diagram__track::after {
  content: "";
  position: absolute;
  left: -5px;
  right: -5px;
  height: 3px;
  background: var(--brass);
  box-shadow: 0 1px 0 rgba(43, 32, 19, 0.28);
}
.line-diagram__track::before { top: 0; }
.line-diagram__track::after { bottom: 0; }

.line-diagram__stations,
.line-diagram__trains {
  pointer-events: none;
}

.station-node {
  position: absolute;
  left: var(--pos);
  top: 50%;
  transform: translate(-50%, -50%);
  text-align: center;
  pointer-events: auto;
}

/* Sized to exactly fill the gap between the two rails, on the sleepers,
   so the dot reads as set *into* the track rather than sitting on top of
   it. The panel-coloured fill masks the sleepers behind it. */
.station-node__dot {
  display: block;
  width: 16px;
  height: 16px;
  padding: 0;
  background: var(--panel);
  border: 3px solid var(--heritage-green-dark);
  border-radius: 50%;
  box-shadow: inset 0 1px 2px rgba(43, 32, 19, 0.45);
  cursor: pointer;
  position: relative;
}

/* The visible dot stays small so the diagram reads cleanly, but the hit
   area is padded out to a proper touch target. */
.station-node__dot::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
}

.station-node__dot:hover { border-color: var(--maroon); }
.station-node__dot:focus-visible {
  outline: 2px solid var(--brass-dark);
  outline-offset: 3px;
}

.station-node__label {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  font-size: 1rem;
  font-weight: 700;
  font-family: "Lora", Georgia, serif;
  letter-spacing: 0.01em;
  color: var(--heritage-green-dark);
}

.station-node--above .station-node__label { bottom: calc(100% + 10px); }
.station-node--below .station-node__label { top: calc(100% + 10px); }

/* The endpoint stations sit right at the diagram's edge — a centred label
   there would overhang past the frame, so anchor it to the dot instead of
   centring on it. */
.station-node:first-child .station-node__label {
  left: -4px;
  transform: none;
  text-align: left;
}
.station-node:last-child .station-node__label {
  left: auto;
  right: -4px;
  transform: none;
  text-align: right;
}

.train-marker {
  position: absolute;
  left: var(--pos);
  top: 50%;
  transform: translate(-50%, -50%);
  transition: left 1s linear, top 1s linear;
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: auto;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
}

.train-marker:focus-visible {
  outline: 2px solid var(--brass-dark);
  outline-offset: 2px;
  border-radius: 4px;
}

/* The locomotive is a dark silhouette riding over a striped track, which
   made it genuinely hard to pick out. Stacked zero-offset drop-shadows in
   the panel colour trace a light halo around the whole silhouette,
   separating it from the sleepers, and a soft dark shadow underneath
   grounds it on the rails. */
.train-marker svg {
  width: 100%;
  height: 100%;
  display: block;
  filter:
    drop-shadow(0 0 2px var(--panel))
    drop-shadow(0 0 2px var(--panel))
    drop-shadow(0 0 1px var(--panel))
    drop-shadow(0 2px 2px rgba(43, 32, 19, 0.35));
}
/* The locomotive artwork faces left (funnel at the low-x end) by default,
   so a "forward" train — moving toward increasing position_km, i.e.
   rightward on screen — needs to be flipped to actually face the way
   it's travelling. "Reverse" (moving left) already faces the right way
   unflipped. Colour identifies which *service* a train belongs to, not
   its direction — see the headcode disc below and the matching chip in
   the timetable header. */
.train-marker--forward { transform: translate(-50%, -50%) scaleX(-1); }

.train-marker--c0 { color: var(--headcode-0); }
.train-marker--c1 { color: var(--headcode-1); }
.train-marker--c2 { color: var(--headcode-2); }
.train-marker--c3 { color: var(--headcode-3); }
.train-marker--c4 { color: var(--headcode-4); }

/* A small numbered disc, like the real headcode discs steam locomotives
   carried to show which working they were on — lets a visitor match a
   train on the diagram to its column in the timetable without hovering
   (which doesn't work on a phone anyway). */
.train-marker__headcode {
  position: absolute;
  top: -6px;
  right: -10px;
  width: 21px;
  height: 21px;
  border-radius: 50%;
  background: var(--panel);
  border: 2px solid currentColor;
  color: inherit;
  font-family: "Lora", Georgia, serif;
  font-size: 12px;
  font-weight: 700;
  line-height: 17px;
  text-align: center;
  box-shadow: 0 1px 2px rgba(43, 32, 19, 0.3);
  /* Above the steam, so a stray wisp can't blur the digit. The two are kept
     apart geometrically (see below), so this only settles the odd frame
     where they graze — and it stops ::before and ::after disagreeing about
     it, since the default painting order puts one either side of the
     element's own children. */
  z-index: 2;
}
/* Anchored to the *rear* of the locomotive: the right-hand end of the
   artwork, which the marker's own transform then carries to whichever side
   of the screen the train is coming from. The disc therefore changes sides
   when a service reverses at a terminus — but so does the locomotive it's
   attached to, and the alternative is worse. Pinning it to one side of the
   screen puts it over the funnel on a "forward" train, where it covers the
   steam. Only the counter-flip is needed here; the position is inherited. */
.train-marker--forward .train-marker__headcode {
  transform: scaleX(-1);
}

/* A little wisp of steam from the funnel while the engine is running.
   The puffs are children of the marker, so they ride whatever transform it
   has — which means one set of keyframes covers every direction. The
   artwork's funnel is at the low-x end, so local +x is "behind the engine"
   no matter how the marker is oriented: the desktop scaleX(-1) and both
   mobile rotations map local +x onto the side the train has just come
   from. Steam trails backwards from a moving locomotive, so that's the way
   the drift has to go. */
.train-marker--steam::before,
.train-marker--steam::after {
  content: "";
  position: absolute;
  top: 3px;
  /* Percentages of the 44px marker. The 64x40 viewBox fits by width, so the
     funnel (viewBox x 10-17) lands at roughly 16-27% and these put the
     13px puff's centre over it. */
  left: 6%;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  /* A warm grey, not the parchment cream — the previous puff colour was
     nearly identical to the page background, so it was animating but
     invisible. Kept soft-edged and short-travelling so it reads as a wisp
     off the funnel rather than a hard dot drifting away from the engine. */
  background: radial-gradient(circle, rgba(140, 132, 114, 0.55), rgba(140, 132, 114, 0) 60%);
  animation: steam-puff 2.6s ease-out infinite;
  pointer-events: none;
  /* Over the locomotive, under the headcode disc and the tooltip. Stated
     rather than left to source order, because ::before and ::after sit on
     opposite sides of the children in the default painting order and would
     otherwise disagree about which of them the disc covers. */
  z-index: 1;
}
/* The delayed puff starts a little further back along the trail, so the two
   read as one older and one newer rather than a pair leaving together. */
.train-marker--steam::after {
  left: 13%;
  animation-delay: 1.3s;
}

@keyframes steam-puff {
  0% { opacity: 0; transform: translate(0, 0) scale(0.45); }
  25% { opacity: 0.75; }
  100% { opacity: 0; transform: translate(6px, -16px) scale(1.7); }
}

/* Standing at a station there's no headwind to carry the exhaust anywhere,
   so it goes straight up. Trailing it backwards on a stopped train showed
   movement that isn't happening. */
.train-marker--steam.train-marker--dwell::before,
.train-marker--steam.train-marker--dwell::after {
  animation-name: steam-puff-still;
}

@keyframes steam-puff-still {
  0% { opacity: 0; transform: translate(0, 0) scale(0.45); }
  25% { opacity: 0.75; }
  100% { opacity: 0; transform: translate(0, -17px) scale(1.7); }
}

@media (prefers-reduced-motion: reduce) {
  .train-marker--steam::before, .train-marker--steam::after {
    animation: none;
    display: none;
  }

  /* The rest of the moving parts. A train gliding for a full second is the
     largest movement on the page and was the one thing the original
     reduced-motion block didn't cover — it now jumps straight to its new
     position. (The smooth scrolls in diagram.js check the same media query
     in JS; CSS can't reach them.) */
  .train-marker { transition: none; }
  .railway-card { transition: none; }
  .railway-card:hover { transform: none; }
  .time-explorer__chevron { transition: none; }
  .service-card__summary::after { transition: none; }
  html { scroll-behavior: auto; }
}

.train-marker__tooltip {
  position: absolute;
  bottom: 130%;
  left: 50%;
  /* Centred on the train by default. `--tip-shift` is a nudge back inside
     the diagram for a train near either end of the line, measured and set
     by diagram.js — the caption's width depends on its wording, so there's
     no fixed offset that would do. */
  transform: translateX(calc(-50% + var(--tip-shift, 0px)));
  z-index: 3;
  background: var(--heritage-green-dark);
  color: var(--parchment);
  font-size: 0.88rem;
  padding: 0.3rem 0.6rem;
  border-radius: 4px;
  /* Unwrapped, the service name plus "between X and Y · due HH:MM" ran to
     over 500px — too wide to read at a glance and wide enough to escape the
     diagram entirely. Wrap it into a couple of short lines instead. */
  width: max-content;
  max-width: 15rem;
  text-align: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease;
}

/* Counter-flip, same as the headcode digit — without this the tooltip text
   renders mirrored on a "forward" train, since the marker as a whole is
   scaleX(-1) to face its direction of travel. */
.train-marker--forward .train-marker__tooltip {
  transform: translateX(calc(-50% + var(--tip-shift, 0px))) scaleX(-1);
}

.train-marker:hover .train-marker__tooltip { opacity: 1; }

.line-diagram__empty {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 1rem;
  text-align: center;
  color: var(--muted);
  font-size: 1rem;
  font-style: italic;
  margin: 0;
  padding: 0 1rem;
}

/* ------------------------------------------------------------- departures */

.departures-section { margin-bottom: 2rem; }

.departures {
  background: var(--panel);
  border: 1px solid var(--border);
  /* Green, and the only panel on this page with it. Green means "your
     times" — the timetable header further down is the same colour — while
     brass frames everything else. With a brass top this looked exactly
     like the "Coming up" strip below it, which is the one thing on the
     page it is not. */
  border-top: 3px solid var(--heritage-green);
  border-radius: 4px;
  padding: 1rem 1.1rem 1.1rem;
}

/* Side by side where there's room, stacked where there isn't. Both are one
   question — "where are you, and where are you going" — so they read as a
   pair rather than as a control and an afterthought. */
.departures__pickers {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem 1rem;
  margin-bottom: 0.85rem;
}

.departures__picker {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  /* Each wants at least this much before wrapping, so a long station name
     never has to be read three characters at a time. */
  flex: 1 1 13rem;
  min-width: 0;
}

/* The destination is optional, and the page should not open looking as
   though there are two things to fill in before anything happens. */
.departures__optional {
  font-weight: 400;
  font-size: 0.85rem;
  color: var(--muted);
}

.departures__picker span {
  font-weight: 600;
  font-size: 0.95rem;
  color: var(--heritage-green-dark);
}

.departures__picker select {
  padding: 0.6rem 0.7rem;
  font: inherit;
  font-size: 1.05rem;
  color: var(--ink);
  background-color: var(--field-bg);
  border: 1px solid var(--border);
  border-radius: 5px;
  /* Comfortably tappable — this is the main control on the page. */
  min-height: 48px;
}

.departures__picker select:focus-visible {
  outline: 2px solid var(--brass-dark);
  outline-offset: 2px;
}


.departures__empty {
  margin: 0;
  color: var(--muted);
  font-style: italic;
  font-size: 1rem;
}

.departures__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  /* minmax(0, 1fr), not 1fr: grid tracks default to min-width:auto, which
     lets a wide row push past the container instead of its contents
     shrinking. Without this the countdown gets clipped off-screen. */
  grid-template-columns: minmax(0, 1fr);
  gap: 0.5rem;
}

.departure { min-width: 0; }

.departure {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.6rem 0.7rem;
  background: var(--parchment);
  border: 1px solid var(--border);
  border-left: 3px solid currentColor;
  border-radius: 3px;
}

.departure--c0 { color: var(--headcode-0); }
.departure--c1 { color: var(--headcode-1); }
.departure--c2 { color: var(--headcode-2); }
.departure--c3 { color: var(--headcode-3); }
.departure--c4 { color: var(--headcode-4); }

.departure__headcode {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 2px solid currentColor;
  background: var(--panel);
  font-family: "Lora", Georgia, serif;
  font-weight: 700;
  font-size: 0.95rem;
}

.departure__time {
  flex: none;
  font-family: "Playfair Display", Georgia, serif;
  font-weight: 700;
  font-size: 1.3rem;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.departure__body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.departure__towards {
  font-weight: 600;
  font-size: 1rem;
  color: var(--ink);
}

.departure__name {
  font-size: 0.88rem;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.departure__when {
  flex: none;
  font-weight: 700;
  font-size: 0.92rem;
  color: var(--maroon);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------- live board */

.live-board { margin-bottom: 1.25rem; }

.live-board__empty {
  margin: 0;
  padding: 0.85rem 1rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-left: 3px solid var(--brass);
  border-radius: 3px;
  color: var(--muted);
  font-size: 1.02rem;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

.live-board__empty-headline { font-style: italic; }

/* The useful half of the empty state — when the next train actually goes. */
.live-board__empty-detail {
  color: var(--ink);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.live-board__empty-hint {
  font-size: 0.88rem;
  font-style: italic;
  color: var(--muted);
}

.live-board__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0.5rem;
}

.live-board__item { min-width: 0; }

.live-board__item {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.7rem 0.9rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-left: 3px solid currentColor;
  border-radius: 3px;
}

/* Brief highlight when a visitor taps that train on the diagram, so the
   connection between the two is obvious. */
.live-board__item--flash {
  animation: board-flash 1.8s ease-out;
}

@keyframes board-flash {
  0%, 45% { background: var(--brass-light); }
  100% { background: var(--panel); }
}

@media (prefers-reduced-motion: reduce) {
  .live-board__item--flash { animation: none; outline: 2px solid var(--brass-dark); }
}

.live-board__item--c0 { color: var(--headcode-0); }
.live-board__item--c1 { color: var(--headcode-1); }
.live-board__item--c2 { color: var(--headcode-2); }
.live-board__item--c3 { color: var(--headcode-3); }
.live-board__item--c4 { color: var(--headcode-4); }

.live-board__headcode {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 2px solid currentColor;
  background: var(--parchment);
  font-family: "Lora", Georgia, serif;
  font-weight: 700;
  font-size: 1.05rem;
}

.live-board__body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.live-board__name {
  font-weight: 600;
  font-size: 1.05rem;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.live-board__status {
  font-size: 0.95rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.live-board__tag {
  flex: none;
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 0.25rem 0.6rem;
  border-radius: 2px;
  white-space: nowrap;
}
.live-board__tag--moving { background: var(--heritage-green); color: var(--parchment); }
.live-board__tag--dwell { background: var(--brass-light); color: var(--heritage-green-dark); }

/* ---------------------------------------------------------- time explorer */

.time-explorer {
  margin-top: 1rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 4px;
}

.time-explorer--active { border-color: var(--brass); }

/* Collapsed by default — normal use is "what's happening now", and a
   permanently visible slider implied you had to operate something. */
.time-explorer__toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  width: 100%;
  padding: 0.8rem 1.1rem;
  min-height: 48px;
  background: none;
  border: none;
  cursor: pointer;
  font: inherit;
  font-family: "Playfair Display", Georgia, serif;
  font-weight: 700;
  font-size: 1.05rem;
  color: var(--heritage-green-dark);
  text-align: left;
}

.time-explorer__toggle:hover { color: var(--maroon); }
.time-explorer__toggle:focus-visible {
  outline: 2px solid var(--brass-dark);
  outline-offset: -2px;
}

.time-explorer__chevron {
  flex: none;
  width: 9px;
  height: 9px;
  border-right: 2px solid var(--brass);
  border-bottom: 2px solid var(--brass);
  transform: rotate(45deg);
  transition: transform 0.15s ease;
}
.time-explorer__toggle[aria-expanded="true"] .time-explorer__chevron {
  transform: rotate(-135deg);
}

/* The controls sit above everything that changes as you scrub (departures,
   the live board), so reflow only ever pushes content *below* them — the
   slider stays exactly where your finger left it. Nothing floats over the
   page and no space has to be reserved. */
.time-explorer__panel {
  padding: 0.9rem 1.1rem 1rem;
  border-top: 1px solid var(--border);
  display: grid;
  /* The date field takes only the width a date needs; the slack goes to the
     empty column after "Back to live", which sits straight after it. Giving
     the field the 1fr instead stretched it across the whole panel and threw
     the button to the far side of the row, where the two read as unrelated
     controls that happened to share a line. */
  grid-template-columns: auto minmax(0, 1fr);
  align-items: end;
  gap: 0.7rem 0.9rem;
}

.time-explorer__field {
  grid-column: 1;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  margin-bottom: 0;
}

.time-explorer__field span {
  font-weight: 600;
  font-size: 0.95rem;
  color: var(--heritage-green-dark);
}

.time-explorer__field select {
  padding: 0.6rem 0.7rem;
  font: inherit;
  font-size: 1.05rem;
  color: var(--ink);
  /* background-color, not the shorthand — the shorthand would reset the
     custom dropdown arrow set on the shared select rule below. */
  background-color: var(--field-bg);
  border: 1px solid var(--border);
  border-radius: 5px;
  min-height: 48px;
}

.time-explorer__field select:focus-visible {
  outline: 2px solid var(--brass-dark);
  outline-offset: 2px;
}

/* The native dropdown arrow sits hard against the right edge and can't be
   moved, so it's replaced with a brass chevron we can inset properly. The
   extra right padding keeps long station names off it. This has to come
   after both select rules above: their `padding` shorthand would otherwise
   reset the padding-right set here. */
.departures__picker select,
.time-explorer__field select {
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%23a97b2e' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.95rem center;
  background-size: 12px 8px;
  padding-right: 2.7rem;
}

/* Matches the select above, minus the custom chevron — a date input brings
   its own calendar button, and overriding the appearance would take that
   away along with the whole native picker. */
.time-explorer__field input[type="date"] {
  padding: 0.6rem 0.7rem;
  font: inherit;
  font-size: 1.05rem;
  color: var(--ink);
  background-color: var(--field-bg);
  border: 1px solid var(--border);
  border-radius: 5px;
  min-height: 48px;
  /* iOS Safari otherwise renders date inputs at the OS width, ignoring the
     grid column and pushing the panel wide enough to scroll the page. */
  max-width: 100%;
}

.time-explorer__field input[type="date"]:focus-visible {
  outline: 2px solid var(--brass-dark);
  outline-offset: 2px;
}

/* Most heritage lines are shut on any given day, so the next few dates
   something actually runs saves a lot of poking at the calendar. */
.time-explorer__shortcuts {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.time-explorer__shortcuts-label {
  font-size: 0.9rem;
  color: var(--muted);
}

.date-chip {
  padding: 0.35rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--field-bg);
  font: inherit;
  font-size: 0.9rem;
  color: var(--maroon);
  cursor: pointer;
  /* Comfortably tappable without making the row of chips tall. */
  min-height: 36px;
}
.date-chip:hover { border-color: var(--brass); }
.date-chip:focus-visible {
  outline: 2px solid var(--brass-dark);
  outline-offset: 2px;
}

.time-explorer__none {
  grid-column: 1 / -1;
  margin: 0;
  color: var(--muted);
  font-style: italic;
}

.time-explorer__time { grid-column: 1 / -1; }

.time-explorer__panel .button--ghost {
  grid-column: 2;
  grid-row: 1;
  justify-self: start;
  margin-top: 0;
  align-self: end;
  /* Squared off against the date field it stands beside — a 35px button
     against a 48px field read as a mismatched pair. inline-flex because the
     inherited inline-block would leave the label at the top of that height. */
  display: inline-flex;
  align-items: center;
  min-height: 48px;
}

.time-explorer__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 0.35rem;
}

.time-explorer__label {
  font-family: "Playfair Display", Georgia, serif;
  font-weight: 700;
  font-size: 1.1rem;
  color: var(--heritage-green-dark);
}

.time-explorer__readout {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: 1.15rem;
  color: var(--maroon);
}

.time-explorer__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-top: 0.4rem;
  font-size: 0.9rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.time-explorer__slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  margin: 0;
  background: transparent;
  cursor: pointer;
}

.time-explorer__slider::-webkit-slider-runnable-track {
  height: 6px;
  border-radius: 3px;
  background: linear-gradient(var(--brass), var(--brass)) border-box;
  border: 1px solid var(--border);
}
.time-explorer__slider::-moz-range-track {
  height: 6px;
  border-radius: 3px;
  background: var(--brass);
  border: 1px solid var(--border);
}

/* Deliberately large (28px) — this is the primary touch target on a phone,
   and a fiddly thumb makes the whole feature frustrating on mobile. */
.time-explorer__slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 28px;
  height: 28px;
  margin-top: -12px;
  border-radius: 50%;
  background: var(--heritage-green);
  border: 3px solid var(--brass-light);
  box-shadow: 0 1px 3px rgba(43, 32, 19, 0.35);
}
.time-explorer__slider::-moz-range-thumb {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--heritage-green);
  border: 3px solid var(--brass-light);
  box-shadow: 0 1px 3px rgba(43, 32, 19, 0.35);
}

.time-explorer__slider:focus-visible { outline: 2px solid var(--brass-dark); outline-offset: 4px; }

/* -------------------------------------------------------------- timetable */

.timetable-scroll { overflow-x: auto; border: 1px solid var(--border); border-radius: 8px; }
/* It's focusable so it can be scrolled from the keyboard, so it needs to
   show that it has focus. */
.timetable-scroll:focus-visible {
  outline: 2px solid var(--brass-dark);
  outline-offset: 2px;
}

.timetable {
  border-collapse: collapse;
  width: 100%;
  background: var(--panel);
  font-variant-numeric: tabular-nums;
}

.timetable th, .timetable td {
  padding: 0.7rem 1rem;
  border-bottom: 1px solid var(--border);
  text-align: center;
  white-space: nowrap;
  font-size: 1.02rem;
}

.timetable thead th {
  background: var(--heritage-green);
  color: var(--parchment);
  font-family: "Playfair Display", serif;
  font-weight: 600;
  font-size: 0.98rem;
}

.timetable__station-col { text-align: left !important; }
.timetable tbody th { text-align: left; font-weight: 600; background: rgba(184, 134, 11, 0.08); }
.timetable tbody tr:nth-child(even) { background: rgba(184, 134, 11, 0.04); }

.timetable__service-col--reverse { border-bottom: 2px solid var(--maroon); }
.timetable__service-col--forward { border-bottom: 2px solid var(--heritage-green-dark); }

/* Says in words what the border colour says in colour. Light brass, not
   --muted: this sits in the dark green column header, where the muted brown
   used everywhere else on parchment is barely legible. Matches the "runs"
   line below it, which is the established treatment for secondary text in
   this header (5.1:1 against the green). */
.timetable__direction {
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--brass-light);
  white-space: nowrap;
}

/* Matches the numbered disc on that service's train marker on the diagram
   above, so a visitor can tell at a glance which column a moving train
   belongs to. */
.timetable__headcode {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--panel);
  border: 2px solid currentColor;
  font-family: "Lora", Georgia, serif;
  font-size: 0.88rem;
  font-weight: 700;
  vertical-align: middle;
  margin-right: 0.4rem;
}
/* ------------------------------------------------- mobile service cards */

/* The accordion is the mobile presentation of the same data as the grid
   above; the media query at the end of this file swaps which one shows. */
.service-accordion { display: none; }

.service-card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-left: 3px solid var(--brass);
  border-radius: 3px;
  margin-bottom: 0.6rem;
}

.service-card__summary {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.75rem 0.85rem;
  cursor: pointer;
  list-style: none;
  min-height: 48px;
}
.service-card__summary::-webkit-details-marker { display: none; }

.service-card__summary:focus-visible {
  outline: 2px solid var(--brass-dark);
  outline-offset: -2px;
}

.service-card__headcode {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 2px solid currentColor;
  background: var(--parchment);
  font-family: "Lora", Georgia, serif;
  font-weight: 700;
  font-size: 0.95rem;
}

.service-card__title {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.service-card__name { font-weight: 600; color: var(--ink); }

.service-card__span {
  font-size: 0.9rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* A twisty on the right, rotating when the card opens. */
.service-card__summary::after {
  content: "";
  flex: none;
  width: 9px;
  height: 9px;
  border-right: 2px solid var(--brass);
  border-bottom: 2px solid var(--brass);
  transform: rotate(45deg);
  transition: transform 0.15s ease;
}
.service-card[open] .service-card__summary::after { transform: rotate(-135deg); }

.service-card__stops {
  list-style: none;
  margin: 0;
  padding: 0 0.85rem 0.85rem;
}

/* Grid, not flex with space-between: a long station name needs to wrap
   inside its own column rather than spilling underneath the times. */
.service-card__stop {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 0.75rem;
  padding: 0.45rem 0;
  border-top: 1px solid var(--border);
}

.service-card__stop-name { font-weight: 600; color: var(--heritage-green-dark); }

.service-card__stop-times {
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Visually hidden but still read aloud — the compact "11:10–11:30" label
   is unambiguous to a sighted reader in context, but screen readers get
   the spelled-out version. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.timetable__headcode--c0 { color: var(--headcode-0); }
.timetable__headcode--c1 { color: var(--headcode-1); }
.timetable__headcode--c2 { color: var(--headcode-2); }
.timetable__headcode--c3 { color: var(--headcode-3); }
.timetable__headcode--c4 { color: var(--headcode-4); }

.timetable__route {
  white-space: normal;
  max-width: 16rem;
  margin: 0.35rem auto 0;
  font-family: "Lora", Georgia, serif;
  font-style: italic;
  font-weight: 400;
  font-size: 0.85rem;
  line-height: 1.4;
  color: var(--brass-light);
  opacity: 1;
}

.timetable__seq {
  display: inline-block;
  min-width: 1.4em;
  padding: 0.05em 0.25em;
  margin-right: 0.35em;
  border-radius: 3px;
  background: var(--brass-dark);
  color: #fff;
  font-size: 0.82rem;
  font-weight: 700;
  vertical-align: middle;
}

/* -------------------------------------------------------------------- ui */

.empty-note { color: var(--muted); font-style: italic; }

.button {
  display: inline-block;
  padding: 0.55rem 1.1rem;
  border-radius: 5px;
  text-decoration: none;
  font-weight: 600;
  border: none;
  cursor: pointer;
  font-size: 0.95rem;
}

.button--primary {
  background: var(--heritage-green);
  color: var(--parchment);
}
.button--primary:hover { background: var(--heritage-green-dark); color: var(--brass-light); }

.button--ghost {
  background: transparent;
  color: var(--maroon);
  border: 1px solid var(--border);
  /* Kept at a comfortable tap size rather than shrunk to match its small
     label — it sits between two bound labels and needs to stay hittable. */
  padding: 0.45rem 0.9rem;
  font-size: 0.92rem;
}
.button--ghost:hover { border-color: var(--brass); color: var(--brass); }

.link-button {
  background: none;
  border: none;
  padding: 0;
  color: var(--maroon);
  text-decoration: underline;
  cursor: pointer;
  font: inherit;
}
.link-button--danger { color: var(--error); }
.link-button--armed {
  color: #fff;
  background: var(--error);
  padding: 0.15rem 0.5rem;
  border-radius: 4px;
  text-decoration: none;
  font-weight: 600;
}
.inline-form { display: inline; }

.form { display: flex; flex-direction: column; gap: 1rem; max-width: 480px; }

/* For forms built around a table — the stops grid on the service form is
   wider than the default column, so the form has to match it or the
   submit button ends up visibly narrower than the table above it. */
.form--wide { max-width: none; }

/* min-width:0 so a wide child (the stops table) can be scrolled inside its
   box instead of forcing the whole form wider — flex items default to
   min-width:auto and refuse to shrink below their content. */
.form__field { display: flex; flex-direction: column; gap: 0.3rem; min-width: 0; }
.form__field span, fieldset legend { font-weight: 600; font-size: 0.9rem; }
fieldset legend { margin-bottom: 0.6rem; }
.form__hint { color: var(--muted); font-size: 0.85rem; margin-top: -0.5rem; }

/* The negative margin above tightens a hint that follows an input; after a
   legend it dragged the text up over the heading instead. */
legend + .form__hint { margin-top: 0; }

fieldset { border: none; padding: 0; margin: 0; }

/* Listed by type rather than by a bare `input` so checkboxes, radios and
   the date fields (which have their own widget) are left alone. `url` and
   `tel` join the list because the railway-details form uses them for the
   website and phone fields — without them those two render as raw browser
   boxes in the middle of a themed form. */
input[type="text"], input[type="password"], input[type="number"],
input[type="url"], input[type="tel"], input[type="email"], textarea {
  padding: 0.5rem 0.65rem;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--field-bg);
  font: inherit;
  color: var(--ink);
}

input:focus, textarea:focus { outline: 2px solid var(--brass-dark); outline-offset: 1px; }

.checkbox-row { display: flex; flex-wrap: wrap; gap: 0.75rem; }

/* Wider than the plain checkbox row: each choice is a picture and a word,
   so they need room to stay one line rather than wrapping the label under
   its own engine. */
.traction-row { gap: 1.5rem; }

.traction-choice { gap: 0.5rem; }

/* Roughly the size the diagram draws them at, so this is a preview and not
   a suggestion. --ink rather than a headcode colour: on the diagram the
   colour says which working a train is on, and borrowing one here would
   imply this control sets that too. */
.traction-choice__loco {
  width: 40px;
  height: 25px;
  color: var(--ink);
  flex: none;
}

/* The list view only needs to be scannable down a column of names, so it
   sits at text size and takes the row's own colour. */
.traction-mark {
  width: 26px;
  height: 16px;
  vertical-align: -0.15em;
  margin-right: 0.35rem;
  color: var(--muted);
}
.checkbox { display: flex; align-items: center; gap: 0.4rem; font-weight: 500; }

.auth-card {
  max-width: 380px;
  margin: 2rem auto;
  background: var(--panel);
  border: 1px solid var(--border);
  border-top: 4px solid var(--brass);
  border-radius: 8px;
  padding: 2rem;
  text-align: center;
}
.auth-card .form { margin: 0 auto; text-align: left; }
.auth-card__lead { color: var(--muted); margin-bottom: 1.25rem; }
.auth-card__note { margin-top: 1.25rem; font-size: 0.85rem; }

.admin-panel__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1.25rem;
  gap: 1rem;
  flex-wrap: wrap;
}

.dashboard-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 1rem;
}

.dashboard-card {
  display: block;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 1.1rem;
  text-decoration: none;
  color: var(--ink);
}
.dashboard-card:hover { border-color: var(--brass); }
.dashboard-card p { color: var(--muted); margin: 0; font-size: 0.88rem; }

.admin-table {
  border-collapse: collapse;
  width: 100%;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
}
.admin-table th, .admin-table td {
  padding: 0.6rem 0.9rem;
  border-bottom: 1px solid var(--border);
  text-align: left;
}
.admin-table thead th {
  background: var(--heritage-green);
  color: var(--parchment);
  font-size: 0.82rem;
}
.admin-table-scroll {
  overflow-x: auto;
  border-radius: 8px;
}

/* ------------------------------------------------------------ site stats */

.stat-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 1rem;
  margin-bottom: 1.25rem;
}

.stat-tile {
  background: var(--panel);
  border: 1px solid var(--border);
  border-top: 3px solid var(--brass);
  border-radius: 6px;
  padding: 0.9rem 1rem;
}

.stat-tile__value {
  display: block;
  font-family: "Playfair Display", Georgia, serif;
  font-size: 1.9rem;
  line-height: 1.1;
  color: var(--heritage-green-dark);
}
.stat-tile__value small {
  font-size: 0.95rem;
  color: var(--muted);
}

.stat-tile__label {
  display: block;
  margin-top: 0.2rem;
  font-size: 0.85rem;
  color: var(--muted);
}

.period-picker {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 1rem;
}
.period-picker__label { font-size: 0.9rem; color: var(--muted); }

.period-picker__option {
  padding: 0.35rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 0.9rem;
  text-decoration: none;
  color: var(--maroon);
}
.period-picker__option:hover { border-color: var(--brass); }
.period-picker__option.is-selected {
  background: var(--heritage-green);
  border-color: var(--heritage-green);
  color: var(--parchment);
}

.sparkline {
  display: block;
  color: var(--heritage-green);
  /* preserveAspectRatio="none" lets the line stretch to the column, so a
     narrow table doesn't letterbox it. */
  width: 110px;
  height: 26px;
}

.stat-none { color: var(--muted); font-style: italic; }

.admin-table__num { text-align: right; font-variant-numeric: tabular-nums; }
.admin-table td.admin-table__strong { font-weight: 600; }

/* The stats columns took the superadmin table from five columns to ten, at
   which point the browser started breaking every two-word heading, "Reset
   password" and even the railway names across lines to claw back width.
   None of those read well stacked, so this variant stops all wrapping and
   buys the room back with tighter cells instead — and .admin-table-scroll
   handles whatever is still left over on a narrow screen. */
.admin-table--wide th,
.admin-table--wide td { padding: 0.6rem; white-space: nowrap; }
.admin-table--wide .sparkline { width: 84px; }

/* Even tightened, ten columns don't fit the 1100px the rest of the site is
   held to, and the page container caps out well before the screen does — so
   the Delete link sat behind a scrollbar at every desktop size. This is an
   internal data table, not a reading column, so let this one page use the
   width it needs. */
body.page--wide .site-main { max-width: 1320px; }

.stat-footnote {
  margin: 1rem 0 0;
  font-size: 0.85rem;
  color: var(--muted);
  max-width: 46rem;
}

/* Which date the timetable below is showing. Without it the table silently
   changes contents when the explore panel moves, with nothing saying why. */
.timetable__when {
  margin: -0.5rem 0 1rem;
  color: var(--muted);
  font-style: italic;
}

/* When each timetable column actually applies. Normal-weight and quiet, so
   a whole row of "Daily" doesn't compete with the service names, but the
   date-limited ones are exactly what a visitor needs to notice. */
.timetable__runs {
  display: block;
  margin-top: 0.25rem;
  font-weight: 400;
  font-size: 0.8rem;
  font-style: italic;
  color: var(--brass-light);
  white-space: normal;
}
/* Inside the dark table head the brass reads well; on the parchment cards
   of the mobile accordion it doesn't, so that context gets the muted ink. */
.service-card .timetable__runs {
  color: var(--muted);
  font-size: 0.85rem;
}

/* ------------------------------------------------- timetable calendar UI */

.form__optional {
  font-weight: 400;
  font-size: 0.85rem;
  color: var(--muted);
  font-style: italic;
}

/* Season bounds and the one-off date rows share the field styling with the
   rest of the admin forms; only the layout differs. */
.date-range {
  display: flex;
  flex-wrap: wrap;
  gap: 0.9rem;
}
.date-range__field {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  margin-bottom: 0;
}
.date-range__field span {
  font-size: 0.9rem;
  color: var(--muted);
}

.extra-dates {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-bottom: 0.7rem;
}
.extra-dates__row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.date-range input[type="date"],
.extra-dates input[type="date"],
.closure-form input[type="date"] {
  padding: 0.55rem 0.7rem;
  font: inherit;
  color: var(--ink);
  background-color: var(--field-bg);
  border: 1px solid var(--border);
  border-radius: 5px;
  min-height: 46px;
}

/* Runs straight on from the services table above it, which reads as one
   undifferentiated block without a rule between them. */
.export-panel {
  margin-top: 2.25rem;
  padding-top: 1.75rem;
  border-top: 1px solid var(--border);
}

/* Same shape as the closure form: a date and its button on one line,
   wrapping to two when there isn't room. */
.export-form__fields {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.9rem;
}
.export-form__fields .form__field { margin-bottom: 0; }
.export-form__fields .button {
  /* Squared off against the date field beside it, as in the explore panel. */
  display: inline-flex;
  align-items: center;
  min-height: 46px;
}
.export-form .form__hint { margin-top: 0.8rem; }

.closure-form__fields {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.9rem;
}
.closure-form__fields .form__field {
  flex: 1 1 12rem;
  margin-bottom: 0;
}
.closure-form { margin-bottom: 1.75rem; }

/* The row of tools under the stops grid. Wraps rather than scrolls: on a
   phone the shift control drops below the Add stop button instead of
   pushing it off the edge. */
.stop-tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.9rem;
  margin-top: 0.9rem;
}

.stop-shift {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.45rem;
  font-size: 0.9rem;
  color: var(--muted);
}
.stop-shift label { font-weight: 600; }
.stop-shift input {
  width: 5rem;
  padding: 0.4rem 0.5rem;
  text-align: right;
}
/* Matches the touch target the rest of the admin area uses, so these are
   tappable on the phone this feature exists for. */
.stop-shift .button { min-height: 44px; }

#shift-status:empty { display: none; }

/* Small status pills in the services list — "is this running today" and
   "has this quietly expired" are the two questions a railway asks of that
   page, and both were previously unanswerable without doing date maths. */
.tag {
  display: inline-block;
  margin-left: 0.4rem;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 600;
  white-space: nowrap;
}
.tag--today {
  background: var(--success-bg);
  color: var(--heritage-green-dark);
}
.tag--warn {
  background: var(--error-bg);
  color: var(--error);
}

/* ------------------------------------- a railway's own stats: daily chart */

.stats-chart__heading {
  font-size: 1.1rem;
  margin: 1.75rem 0 0.6rem;
}

.stats-chart {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 1rem 1rem 0.7rem;
}

.stats-chart__bars {
  display: flex;
  align-items: flex-end;
  /* Gaps rather than margins so 90 bars still add up to exactly 100% of the
     row, and a proportional gap so a 7-day week doesn't look like a picket
     fence while 90 days turns solid. */
  gap: max(1px, calc(0.6 / var(--bar-count) * 100%));
  height: 150px;
}

.stats-chart__bar {
  flex: 1 1 0;
  min-width: 0;
  height: var(--height);
  background: var(--heritage-green);
  border-radius: 2px 2px 0 0;
}
/* Heritage railways mostly run at weekends, so the running pattern should be
   readable off the chart rather than needing to be counted out. */
.stats-chart__bar.is-weekend { background: var(--brass); }

/* A day with no traffic still needs to occupy its slot, or the remaining
   bars stretch and the dates stop lining up with the axis. */
.stats-chart__bar.is-empty {
  height: 2px;
  background: var(--border);
}

.stats-chart__axis {
  display: flex;
  justify-content: space-between;
  gap: 0.5rem;
  margin-top: 0.5rem;
  font-size: 0.8rem;
  color: var(--muted);
}
.stats-chart__key { font-style: italic; }

.stats-empty {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 1.25rem 1.4rem;
  max-width: 46rem;
}
.stats-empty h2 { margin: 0 0 0.5rem; font-size: 1.1rem; }
.stats-empty p { margin: 0 0 0.6rem; color: var(--muted); }
.stats-empty p:last-child { margin-bottom: 0; }

/* The chart is decorative duplication of numbers that also need to be
   readable by a screen reader, so the table stays in the document and is
   taken out of the visual layout only. `display: none` would remove it from
   the accessibility tree too, which defeats the point. */
.visually-hidden-table {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

@media (max-width: 640px) {
  /* The stacked card layout puts the label on the left and the value on the
     right, so right-aligning the numbers is what keeps them off the label. */
  .admin-table td.admin-table__num { text-align: right; }
  .sparkline { width: 100px; margin-left: auto; }
}

/* ------------------------------------------------------------- poster QR */

.admin-panel__intro {
  color: var(--muted);
  max-width: 46rem;
  margin: -0.5rem 0 1.75rem;
}

.qr-layout {
  display: grid;
  /* minmax(0, 1fr) so the long URL can't force the column wider than the
     page — it wraps instead. */
  grid-template-columns: auto minmax(0, 1fr);
  gap: 2rem;
  align-items: start;
}

.qr-card {
  margin: 0;
  background: #fff;
  border: 1px solid var(--border);
  border-top: 3px solid var(--brass);
  border-radius: 8px;
  padding: 1rem 1rem 0.85rem;
  text-align: center;
}

/* The code itself is deliberately plain black on white — see qr.py. The card
   around it carries the theming instead, so nothing interferes with the scan. */
.qr-card__code {
  display: block;
  width: 240px;
  max-width: 100%;
  height: auto;
}

.qr-card__caption {
  margin-top: 0.6rem;
  font-family: "Playfair Display", Georgia, serif;
  font-size: 0.95rem;
  color: var(--ink);
}

.qr-details h2 {
  font-size: 1.05rem;
  margin: 0 0 0.5rem;
}
.qr-details h2 + p { margin-top: 0; }
.qr-details > * + h2 { margin-top: 1.75rem; }

.qr-url {
  margin: 0 0 0.35rem;
  font-size: 1.05rem;
  /* Long slugs would otherwise push the grid track wide on a phone. */
  overflow-wrap: anywhere;
}

.qr-downloads {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-bottom: 0.6rem;
}

.qr-tips {
  margin: 0;
  padding-left: 1.2rem;
  color: var(--muted);
  line-height: 1.55;
}
.qr-tips li + li { margin-top: 0.5rem; }
.qr-tips strong { color: var(--ink); }

@media (max-width: 720px) {
  .qr-layout {
    grid-template-columns: minmax(0, 1fr);
    gap: 1.5rem;
  }
  .qr-card { justify-self: center; }
}

/* Laid out inline rather than with flex. A `td` set to `display: flex`
   stops being a table cell — the browser wraps it in an anonymous one, so it
   no longer stretches to the row height and draws its bottom border partway
   up the row. That was invisible while every row was a single line tall, and
   showed up the moment a "runs on" label wrapped to three lines: the
   Edit/Delete column kept its own, shorter rows. */
.admin-table__actions { white-space: nowrap; }
.admin-table__actions > * + * { margin-left: 0.75rem; }
.admin-table--stops td, .admin-table--stops th { text-align: left; }
.admin-table--stops input { width: 6.5rem; }

.flash-list { margin-bottom: 1.5rem; display: flex; flex-direction: column; gap: 0.5rem; }
.flash {
  padding: 0.6rem 0.9rem;
  border-radius: 5px;
  font-size: 0.9rem;
  font-weight: 500;
}
.flash--success { background: var(--success-bg); color: var(--success); }
.flash--error { background: var(--error-bg); color: var(--error); }

/* The description reads as a plain paragraph on desktop — the disclosure
   only exists to collapse it on small screens. */
.railway-header__about { border: none; }
.railway-header__about > summary { display: none; }
/* ------------------------------------------------------------ date picker */

/* Built by datepicker.js, which hides the native `input type="date"` and
   puts these in its place. The rules above for `input[type="date"]` are the
   no-JS fallback and still apply when this script hasn't run. */

.datepicker {
  position: relative;
  display: block;
  width: 11.5rem;
  max-width: 100%;
}

/* The closure form's date lines up with the "Reason" box beside it, so it
   takes the full width of its column. Everywhere else a date field is sized
   to the date it holds. */
.closure-form .datepicker { width: 100%; }

/* Qualified with the element on purpose. The shared
   `input[type="text"], ...` rule further up is an attribute selector and so
   outranks a bare class — it was quietly winning the padding and the leading
   here, which let a date slide under the calendar button. Matching its
   specificity is enough; this rule comes later in the file. */
input.datepicker__input {
  width: 100%;
  /* Right padding clears the calendar button sitting inside the field. */
  padding: 0.55rem 3rem 0.55rem 0.7rem;
  font: inherit;
  /* The page's 1.65 body leading would push the box past its min-height and
     make the exact height depend on the font size, which is awkward for
     anything that has to stand level with it. Below the minimum, the
     min-height decides and the field is a known 46px (48 in the explore
     panel) — inputs centre their own single line of text regardless. */
  line-height: 1.2;
  color: var(--ink);
  background: var(--field-bg);
  border: 1px solid var(--border);
  border-radius: 5px;
  min-height: 46px;
}

/* Matches the neighbouring station select, which is a size up from the
   admin forms. */
.time-explorer__field .datepicker__input {
  font-size: 1.05rem;
  min-height: 48px;
  padding-top: 0.6rem;
  padding-bottom: 0.6rem;
}

.datepicker__button {
  position: absolute;
  top: 1px;
  right: 1px;
  bottom: 1px;
  width: 2.6rem;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  border-left: 1px solid var(--border);
  border-radius: 0 4px 4px 0;
  color: var(--brass);
  cursor: pointer;
}

.datepicker__button svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
}

.datepicker__button:hover {
  background: rgba(169, 123, 46, 0.14);
  color: var(--heritage-green-dark);
}

.datepicker__button:focus-visible {
  outline: 2px solid var(--brass-dark);
  outline-offset: -2px;
}

.datepicker__pop {
  position: absolute;
  z-index: 40;
  top: calc(100% + 0.4rem);
  left: 0;
  width: 19rem;
  max-width: calc(100vw - 2rem);
  padding: 0.75rem;
  background: var(--panel);
  border: 1px solid var(--brass);
  border-radius: 6px;
  box-shadow: 0 12px 30px rgba(43, 32, 19, 0.24);
}

/* Set by datepicker.js only when the calendar genuinely won't fit below —
   see position(), which also nudges `left` inline to keep it on screen. */
.datepicker__pop--above { top: auto; bottom: calc(100% + 0.4rem); }

.datepicker__head {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin-bottom: 0.55rem;
}

.datepicker__month {
  flex: 1;
  text-align: center;
  font-family: "Playfair Display", Georgia, serif;
  font-weight: 700;
  font-size: 1.05rem;
  color: var(--heritage-green-dark);
}

.datepicker__nav {
  width: 2.1rem;
  height: 2.1rem;
  display: flex;
  align-items: center;
  justify-content: center;
  /* The page's serif draws these guillemets as hairlines at this size. */
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 1.4rem;
  font-weight: 700;
  line-height: 1;
  background: transparent;
  color: var(--heritage-green);
  border: 1px solid var(--border);
  border-radius: 4px;
  cursor: pointer;
}

.datepicker__nav:hover:not(:disabled) {
  background: var(--heritage-green);
  border-color: var(--heritage-green);
  color: var(--panel);
}

.datepicker__nav:disabled { opacity: 0.3; cursor: default; }

.datepicker__dow,
.datepicker__grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 2px;
}

.datepicker__dow { margin-bottom: 0.3rem; }

.datepicker__dow span {
  text-align: center;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.datepicker__day {
  min-height: 2.25rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font: inherit;
  font-size: 0.95rem;
  color: var(--ink);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 4px;
  cursor: pointer;
}

.datepicker__day:hover:not(:disabled) {
  background: var(--brass-light);
  color: var(--heritage-green-dark);
}

.datepicker__day:focus-visible {
  outline: 2px solid var(--brass-dark);
  outline-offset: -1px;
}

/* The days either side of the month are kept selectable rather than blanked
   out — reaching the 1st of next month shouldn't need the arrow. */
.datepicker__day--adjacent { color: var(--muted); opacity: 0.6; }

.datepicker__day--today {
  border-color: var(--brass);
  font-weight: 700;
}

.datepicker__day--selected,
.datepicker__day--selected:hover {
  background: var(--heritage-green);
  border-color: var(--heritage-green);
  color: var(--panel);
  font-weight: 700;
  opacity: 1;
}

.datepicker__day:disabled {
  color: var(--muted);
  opacity: 0.3;
  cursor: default;
}

.datepicker__foot {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.6rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--border);
}

/* A required field inside its own season has nothing to offer here, and an
   empty bar would just be a stray rule under the calendar. */
.datepicker__foot:empty { display: none; }

.datepicker__action {
  font: inherit;
  font-size: 0.9rem;
  padding: 0.25rem 0.2rem;
  background: none;
  border: none;
  color: var(--maroon);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}

.datepicker__action:hover { color: var(--brass); }
.datepicker__action--clear { margin-left: auto; color: var(--muted); }

.datepicker__action:focus-visible {
  outline: 2px solid var(--brass-dark);
  outline-offset: 1px;
  border-radius: 3px;
}
/* --------------------------------------------------- multi-date calendar */

/* The one-off dates picker on the service form (date_calendar.js). Reuses
   the single picker's grid classes deliberately — the two calendars on this
   site should look like the same calendar. */

.datecal {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 1.25rem;
  margin-bottom: 0.8rem;
}

.datecal__cal {
  flex: 0 0 auto;
  width: 20rem;
  max-width: 100%;
  padding: 0.75rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 6px;
}

.datecal__head {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin-bottom: 0.55rem;
}

/* Selecting a whole column is what makes a season's worth of dates
   bearable to enter: "every Sunday in September", then untick the two that
   don't run. */
.datecal__bulk {
  margin-top: 0.7rem;
  padding-top: 0.7rem;
  border-top: 1px solid var(--border);
}

.datecal__bulk-label {
  font-size: 0.8rem;
  color: var(--muted);
  margin-bottom: 0.35rem;
}

/* The same seven columns as the calendar above, so each button sits
   directly under the column of dates it takes. */
.datecal__cols {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 2px;
}

.datecal__col {
  padding: 0.3rem 0.1rem;
  font: inherit;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--heritage-green);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 4px;
  cursor: pointer;
}

.datecal__col:hover {
  background: var(--heritage-green);
  border-color: var(--heritage-green);
  color: var(--panel);
}

.datecal__col:focus-visible,
.datecal__clear-month:focus-visible {
  outline: 2px solid var(--brass-dark);
  outline-offset: 1px;
}

.datecal__clear-month {
  display: block;
  margin: 0.5rem 0 0 auto;
  font-size: 0.82rem;
}

.datecal__chosen {
  flex: 1 1 15rem;
  min-width: 0;
}

.datecal__count {
  margin: 0 0 0.6rem;
  font-weight: 600;
  font-size: 0.92rem;
  color: var(--heritage-green-dark);
}

.datecal__months {
  list-style: none;
  margin: 0 0 0.7rem;
  padding: 0;
}

/* Grouped by month with bare day numbers, which is how a railway prints
   its own season ("June 7, 14, 28"). Two dozen "Sunday 7 June" chips is a
   wall of text nobody can check at a glance. */
.datecal__month-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.3rem;
  padding: 0.35rem 0;
  border-bottom: 1px solid var(--border);
}

.datecal__month-name {
  flex: 0 0 5.2rem;
  font-weight: 600;
  font-size: 0.88rem;
  color: var(--muted);
}

.datecal__chip {
  display: inline-flex;
  align-items: center;
  gap: 0.15rem;
  padding: 0.15rem 0.35rem 0.15rem 0.45rem;
  font: inherit;
  font-size: 0.85rem;
  color: var(--heritage-green-dark);
  background: var(--field-bg);
  border: 1px solid var(--border);
  border-radius: 4px;
  cursor: pointer;
}

.datecal__chip:hover {
  border-color: var(--maroon);
  color: var(--maroon);
}

.datecal__chip:focus-visible {
  outline: 2px solid var(--brass-dark);
  outline-offset: 1px;
}

.datecal__chip-x {
  font-size: 0.95rem;
  line-height: 1;
  color: var(--muted);
}

.datecal__chip:hover .datecal__chip-x { color: var(--maroon); }

/* Cancelled: the weekly pattern would run this day and the railway has
   struck it out. Deliberately different from a plain unselected day —
   "we chose not to run" is information, and it's what stops a gap in a
   season being forgotten and then advertised by mistake. */
.datecal__day--skip {
  color: var(--error);
  background: var(--error-bg);
  border-color: var(--error-bg);
  text-decoration: line-through;
  text-decoration-thickness: 2px;
}

.datecal__day--skip:hover:not(:disabled) {
  background: var(--error-bg);
  color: var(--error);
}

.datecal__legend {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin: 0.6rem 0 0;
  font-size: 0.8rem;
  color: var(--muted);
}

.datecal__key {
  display: inline-block;
  width: 0.85rem;
  height: 0.85rem;
  border-radius: 3px;
  vertical-align: -1px;
}
.datecal__key--runs { background: var(--heritage-green); }
.datecal__key--skip { background: var(--error-bg); border: 1px solid var(--error); margin-left: 0.6rem; }

.datecal__list + .datecal__list { margin-top: 0.9rem; }
.datecal__count--empty { font-weight: 400; color: var(--muted); }

.datecal__chip--skip {
  color: var(--error);
  background: var(--error-bg);
  border-color: #e6c9c9;
}
.datecal__chip--skip .datecal__chip-x { color: var(--error); }

/* The no-JavaScript fallback: the two plain date lists the calendar
   replaces. Only ever visible if date_calendar.js didn't run. */
.date-fallback {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem;
}
.date-fallback__group { flex: 1 1 14rem; }
.date-fallback__label {
  margin: 0 0 0.4rem;
  font-weight: 600;
  font-size: 0.9rem;
  color: var(--muted);
}


/* ---------------------------------------------------------------- notices */

/* A standing banner, as opposed to .flash which reports something that just
   happened and goes away on the next page. This one stays until the state
   it describes changes. */
.notice {
  margin: 0 0 1.25rem;
  padding: 0.8rem 1rem;
  border-radius: 5px;
  border-left: 4px solid var(--brass);
  background: var(--panel);
  font-size: 0.95rem;
  line-height: 1.5;
}

.notice--warn {
  background: var(--error-bg);
  border-left-color: var(--error);
  color: var(--error);
}
.notice--warn strong { color: var(--error); }
.notice--warn a { color: var(--error); }

/* The railway's own notice, above everything including its name — a
   cancelled train outranks a tagline. Slightly heavier than a plain notice
   because it is the one thing on the page that might change someone's
   afternoon. */
.railway-notice {
  font-weight: 600;
}
.railway-notice[hidden] { display: none; }

/* "Last train from X: 16:45". Set apart from the departure rows above it —
   it answers a different question from them. */
.departures__last {
  margin: 0.85rem 0 0;
  padding-top: 0.75rem;
  border-top: 1px dashed var(--border);
  color: var(--muted);
  font-size: 0.92rem;
  font-weight: 600;
}
.departures__last[hidden] { display: none; }

/* ------------------------------------------------------- planning a visit */

/* The timetable section carries no bottom margin — it was the last thing on
   the page until this section went in below it — so the spacing has to come
   from this side. 2.5rem matches the gap between the sections above. */
.visit {
  margin-top: 2.5rem;
  margin-bottom: 2.5rem;
}

.visit__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  /* Two up on a wide screen, one on a phone, without a media query. */
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 0.6rem 1.5rem;
}

.visit__item {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--border);
  /* A long URL must not push the page sideways. */
  overflow-wrap: anywhere;
}

.visit__label {
  flex: none;
  min-width: 5.5rem;
  color: var(--muted);
  font-size: 0.82rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.visit__note {
  margin-top: 0.9rem;
  color: var(--muted);
  font-size: 0.88rem;
  font-style: italic;
}

/* Sits directly above the live board it is warning about, tighter than a
   standalone notice so the two read as one block rather than two. */
.live-board__stale {
  margin-bottom: 0.6rem;
  font-weight: 600;
}
.live-board__stale[hidden] { display: none; }

/* Set apart from the text fields above it: this is the control that decides
   whether anyone can see the page at all. */
.visibility {
  padding: 0.9rem 1.1rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--panel);
}
.visibility legend {
  padding: 0 0.4rem;
  color: var(--heritage-green-dark);
}
.visibility .checkbox { font-weight: 600; }
.visibility .form__hint { margin-top: 0.5rem; }
.visibility .form__hint:last-child { margin-bottom: 0; }

@media (max-width: 640px) {
  .site-header__inner { flex-direction: column; gap: 0.5rem; align-items: flex-start; }
  .hero h1 { font-size: 2rem; }
  .page--poster .site-main { padding: 1.5rem 1rem 2.5rem; margin-top: 1rem; margin-bottom: 1rem; }
  .page--poster .site-main::before { outline-offset: 4px; }

  /* Less chrome before the useful information starts. */
  .railway-header h1 { font-size: 1.7rem; }
  .railway-header { margin-bottom: 1.5rem; padding-bottom: 1rem; }
  .ornament-rule { margin: 0.6rem 0 0.9rem; }

  .railway-header__about > summary {
    display: block;
    cursor: pointer;
    font-size: 0.92rem;
    font-weight: 600;
    color: var(--maroon);
    padding: 0.4rem 0;
    min-height: 44px;
    line-height: 2.1;
  }
  .railway-header__about > summary:focus-visible {
    outline: 2px solid var(--brass-dark);
    outline-offset: 2px;
  }

  /* Swap the wide grid for the vertical accordion — no horizontal scrolling
     and the station name always stays beside its times. */
  .timetable-scroll { display: none; }
  .service-accordion { display: block; }

  /* Admin list tables restack as cards. As a table they ran off the side of
     the screen and took the Edit/Delete column with them, so the actions
     were unreachable on a phone. */
  .admin-table,
  .admin-table tbody,
  .admin-table tr,
  .admin-table td { display: block; width: auto; }

  .admin-table thead { display: none; }

  .admin-table {
    border: none;
    background: none;
    overflow: visible;
  }

  .admin-table tr {
    background: var(--panel);
    border: 1px solid var(--border);
    border-left: 3px solid var(--brass);
    border-radius: 3px;
    padding: 0.75rem 0.9rem;
    margin-bottom: 0.6rem;
  }

  .admin-table td {
    border: none;
    padding: 0.2rem 0;
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 1rem;
  }

  .admin-table td::before {
    content: attr(data-label);
    flex: none;
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--muted);
  }

  /* The row's title: no label, larger, on its own line. Qualified with td
     to outrank the `.admin-table td` block above, which would otherwise
     win on specificity and keep these as labelled flex rows. */
  .admin-table td.admin-table__primary {
    display: block;
    font-family: "Playfair Display", Georgia, serif;
    font-size: 1.1rem;
    font-weight: 700;
    color: var(--heritage-green-dark);
    margin-bottom: 0.4rem;
  }
  .admin-table td.admin-table__primary::before { content: none; }

  .admin-table td.admin-table__actions > * + * { margin-left: 0; }
  .admin-table td.admin-table__actions {
    justify-content: flex-start;
    gap: 1.25rem;
    margin-top: 0.5rem;
    padding-top: 0.6rem;
    border-top: 1px solid var(--border);
  }
  .admin-table td.admin-table__actions::before { content: none; }

  /* The stops grid inside the service form stays a real table — it's an
     editing grid, not a list — so it keeps scrolling inside its own box. */
  .admin-table--stops,
  .admin-table--stops tbody,
  .admin-table--stops tr,
  .admin-table--stops td { display: revert; }
  .admin-table--stops thead { display: revert; }
  .admin-table--stops { border: 1px solid var(--border); background: var(--panel); }
  .admin-table--stops tr { padding: 0; margin: 0; border-radius: 0; border-left: none; }
  .admin-table--stops td { padding: 0.6rem 0.9rem; border-bottom: 1px solid var(--border); }
  .admin-table--stops td::before { content: none; }

  /* One row is too tight at this width — the countdown ends up squeezing
     the destination onto two lines. Time and countdown share the top row,
     destination and service name get the full width beneath. */
  .departure {
    display: grid;
    grid-template-columns: auto 1fr auto;
    grid-template-areas:
      "disc time when"
      "disc body body";
    align-items: baseline;
    column-gap: 0.7rem;
    row-gap: 0.15rem;
  }
  .departure__headcode { grid-area: disc; align-self: center; }
  .departure__time { grid-area: time; }
  .departure__when { grid-area: when; justify-self: end; }
  .departure__body { grid-area: body; }
  .departure__name { white-space: normal; }

  /* On a phone there isn't room to spread stations across the width without
     the labels colliding, so the whole diagram turns on its side: the track
     runs top-to-bottom and every label sits to its right, reading normally.
     Height scales with the number of stations so a long line stays legible
     instead of cramming. */
  .line-diagram {
    padding: 0;
    height: calc(5rem + var(--station-count, 5) * 62px);
    min-height: 0;
  }

  .line-diagram__track,
  .line-diagram__stations,
  .line-diagram__trains {
    left: 2.25rem;
    right: auto;
    top: 2.25rem;
    bottom: 3.25rem;
    width: 22px;
    height: auto;
    transform: none;
  }

  .line-diagram__track {
    background: repeating-linear-gradient(
      180deg,
      rgba(91, 70, 48, 0.38) 0, rgba(91, 70, 48, 0.38) 3px,
      transparent 3px, transparent 16px
    );
  }

  .line-diagram__track::before,
  .line-diagram__track::after {
    top: -5px;
    bottom: -5px;
    left: auto;
    right: auto;
    width: 3px;
    height: auto;
    box-shadow: 1px 0 0 rgba(43, 32, 19, 0.28);
  }
  .line-diagram__track::before { left: 0; }
  .line-diagram__track::after { right: 0; }

  .station-node {
    left: 50%;
    top: var(--pos);
  }

  /* Labels all sit to the right of the line — no alternating, no rotation. */
  .station-node__label,
  .station-node--above .station-node__label,
  .station-node--below .station-node__label,
  .station-node:first-child .station-node__label,
  .station-node:last-child .station-node__label {
    /* Close to the rails — the headcode discs sit on the left, so this
       side is clear and the extra width goes to longer station names. */
    left: 26px;
    right: auto;
    top: 50%;
    bottom: auto;
    transform: translateY(-50%);
    text-align: left;
    font-size: 1rem;
  }

  .train-marker {
    left: 50%;
    top: var(--pos);
  }

  /* Vertical track: rotate the loco to face along it (down = forward,
     toward increasing distance) instead of mirroring it sideways. */
  .train-marker--forward { transform: translate(-50%, -50%) rotate(-90deg); }
  .train-marker--reverse { transform: translate(-50%, -50%) rotate(90deg); }
  /* The marker is rotated, which drags its child anchors round with it, so
     the disc has to be positioned in the marker's *rotated* frame to end
     up on a consistent side of the track. Discs go on the LEFT here — the
     station names all sit to the right, and keeping the two apart stops
     a passing train crowding the name it's passing. */
  /* The marker is rotated a quarter turn, which swaps what the disc's own
     offsets do: `top` decides which side of the rails it lands on, and
     `left` slides it along the track. So `top` keeps both directions on the
     left, away from the station names, and `left` puts the disc at the
     locomotive's rear — clear of the steam, which trails from the funnel at
     the other end. */
  .train-marker--forward .train-marker__headcode {
    left: 29px;
    right: auto;
    top: -10.5px;
    bottom: auto;
    transform: rotate(90deg);
  }
  .train-marker--reverse .train-marker__headcode {
    left: 29px;
    right: auto;
    top: 33.5px;
    bottom: auto;
    transform: rotate(-90deg);
  }

  /* Hover tooltips don't exist on touch, and the rotated marker would drag
     the tooltip sideways with it — the live board above carries the same
     information in a readable form. */
  .train-marker__tooltip { display: none; }

  .line-diagram__empty { bottom: 0.7rem; font-size: 0.95rem; }

  /* Flex-wrapping pushed the disc, the name, the status and the tag onto
     four separate rows each, so two trains filled most of the screen.
     An explicit grid keeps the disc alongside the name and drops only the
     tag to a second row. */
  .live-board__item {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas:
      "disc body"
      "tag  tag";
    align-items: center;
    gap: 0.45rem 0.7rem;
    padding: 0.65rem 0.8rem;
  }
  .live-board__headcode { grid-area: disc; }
  .live-board__body { grid-area: body; }
  .live-board__tag { grid-area: tag; justify-self: start; }
  .live-board__name { white-space: normal; }

  .time-explorer__foot { flex-wrap: wrap; }

  /* The field and "Back to live" together are wider than a phone, so they
     stack rather than squeezing the date box down to nothing. */
  .time-explorer__panel { grid-template-columns: minmax(0, 1fr); }
  .time-explorer__panel .button--ghost { grid-column: 1; grid-row: auto; }

  /* A 2.25rem cell is comfortable with a mouse and fiddly with a thumb, and
     at this width the calendar may as well use the column it is in.

     Squaring the calendar off against its own field is what keeps it inside
     the poster frame: sized to the viewport instead, it sat off-centre in
     the column and ran out over the border. The floor is for the narrow
     wrapper in a one-off date row, which shares its line with a button —
     there position() slides the overhang back on screen. */
  .datepicker { width: 100%; }
  .datepicker__pop {
    width: 100%;
    min-width: 16rem;
    max-width: calc(100vw - 1.5rem);
    padding: 0.65rem;
  }
  .datepicker__day { min-height: 2.6rem; font-size: 1rem; }
}


/* ==========================================================================
   Coming up
   ==========================================================================
   One line above the timetable, not a section. It has to be findable
   without competing with the times themselves, so it borrows the brass
   left border the rest of the page uses for "look here" and nothing else.
   ========================================================================== */

.specials {
  border-left: 3px solid var(--brass);
  background: var(--panel);
  border-radius: 3px;
  padding: 0.6rem 0.9rem;
  /* Both sides. The timetable section below carries no top margin of its
     own — it used to follow the diagram directly — so a one-sided margin
     here left this sitting flush against it. 2.5rem matches the gap above,
     which the diagram section's own bottom margin collapses with. */
  margin: 2.5rem 0;
}

.specials__label {
  display: block;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--brass-dark);
  margin-bottom: 0.3rem;
}

.specials__list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.specials__item {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.15rem 0.6rem;
}

.specials__item + .specials__item { margin-top: 0.25rem; }

.specials__name { font-weight: 600; color: var(--heritage-green-dark); }

.specials__dates {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  color: var(--muted);
  font-size: 0.95rem;
}

/* Smaller than the explore panel's own chips. These sit inline after a
   name, where a full-size control would read as the main thing on the row
   rather than the date attached to it. */
.date-chip--special {
  padding: 0.2rem 0.55rem;
  font-size: 0.9rem;
  min-height: 0;
}

.specials__more { font-style: italic; }


/* ==========================================================================
   Is it running today?
   ==========================================================================
   The home page was a column of names, none of which said whether it was
   worth the drive.
   ========================================================================== */

.railway-card__status {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0.5rem 0 0;
  font-size: 0.92rem;
  color: var(--muted);
}

.railway-card__status--running {
  color: var(--heritage-green-dark);
  font-weight: 600;
}

/* Never the only carrier of the meaning — the words beside it say the same
   thing, so this survives being unable to see colour (WCAG 1.4.1). */
.railway-card__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--heritage-green);
  flex: none;
}


/* ==========================================================================
   Take it with you
   ========================================================================== */

/* A grid rather than flex so the heading and the date beneath it stay one
   block: the actions take a second column on a wide screen and drop to
   their own row on a narrow one, landing below the date line instead of
   splitting the heading from its subtitle. */
.timetable-section__head {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  gap: 0.35rem 1rem;
}

@media (max-width: 560px) {
  .timetable-section__head { grid-template-columns: 1fr; }
}

.timetable-section__head h2 { margin-bottom: 0; }

.timetable-section__titles > p { margin-bottom: 0; }

.timetable-actions {
  display: flex;
  gap: 1rem;
  margin: 0;
}

/* Quiet on purpose. These sit beside a heading and must not compete with
   it — a visitor looking for the times should see the times first, and
   find these only once they have decided to take them away. */
.timetable-actions__link {
  font-family: inherit;
  font-size: 0.95rem;
  color: var(--brass-dark);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.timetable-actions__link:hover { color: var(--heritage-green-dark); }

.timetable-actions__link:focus-visible {
  outline: 2px solid var(--brass-dark);
  outline-offset: 3px;
  border-radius: 2px;
}


/* ==========================================================================
   Print
   ==========================================================================
   Signal along a preserved line is poor to non-existent, so a sheet of
   paper is a real answer rather than a nostalgic one. What survives is the
   timetable and enough of the header to know whose it is; everything that
   depends on the page being live goes, because a printed live diagram is a
   photograph of one moment that will be wrong before the ink is dry.
   ========================================================================== */

@media print {
  /* Backgrounds and borders on paper cost ink and buy nothing. */
  :root {
    --parchment: #fff;
    --panel: #fff;
    --ink: #000;
    --muted: #333;
  }

  body,
  .site-main,
  .page--poster .site-main {
    background: #fff !important;
    color: #000;
    margin: 0;
    padding: 0;
    max-width: none;
    box-shadow: none;
  }

  .page--poster .site-main::before { display: none; }

  /* Anything that is only meaningful on a live page, or only meaningful as
     a control. */
  .site-header,
  .site-footer,
  .skip-link,
  .departures-section,
  .diagram-section,
  .time-explorer,
  .timetable-actions,
  .railway-header__about,
  .specials,
  .preview-banner {
    display: none !important;
  }

  /* The grid, not the accordion: on paper there is no viewport to be narrow,
     and a column per service is what a timetable is. The screen media query
     may have hidden one of them, so both are overridden explicitly. */
  .timetable-scroll {
    display: block !important;
    overflow: visible !important;
    border: 0;
  }

  .service-accordion { display: none !important; }

  .timetable {
    width: 100%;
    border-collapse: collapse;
    font-size: 10pt;
  }

  /* Descendants too, not just the cell. The direction arrow, the route
     summary and the "runs on" line each carry their own brass or green,
     which are chosen for contrast against parchment and come out as faint
     grey on white paper — legible on a good laser printer and gone on a
     tired inkjet at a station office. */
  .timetable th,
  .timetable td,
  .timetable th *,
  .timetable td * {
    background: #fff !important;
    color: #000 !important;
  }

  .timetable th,
  .timetable td {
    border: 1px solid #999 !important;
    padding: 3pt 5pt;
  }

  /* Reversed out of a solid brass square on screen. Inverting it would put
     white numerals on white paper, so it becomes an outlined box. */
  .timetable__seq {
    border: 0.5pt solid #000 !important;
    font-weight: 700;
  }

  /* Same reasoning for the headcode disc in the column heading. */
  .timetable__headcode {
    border: 0.5pt solid #000 !important;
  }

  /* A row of times split across a page break is unreadable. */
  .timetable tr,
  .visit__item { break-inside: avoid; }

  /* A disruption notice is exactly the thing someone needs to have seen
     before they set out, so it prints. */
  .railway-notice {
    border: 1pt solid #000;
    padding: 4pt 6pt;
  }

  a[href]::after { content: ""; }

  /* Except the railway's own links, which are the point of taking the
     sheet away in the first place. */
  .visit a[href^="http"]::after {
    content: " (" attr(href) ")";
    font-size: 9pt;
    word-break: break-all;
  }
}
