/* styles.css
   A small custom layer on top of Tailwind. It holds the pieces that are clearer
   as real CSS than as utility soup: the Underground line, the interchange dots,
   the train that slides between stops, and a few gentle reveal animations.
   British English throughout. No em dashes. */

:root {
  --ink: #1d1f25;
  --paper: #faf9f6;
  --muted: #6f7076; /* must match the muted token in assets/theme.js */
  --accent: #e31e24;
  --line: #e7e3dd;
  --tint: #f2eee7; /* a warm panel tint for an open section */
}

html {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
/* Smooth scrolling is motion, so only offer it to people who have not asked for
   less. */
@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }
}

/* A small caps kicker above a heading (sign-up, login). Defined once here so the
   .eyebrow used on those pages is a deliberate, understated label rather than
   plain red body text. Kept small and letter-spaced, not a filled badge. */
.eyebrow {
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.18em;
}

body {
  background-color: var(--paper);
  color: var(--ink);
}

[x-cloak] {
  display: none !important;
}

.hero-actions {
  align-items: stretch;
}
.hero-cta {
  display: inline-flex;
  min-width: 13.75rem;
  height: 3.5rem;
  align-items: center;
  justify-content: center;
  text-align: center;
}

/* Content loaded from data.js fades in once it is ready. */
.fade-in {
  opacity: 0;
  transition: opacity 0.5s ease;
}
.fade-in.is-ready {
  opacity: 1;
}

/* Reveal on scroll. Elements start slightly down and faded, then settle when
   they enter the viewport. Kept gentle so it reads as polish, not fireworks. */
.reveal {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity 0.7s ease, transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
}
.reveal.is-visible {
  opacity: 1;
  transform: none;
}

/* ------------------------------------------------------------------ */
/* The Underground line: track, stops, interchange dots and the train */
/* ------------------------------------------------------------------ */

.line {
  position: relative;
}

/* The base rail the train runs along. The dots and the train are centred on
   x = 27px (they use left: 27px with translate(-50%)), so the rail must be
   centred there too, not have its left edge at 27px, or it sits 1.5px to the
   side of the dots. translateX(-50%) centres its 3px width on 27px. */
.line__rail {
  position: absolute;
  left: 27px;
  top: 8px;
  bottom: 8px;
  width: 3px;
  transform: translateX(-50%);
  background: var(--line);
  border-radius: 3px;
}

/* One stop. Position relative so its dot can pin to the shared rail. */
.stop {
  position: relative;
  padding-left: 68px;
}

.stop__dot {
  position: absolute;
  left: 27px;
  top: 50%;
  width: 15px;
  height: 15px;
  transform: translate(-50%, -50%);
  background: var(--paper);
  border: 3px solid var(--ink);
  border-radius: 50%;
  transition: border-color 0.3s ease, background-color 0.3s ease;
}
.stop[data-active='true'] .stop__dot {
  border-color: var(--accent);
}

.stop--minor .stop__dot {
  width: 11px;
  height: 11px;
  border-width: 2px;
}

/* Tiers are separated by a divider so the sections read as distinct. The open
   state is shown by the arrow, the "Open" tag, the revealed stations and the
   train, not by a change of colour. */
.tier {
  border-top: 1px solid var(--line);
}

/* The train: a red carriage that slides to the active stop. Its vertical
   position is set from JavaScript through the transform (see the :style binding
   in browse.html), so it moves once and settles. Animating transform rather than
   top keeps the movement on the compositor, so it stays smooth even while the
   accordion animates alongside it. The easing is a clean ease-out with no
   overshoot, so it never bounces. */
.train {
  position: absolute;
  left: 27px;
  top: 0;
  width: 26px;
  height: 48px;
  /* JavaScript sets the transform to translate(-50%, calc(<y>px - 50%)); the
     -50%s centre the carriage on the rail and on its target y. */
  transform: translate(-50%, -50%);
  will-change: transform;
  /* A little carriage: rounded nose, body sheen, red throughout. */
  background: linear-gradient(180deg, #f4362c 0%, #e31e24 55%, #c1141a 100%);
  border-radius: 13px 13px 11px 11px;
  box-shadow: 0 10px 22px -10px rgba(227, 30, 36, 0.8), inset 0 2px 0 rgba(255, 255, 255, 0.28);
  z-index: 3;
  transition: transform 0.24s cubic-bezier(0.22, 1, 0.36, 1);
}
/* Suppress the slide for the very first placement on load (browse.html adds this
   until the train has parked at the first tier), so it starts already at the
   centre of the first tier instead of animating down from the top. */
.train--instant {
  transition: none;
}
/* The window band across the front. */
.train::before {
  content: '';
  position: absolute;
  top: 9px;
  left: 50%;
  transform: translateX(-50%);
  width: 17px;
  height: 10px;
  border-radius: 3px;
  background: rgba(250, 249, 246, 0.92);
  box-shadow: inset 0 -3px 0 rgba(0, 0, 0, 0.06);
}
/* Two headlamps at the leading edge. */
.train::after {
  content: '';
  position: absolute;
  bottom: 6px;
  left: 50%;
  transform: translateX(-50%);
  width: 16px;
  height: 4px;
  border-radius: 2px;
  background: radial-gradient(circle 2px at 3px 2px, rgba(255, 255, 255, 0.9) 55%, transparent 60%),
    radial-gradient(circle 2px at 13px 2px, rgba(255, 255, 255, 0.9) 55%, transparent 60%);
}

/* The chevron on a collapsible stop rotates when open. */
.chev {
  transition: transform 0.3s ease;
}
.chev[data-open='true'] {
  transform: rotate(90deg);
}

/* A quiet zoom on station imagery when hovered. */
.tile img {
  transition: transform 0.6s ease;
}
.tile:hover img {
  transform: scale(1.03);
}

/* Network map. The whole Underground and DLR: every line as continuous coloured
   strokes over a paper casing, every station as a clickable dot. Names are
   hidden until you hover, focus, search, or zoom in, so the whole map stays
   legible. Pan by dragging or two finger scroll, zoom by pinch, wheel or double
   tap (see the controller in browse.html). */
.map-control-panel {
  border: 1px solid var(--line);
  background: #fffdfa;
  padding: 1rem;
  box-shadow: 0 18px 40px -32px rgba(29, 31, 37, 0.55);
}
.map-search {
  min-height: 2.75rem;
}
.netmap input[type='search']::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}
.map-clear,
.map-icon-button,
.map-reset-button,
.map-tier-button,
.map-line-button {
  min-height: 2.25rem;
}
.map-icon-button {
  display: flex;
  height: 2.25rem;
  width: 2.25rem;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--ink);
  color: var(--ink);
  font-size: 1.1rem;
  line-height: 1;
  transition: background-color 0.2s ease, color 0.2s ease;
}
.map-reset-button {
  border: 1px solid var(--ink);
  padding: 0.375rem 0.85rem;
  color: var(--ink);
  font-size: 0.875rem;
  transition: background-color 0.2s ease, color 0.2s ease;
}
.map-icon-button:hover,
.map-reset-button:hover,
.map-tier-button:hover,
.map-line-button:hover {
  background: rgba(29, 31, 37, 0.05);
}
.map-tier-row,
.map-line-strip,
.map-key {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.map-line-select {
  display: inline-flex;
  align-items: center;
  gap: 0.65rem;
  border: 1px solid var(--line);
  background: #ffffff;
  padding: 0.55rem 0.8rem;
  color: var(--ink);
  font-size: 0.875rem;
}
.map-line-select span {
  color: var(--muted);
}
.map-line-select select {
  min-width: 11rem;
  background: transparent;
  color: var(--ink);
  outline: none;
}
.map-tier-row,
.map-line-strip {
  overflow-x: auto;
  padding-bottom: 0.2rem;
}
.map-tier-button,
.map-line-button {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 0.5rem;
  border: 1px solid var(--ink);
  padding: 0.4rem 0.75rem;
  color: var(--ink);
  font-size: 0.875rem;
  transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}
.map-tier-button.is-active,
.map-line-button.is-active {
  background: var(--ink);
  color: var(--paper);
}
.tier-swatch,
.line-swatch,
.landmark-swatch {
  display: inline-block;
  flex: 0 0 auto;
}
.tier-swatch {
  width: 0.72rem;
  height: 0.72rem;
  border: 2px solid var(--ink);
  background: #ffffff;
}
.tier-swatch[data-tier='A'] {
  border-color: var(--accent);
  background: rgba(227, 30, 36, 0.08);
}
.tier-swatch[data-tier='B'] {
  border-color: #30343b;
  background: rgba(29, 31, 37, 0.06);
}
.tier-swatch[data-tier='C'] {
  border-color: #8f887b;
  background: rgba(143, 136, 123, 0.12);
}
.line-swatch {
  width: 1rem;
  height: 3px;
}
.landmark-swatch {
  width: 0.68rem;
  height: 0.68rem;
  border: 1.5px solid #9b7b45;
  background: #fff7df;
  transform: rotate(45deg);
}
.map-key {
  flex-wrap: wrap;
  color: var(--muted);
  font-size: 0.75rem;
  line-height: 1.5;
}
.map-key > span {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}
.map-frame {
  overflow: hidden;
  border: 1px solid #d8d1c5;
  background: #f5f1e8;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.75), 0 22px 44px -38px rgba(29, 31, 37, 0.65);
}
@media (max-width: 640px) {
  .map-control-panel {
    padding: 0.85rem;
  }
  .map-tier-row {
    gap: 0.35rem;
    overflow-x: visible;
  }
  .map-tier-button {
    gap: 0.38rem;
    padding: 0.36rem 0.5rem;
    font-size: 0.8125rem;
  }
  .map-key {
    gap: 0.38rem;
    font-size: 0.7rem;
  }
  .tier-swatch {
    width: 0.64rem;
    height: 0.64rem;
    border-width: 1.7px;
  }
  .map-tier-row,
  .map-line-strip {
    margin-right: -0.85rem;
    padding-right: 0.85rem;
  }
}
@media (min-width: 641px) {
  .map-tier-row,
  .map-line-strip {
    flex-wrap: wrap;
    overflow-x: visible;
  }
}
.netmap svg {
  cursor: grab;
  touch-action: none; /* we handle drag, pinch and wheel ourselves */
}
.netmap svg.is-panning {
  cursor: grabbing;
}
/* The map keeps the network's own aspect on wide screens. On a phone the box
   goes taller (the SVG letterboxes onto the land tint, which is drawn well
   beyond the network), so the map is a workable size for touch instead of a
   thin landscape letterslot. */
.netmap .netmap-canvas {
  aspect-ratio: 1174 / 766;
}
@media (max-width: 640px) {
  .netmap .netmap-canvas {
    aspect-ratio: 1 / 1;
  }
}
.netmap svg a {
  cursor: pointer;
}
/* A warm land tint under the whole map, so it reads as a place, not a blank
   white window. */
.netmap .map-land {
  fill: #f4f1ea;
}
/* The Zone 1 boundary: a quiet dashed ring around central London, drawn from the
   real Zone 1 stations, with a small caps label. */
.netmap .map-zone1 {
  fill: #ffffff;
  fill-opacity: 0.35;
  stroke: #cabfa9;
  stroke-width: 1.1;
  stroke-dasharray: 4 3;
  opacity: 0.9;
}
.netmap .map-zone1-label {
  font-size: calc(var(--label-size, 9px) * 0.9);
  fill: #9c9384;
  font-weight: 700;
  letter-spacing: 0.16em;
  pointer-events: none;
}
/* Parks and commons: soft green shapes with a quiet green label on the big ones. */
.netmap .map-park {
  fill: #d8e6cd;
  opacity: 0.8;
}
.netmap .map-park-label {
  font-size: calc(var(--label-size, 9px) * 0.82);
  fill: #6f8566;
  font-weight: 600;
  letter-spacing: 0.01em;
  paint-order: stroke;
  stroke: #f4f1ea;
  stroke-width: 2px;
  stroke-linejoin: round;
  pointer-events: none;
}
/* The River Thames: a soft, wide band well behind the lines, drawn from real
   geography (see build-network.mjs). Faint enough to orient without competing. */
.netmap .map-river {
  stroke: #a9cfe6;
  stroke-width: 11;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.6;
}
.netmap .map-river-label {
  fill: #5f8ba6;
  font-style: italic;
  font-weight: 500;
  letter-spacing: 0.03em;
  pointer-events: none;
}
/* The lines. Each is drawn twice: a paper coloured casing underneath (so where
   several lines run parallel the bundle sits on one clean band, the classic map
   legibility trick) and the line colour on top. Widths are in SVG user units,
   driven by a variable the pan/zoom controller updates per zoom, so lines read
   bolder as you zoom in without ballooning. */
.netmap .ns-case {
  stroke: #faf9f6;
  stroke-width: calc(var(--line-w, 2.2px) * 2);
  stroke-linejoin: round;
  stroke-linecap: round;
}
.netmap .ns-line {
  stroke-width: var(--line-w, 2.2px);
  stroke-linejoin: round;
  stroke-linecap: round;
  transition: opacity 0.25s ease;
}
/* The line filter dims everything off the chosen line (a class toggle from the
   controller; the map is never rebuilt). Dimmed stations stop taking clicks so
   the filtered line is the only thing you can land on by accident. */
.netmap .ns-line.is-dim {
  opacity: 0.08;
}
.netmap .ns-station {
  transition: opacity 0.25s ease;
}
.netmap .ns-station.is-dim {
  opacity: 0.1;
}
.netmap .ns-station.is-dim .ns-hit {
  pointer-events: none;
}
/* Station dots. Their radius is driven by variables the pan/zoom controller sets
   per zoom (see applyVb in browse.html), so the dots keep a constant on-screen
   size and stop colliding as you zoom in. The px values here are only fallbacks. */
.netmap .ns-dot {
  r: var(--dot-r, 2.4px);
  fill: #ffffff;
  stroke: #8a8f98;
  /* The ring scales with the dot, so a roundel stays a white disc with a dark
     ring at every zoom rather than clotting into a solid dark blob. */
  stroke-width: calc(var(--dot-r, 2.4px) * 0.42);
  transition: stroke 0.15s ease, stroke-width 0.15s ease;
}
.netmap .ns-hit {
  r: var(--hit-r, 7px);
}
/* Interchange stations (two or more lines) read as bolder white roundels, the
   classic Tube map cue, so the busy centre separates into recognisable hubs. This
   rule follows .ns-dot so its larger radius wins. */
.netmap .ns-dot--xchg {
  r: var(--dot-r-xchg, 3.8px);
  stroke: #43474e;
  stroke-width: calc(var(--dot-r-xchg, 3.8px) * 0.38);
}
/* Hidden station name, revealed on hover, on focus, or when zoomed in. A paper
   halo keeps it readable over the busy lines. */
.netmap .dot-label {
  fill: var(--ink);
  opacity: 0;
  pointer-events: none;
  /* Font size is in SVG user units but is kept a constant screen size by the
     pan/zoom controller, which sets --label-size in user units per zoom level. */
  font-size: var(--label-size, 9px);
  font-weight: 600;
  paint-order: stroke;
  stroke: var(--paper);
  stroke-width: calc(var(--label-size, 9px) / 3.5);
  stroke-linejoin: round;
  transition: opacity 0.12s ease;
}
.netmap svg a:hover .ns-dot,
.netmap svg a:focus-visible .ns-dot {
  stroke: var(--accent);
  stroke-width: calc(var(--dot-r, 2.4px) * 0.6);
}
.netmap svg a:hover .dot-label,
.netmap svg a:focus-visible .dot-label {
  opacity: 1;
  fill: var(--accent);
}
/* Names show on hover or focus only, never automatically on zoom, so they never
   pile up on top of one another. */

/* Level of detail: the minor (single line) stops fade back and stop taking clicks
   until you zoom in past the threshold (data-detail), so the zoomed-out overview
   reads as a clean network of interchange roundels rather than a field of dots. */
.netmap:not([data-detail='true']) a.ns-station[data-xchg='false'] {
  opacity: 0.32;
}
.netmap:not([data-detail='true']) a.ns-station[data-xchg='false'] .ns-hit {
  pointer-events: none;
}
/* On the schematic, single-line stops stay visible when zoomed out (just smaller),
   so you can always see where the stations are; only their names wait for a zoom. */
.netmap:not([data-detail='true']) .sm-station[data-xchg='false'] .sm-dot {
  opacity: 0.7;
}
.netmap svg a:focus {
  outline: none;
}
.netmap svg a:focus-visible .ns-dot {
  stroke: var(--accent);
}
/* A searched-for station: its name label comes on, its dot takes the brand red
   ring with a small pop, and it stays visible even at the zoomed-out overview. */
.netmap .ns-station.is-found .ns-dot {
  stroke: var(--accent);
  stroke-width: calc(var(--dot-r, 2.4px) * 0.6);
  transform-box: fill-box;
  transform-origin: center;
  animation: foundPop 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.netmap .ns-station.is-found .dot-label {
  opacity: 1;
  fill: var(--accent);
}
.netmap:not([data-detail='true']) a.ns-station[data-xchg='false'].is-found {
  opacity: 1;
}
@keyframes foundPop {
  from {
    transform: scale(0.4);
  }
  to {
    transform: scale(1);
  }
}
@media (prefers-reduced-motion: reduce) {
  .netmap .ns-station.is-found .ns-dot {
    animation: none;
  }
}
/* Faint region names (NORTH LONDON, etc), the broad orientation layer. Kept a
   constant on-screen size (larger than the district labels) so it does not balloon
   when zoomed. */
.netmap .map-region {
  font-size: calc(var(--label-size, 9px) * 1.8);
  fill: var(--ink);
  opacity: 0.06;
  font-weight: 800;
  letter-spacing: 0.12em;
  pointer-events: none;
}
/* District and landmark names, a closer layer of context. Sized from the same
   variable as the station labels so they keep a constant on-screen size and do not
   balloon (and overlap) as you zoom in. */
.netmap .map-district {
  font-size: calc(var(--label-size, 9px) * 0.92);
  fill: var(--ink);
  opacity: 0.32;
  font-weight: 600;
  letter-spacing: 0.02em;
  paint-order: stroke;
  stroke: var(--paper);
  stroke-width: 2.5px;
  stroke-linejoin: round;
  pointer-events: none;
}

/* Level of detail for the finer labels: at the zoomed-out overview only the broad
   region names and the geography shapes show; the district, park and zone labels
   fade in as you zoom past the detail threshold, so the overview stays clean and
   uncluttered. The big region labels and the shapes themselves are always on. */
.netmap .map-district,
.netmap .map-park-label,
.netmap .map-zone1-label {
  transition: opacity 0.2s ease;
}
.netmap:not([data-detail='true']) .map-district,
.netmap:not([data-detail='true']) .map-park-label,
.netmap:not([data-detail='true']) .map-zone1-label {
  opacity: 0;
}

/* ============================================================================
   Octolinear schematic (the tube-map Map view). Our OWN drawing: only horizontal,
   vertical and 45 degree lines, evenly spaced stops, geography abstracted away, the
   antidote to the geographic map the client felt looked like "spaghetti". Drawn in a
   fixed coordinate space; zoom is uniform magnification, so everything is in plain
   user units and scales together like a printed map. See buildSchematicMap. */

/* A clean paper panel behind the whole schematic. */
.netmap .sm-bg {
  fill: #fbf8f1;
}
/* The stylised River Thames: a broad, soft band, the map's recognisable shape. */
.netmap .sm-river {
  stroke: #9fc9df;
  stroke-width: 17;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.78;
}
/* Faint district names for orientation, well behind the lines. */
.netmap .sm-district {
  fill: var(--ink);
  opacity: 0.18;
  font-size: 15px;
  font-weight: 800;
  letter-spacing: 0.16em;
  pointer-events: none;
}
/* Broad orientation labels (NORTH LONDON, DOCKLANDS...) for the wider network. */
.netmap .sm-district--big {
  font-size: 24px;
  opacity: 0.08;
  letter-spacing: 0.2em;
}
/* Named landmarks help visitors orient themselves without turning the schematic
   into a tourist map. They are quieter than stations, but clearer than the broad
   district watermarks. */
.netmap .sm-landmark {
  opacity: 0.72;
  pointer-events: none;
}
.netmap .sm-landmark-dot {
  fill: #fff8df;
  stroke: #9b7b45;
  stroke-width: 1.5;
}
.netmap .sm-landmark--park .sm-landmark-dot {
  fill: #e1ecd8;
  stroke: #78936b;
}
.netmap .sm-landmark--culture .sm-landmark-dot {
  fill: #f7e7ec;
  stroke: #a66d82;
}
.netmap .sm-landmark-label {
  fill: #6a6257;
  font-size: calc(var(--sm-label, 12px) * 0.8);
  font-weight: 700;
  paint-order: stroke;
  stroke: #fbf8f1;
  stroke-width: calc(var(--sm-label, 12px) * 0.22);
  stroke-linejoin: round;
}

/* Lines. Each is drawn twice: a paper casing underneath (so a fanned bundle sits on
   one clean band) and the colour on top. Rounded joins keep the 45 degree corners
   smooth. Widths are fixed user units (they scale with the uniform zoom). */
.netmap .sm-case {
  stroke: #fbf8f1;
  stroke-width: var(--sm-case-w, 6.7);
  stroke-linejoin: round;
  stroke-linecap: round;
}
.netmap .sm-line {
  stroke-width: var(--sm-line-w, 3.05);
  stroke-linejoin: round;
  stroke-linecap: round;
  transition: stroke 0.25s ease, opacity 0.25s ease;
}
/* The line filter the client liked: keep the chosen line in its colour and turn
   everything else a quiet grey (not just faded), so the selected route reads clearly
   against a still-legible network. CSS stroke overrides the inline colour. */
.netmap .sm-line.is-dim {
  stroke: #d6d2c8;
}
.netmap .sm-case.is-dim {
  opacity: 0.6;
}

/* Stops. Interchanges (two or more lines) are bold white roundels with a dark ring,
   the classic Tube map cue; single-line stops are smaller white ticks. Dots sit
   exactly on the line junctions (centred by construction), which is what the client
   asked for. */
.netmap .sm-station {
  transition: opacity 0.2s ease;
}
.netmap .sm-station--context {
  opacity: 0.42;
}
.netmap .sm-station--context .sm-hit {
  pointer-events: none;
}
.netmap .sm-dot {
  r: var(--sm-dot-r, 3.65);
  fill: #ffffff;
  stroke: #3f434a;
  stroke-width: calc(var(--sm-dot-r, 3.65) * 0.5);
  transition: fill 0.15s ease, stroke 0.15s ease;
}
.netmap .sm-dot--xchg {
  r: var(--sm-xchg-r, 6.25);
  stroke: #26292e;
  stroke-width: calc(var(--sm-xchg-r, 6.25) * 0.42);
}
.netmap .sm-hit {
  r: var(--sm-hit-r, 13);
}
.netmap .sm-station[data-tier='A'] .sm-dot {
  stroke: var(--accent);
}
.netmap .sm-station[data-tier='B'] .sm-dot {
  stroke: #30343b;
}
.netmap .sm-station[data-tier='C'] .sm-dot {
  stroke: #8f887b;
}
.netmap .sm-station--context .sm-dot {
  fill: #fbf8f1;
  stroke: #bdb6aa;
}
.netmap .sm-station--context .sm-label {
  fill: #8f887b;
  font-weight: 500;
}
/* Station names. Always shown (tube-map style), with a paper halo so they stay
   readable over the lines. Font size tracks the zoom so names stay a sensible
   on-screen size. */
.netmap .sm-label {
  fill: var(--ink);
  font-size: var(--sm-label, 12.2px);
  font-weight: 600;
  pointer-events: none;
  paint-order: stroke;
  stroke: #fbf8f1;
  stroke-width: calc(var(--sm-label, 12.2px) * 0.3);
  stroke-linejoin: round;
  transition: fill 0.12s ease;
}
/* The line filter dims stops off the chosen line right back, and stops them taking
   clicks so only the filtered line is landable. */
.netmap .sm-station.is-dim {
  opacity: 0.17;
}
.netmap .sm-station.is-dim .sm-hit {
  pointer-events: none;
}
/* Hover and keyboard focus: the dot takes the brand red ring and the name lifts to
   red, so the pointer target is obvious. */
.netmap svg a.sm-station:hover .sm-dot,
.netmap svg a.sm-station:focus-visible .sm-dot {
  stroke: var(--accent);
  stroke-width: 3.4;
}
.netmap svg a.sm-station:hover .sm-label,
.netmap svg a.sm-station:focus-visible .sm-label {
  fill: var(--accent);
}
.netmap svg a.sm-station:focus {
  outline: none;
}

/* The red found-pointer: a pin above the dot, hidden until the station is searched
   for, when it drops in and the dot goes solid red. This is the "make it obvious
   where the station is" the client asked for. */
/* The pulsing halo ring around a found station. */
.netmap .sm-halo {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2.5;
  opacity: 0;
  pointer-events: none;
}
.netmap .sm-station.is-found .sm-halo {
  transform-box: fill-box;
  transform-origin: center;
  animation: smHalo 1.6s ease-out infinite;
}
@keyframes smHalo {
  0% { opacity: 0.9; transform: scale(0.7); }
  70% { opacity: 0; transform: scale(2.4); }
  100% { opacity: 0; transform: scale(2.4); }
}
.netmap .sm-pin {
  opacity: 0;
  pointer-events: none;
}
.netmap .sm-pin path,
.netmap .sm-pin circle {
  fill: var(--accent);
}
.netmap .sm-pin .sm-pin-core {
  fill: #ffffff;
}
.netmap .sm-station.is-found .sm-pin {
  opacity: 1;
  transform-box: fill-box;
  transform-origin: center bottom;
  animation: smPinDrop 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.netmap .sm-station.is-found .sm-dot {
  fill: var(--accent);
  stroke: var(--accent);
  r: var(--sm-found-r, 7.5);
}
.netmap .sm-station.is-found .sm-label {
  fill: var(--accent);
  font-weight: 700;
}
/* A found stop always shows through, even when a line filter is dimming its line. */
.netmap .sm-station.is-found {
  opacity: 1 !important;
}
.netmap .sm-station.is-found .sm-hit {
  pointer-events: all !important;
}
@keyframes smPinDrop {
  from {
    transform: translateY(-6px);
    opacity: 0;
  }
  to {
    transform: translateY(0);
    opacity: 1;
  }
}
/* Level of detail across the big network. Zoomed right out it is just lines and
   dots (labels would pile into an unreadable mess). A first zoom (data-detail)
   brings in the interchange names; a closer zoom (data-detail-fine) brings in every
   station name. Hover always reveals a name whatever the zoom. */
.netmap .sm-label {
  opacity: 0;
  transition: opacity 0.2s ease;
}
.netmap[data-detail='true'] .sm-station[data-xchg='true'] .sm-label {
  opacity: 1;
}
.netmap[data-detail-fine='true'] .sm-station .sm-label {
  opacity: 1;
}
.netmap svg a.sm-station:hover .sm-label {
  opacity: 1;
}
.netmap .sm-station.is-found .sm-label {
  opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
  .netmap .sm-station.is-found .sm-pin {
    animation: none;
  }
  .netmap .sm-station.is-found .sm-halo {
    animation: none;
    opacity: 0.7;
  }
}

/* Per station preview thumbnail: a small crop of the map showing where it is. */
.thumb {
  background: #ffffff;
  display: block;
}
/* Long station lists render a map thumbnail per row, so let the browser skip the
   ones that are off screen. */
.station-row {
  content-visibility: auto;
  contain-intrinsic-size: auto 92px;
}

/* ------------------------------------------------------------------ */
/* Homepage artist journey and station booking builder                 */
/* ------------------------------------------------------------------ */

.booking-journey {
  background: linear-gradient(180deg, #faf9f6 0%, #f5f1e8 100%);
}
.journey-track {
  position: relative;
  display: grid;
  gap: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}
.journey-track::before {
  content: '';
  position: absolute;
  left: 1rem;
  top: 1.7rem;
  bottom: 1.7rem;
  width: 3px;
  background: var(--accent);
}
.journey-stop {
  position: relative;
  display: grid;
  grid-template-columns: 2.8rem minmax(0, 1fr);
  gap: 1rem;
  padding: 1.05rem 0;
}
.journey-stop__marker {
  position: relative;
  z-index: 1;
  display: inline-flex;
  width: 2rem;
  height: 2rem;
  align-items: center;
  justify-content: center;
  border: 2px solid var(--accent);
  border-radius: 999px;
  background: var(--paper);
  color: var(--accent);
  font-size: 0.8rem;
  font-weight: 800;
}
.journey-stop h3 {
  font-family: 'Figtree', system-ui, sans-serif;
  font-size: 1.2rem;
  font-weight: 700;
}
.journey-stop p {
  margin-top: 0.25rem;
  max-width: 34rem;
  color: var(--muted);
  font-size: 0.95rem;
  font-weight: 300;
  line-height: 1.55;
}

.booking-builder {
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  padding: 2.5rem 0;
}
.booking-builder__heading {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 2rem;
}
.booking-builder__heading > p {
  max-width: 26rem;
  color: var(--muted);
  font-size: 0.95rem;
  font-weight: 300;
  line-height: 1.6;
}
.station-preview {
  position: relative;
  min-height: 25rem;
  background: #fbf8f1;
}
.station-preview[data-photo='true'] {
  background: var(--ink);
}
.station-preview__photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(0.9) contrast(1.08);
}
.station-preview[data-photo='true']::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(29, 31, 37, 0.08), rgba(29, 31, 37, 0.6));
  pointer-events: none;
}
.station-preview__locator {
  position: absolute;
  inset: 0;
}
.station-preview[data-photo='true'] .station-preview__locator {
  inset: 1rem 1rem auto auto;
  z-index: 2;
  width: min(42%, 16.5rem);
  min-width: 12rem;
  height: 10rem;
  border: 1px solid rgba(250, 249, 246, 0.5);
  background: rgba(251, 248, 241, 0.94);
  box-shadow: 0 18px 34px -26px rgba(0, 0, 0, 0.75);
}
.station-preview__tag {
  position: absolute;
  left: 1rem;
  top: 1rem;
  z-index: 1;
  border: 1px solid rgba(29, 31, 37, 0.2);
  background: rgba(250, 249, 246, 0.92);
  padding: 0.38rem 0.55rem;
  color: var(--ink);
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}
.station-preview[data-photo='true'] .station-preview__tag {
  left: 0.65rem;
  top: 0.65rem;
  font-size: 0.56rem;
  letter-spacing: 0.12em;
}
.station-preview__svg {
  background:
    radial-gradient(circle at 50% 50%, rgba(227, 30, 36, 0.08), transparent 24%),
    #fbf8f1;
}
.station-preview[data-photo='true'] .station-preview__svg {
  height: 100% !important;
}
.station-preview__footer {
  position: absolute;
  left: 1rem;
  right: 1rem;
  bottom: 1rem;
  display: flex;
  align-items: center;
  gap: 0.8rem;
  border: 1px solid rgba(29, 31, 37, 0.12);
  background: rgba(29, 31, 37, 0.93);
  padding: 0.8rem 0.9rem;
  color: var(--paper);
  box-shadow: 0 18px 34px -28px rgba(29, 31, 37, 0.85);
  z-index: 2;
}
.station-preview__roundel {
  position: relative;
  flex: 0 0 auto;
  width: 2.5rem;
  height: 2.5rem;
  border: 0.48rem solid var(--accent);
  border-radius: 999px;
  background: #ffffff;
}
.station-preview__roundel::after {
  content: '';
  position: absolute;
  left: -0.7rem;
  right: -0.7rem;
  top: 50%;
  height: 0.48rem;
  transform: translateY(-50%);
  background: #1f5aa6;
}
.station-preview__footer strong,
.station-preview__footer small {
  display: block;
}
.station-preview__footer strong {
  font-size: 1rem;
  font-weight: 800;
}
.station-preview__footer small {
  margin-top: 0.1rem;
  color: rgba(250, 249, 246, 0.68);
  font-size: 0.78rem;
}
.station-preview__credit {
  position: absolute;
  right: 1rem;
  bottom: 0.45rem;
  z-index: 2;
  color: rgba(250, 249, 246, 0.62);
  font-size: 0.62rem;
}
.station-sales-pitch {
  border-left: 3px solid var(--accent);
  padding-left: 1rem;
}
.station-sales-pitch > p:last-child {
  margin-top: 0.75rem;
  color: var(--ink);
  font-size: 0.98rem;
  font-weight: 600;
  line-height: 1.6;
}
.booking-flow {
  position: relative;
  display: grid;
  gap: 0;
  align-content: start;
  align-self: start;
}
.booking-flow::before {
  content: '';
  position: absolute;
  left: 1.25rem;
  top: 1.6rem;
  bottom: 1.6rem;
  width: 3px;
  background: var(--accent);
  opacity: 0.35;
}
.booking-step {
  position: relative;
  border-top: 1px solid var(--line);
  padding-left: 3.25rem;
}
.booking-step:first-child {
  border-top: 0;
}
.booking-step__toggle {
  display: grid;
  width: 100%;
  grid-template-columns: minmax(0, 1fr) auto 1rem;
  gap: 1rem;
  align-items: center;
  min-height: 4.15rem;
  padding: 0.72rem 0;
  text-align: left;
}
.booking-step__marker {
  position: absolute;
  left: 1.25rem;
  top: 1.05rem;
  z-index: 1;
  display: inline-flex;
  width: 2.05rem;
  height: 2.05rem;
  transform: translateX(-50%);
  align-items: center;
  justify-content: center;
  border: 3px solid var(--accent);
  border-radius: 999px;
  background: var(--paper);
  color: var(--accent);
  font-size: 0.8rem;
  font-weight: 800;
  transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}
.booking-step[data-active='true'] .booking-step__marker {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--paper);
}
.booking-step[data-complete='true']:not([data-active='true']) .booking-step__marker {
  border-color: var(--accent);
  background: var(--paper);
  color: var(--accent);
}
.booking-step__copy,
.booking-step__title,
.booking-step__hint,
.booking-step__summary {
  display: block;
}
.booking-step__title {
  font-family: 'Figtree', system-ui, sans-serif;
  font-size: 1.25rem;
  font-weight: 700;
}
.booking-step__hint {
  margin-top: 0.15rem;
  color: var(--muted);
  font-size: 0.9rem;
  font-weight: 300;
  line-height: 1.55;
}
.booking-step__summary {
  max-width: 11rem;
  color: var(--ink);
  font-size: 0.84rem;
  font-weight: 800;
  text-align: right;
}
.booking-step__chev {
  width: 1rem;
  height: 1rem;
  color: var(--muted);
  transition: transform 0.25s ease;
}
.booking-step__chev[data-open='true'] {
  transform: rotate(90deg);
}
.booking-step__panel {
  overflow: hidden;
  padding: 0.1rem 0 0.95rem;
}
/* Accordion reveal, used by both the browse tiers and the "book in three simple
   steps" panels. Replaces the old x-collapse height animation (which reflowed the
   whole page every frame and read as janky). The panel snaps to its full height in
   one frame (no per-frame layout thrash) and a CSS keyframe fades it in; the
   animation replays each time x-show flips the panel from display:none to shown.
   Enter only, so closing is instant and quick and nothing below jumps. Respects
   reduced motion. */
.panel-anim {
  animation: panelIn 0.2s ease;
}
@keyframes panelIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
@media (prefers-reduced-motion: reduce) {
  .panel-anim {
    animation: none;
  }
}
.space-stack {
  display: grid;
  gap: 1rem;
}
.space-group {
  border: 1px solid var(--line);
  background: #fffdfa;
  padding: 1rem;
  transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
}
.space-group[data-active='true'] {
  border-color: var(--ink);
  background: #ffffff;
  box-shadow: 0 18px 32px -30px rgba(29, 31, 37, 0.75);
}
.space-group__head {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding-bottom: 0.85rem;
}
.space-group__head h4 {
  font-family: 'Figtree', system-ui, sans-serif;
  font-size: 1.05rem;
  font-weight: 800;
}
.space-group__head p {
  margin: 0 0 0 auto;
  color: var(--muted);
  font-size: 0.8rem;
}
.space-group__dot {
  width: 0.8rem;
  height: 0.8rem;
  border: 2px solid var(--ink);
  background: var(--paper);
}
.space-group[data-active='true'] .space-group__dot {
  border-color: var(--accent);
  background: var(--accent);
}
.space-choice-grid {
  display: grid;
  gap: 0.65rem;
}
.space-choice {
  display: flex;
  width: 100%;
  min-height: 5.25rem;
  align-items: center;
  justify-content: space-between;
  gap: 1.25rem;
  border: 1px solid var(--line);
  background: var(--paper);
  padding: 1rem;
  text-align: left;
  transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease, transform 0.2s ease;
}
.space-choice:hover {
  border-color: var(--ink);
  transform: translateY(-1px);
}
.space-choice[data-active='true'] {
  border-color: var(--ink);
  background: var(--ink);
  color: var(--paper);
}
.space-choice strong,
.space-choice small {
  display: block;
}
.space-choice strong {
  font-size: 1rem;
  font-weight: 800;
}
.space-choice small {
  margin-top: 0.25rem;
  color: var(--muted);
  font-size: 0.78rem;
  font-weight: 300;
  line-height: 1.35;
}
.space-choice[data-active='true'] small {
  color: rgba(250, 249, 246, 0.72);
}
.space-choice__price {
  flex: 0 0 auto;
  min-width: 4.5rem;
  text-align: right;
}
.space-choice__price small {
  text-transform: uppercase;
  letter-spacing: 0.16em;
}
.space-choice__price strong {
  font-family: 'Figtree', system-ui, sans-serif;
  font-size: 1.45rem;
  font-weight: 700;
}
/* The duration slider (the signed scope's "slider that drops the price the
   longer they book"). The track reads as a tube rail that paints itself red up
   to the chosen stop; --fill is set inline from the component. The stops under
   the rail are real buttons too, so tapping a label also works. */
.duration-slider {
  padding: 0.25rem 0.15rem 0;
}
.duration-slider input[type='range'] {
  -webkit-appearance: none;
  appearance: none;
  display: block;
  width: 100%;
  height: 28px;
  margin: 0;
  background: transparent;
  cursor: pointer;
}
.duration-slider input[type='range']::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: 4px;
  background: linear-gradient(
    to right,
    var(--accent) 0 var(--fill, 0%),
    var(--line) var(--fill, 0%) 100%
  );
}
.duration-slider input[type='range']::-moz-range-track {
  height: 4px;
  border-radius: 4px;
  background: linear-gradient(
    to right,
    var(--accent) 0 var(--fill, 0%),
    var(--line) var(--fill, 0%) 100%
  );
}
/* The thumb is a little roundel: paper core, red ring, like an interchange dot. */
.duration-slider input[type='range']::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 20px;
  height: 20px;
  margin-top: -8px;
  border-radius: 50%;
  border: 5px solid var(--accent);
  background: var(--paper);
  box-shadow: 0 6px 14px -6px rgba(227, 30, 36, 0.7);
}
.duration-slider input[type='range']::-moz-range-thumb {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  border: 5px solid var(--accent);
  background: var(--paper);
  box-shadow: 0 6px 14px -6px rgba(227, 30, 36, 0.7);
}
.duration-slider input[type='range']:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 4px;
}
.duration-scale {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.5rem;
  margin-top: 0.35rem;
}
.duration-stop {
  padding: 0.35rem 0.2rem;
  text-align: center;
  transition: color 0.2s ease;
}
.duration-stop:first-child {
  text-align: left;
}
.duration-stop:last-child {
  text-align: right;
}
.duration-stop strong,
.duration-stop span {
  display: block;
}
.duration-stop strong {
  font-size: 0.92rem;
  font-weight: 800;
  color: var(--muted);
  transition: color 0.2s ease;
}
.duration-stop span {
  margin-top: 0.15rem;
  color: var(--muted);
  font-size: 0.75rem;
}
.duration-stop:hover strong {
  color: var(--ink);
}
.duration-stop[data-active='true'] strong {
  color: var(--accent);
}
.duration-slider__note {
  margin-top: 0.9rem;
  color: var(--muted);
  font-size: 0.78rem;
}
/* The permitted start dates, under the slider. A plain select for now; the
   real date picker replaces it in a later stage. */
.start-date {
  display: grid;
  gap: 0.4rem;
  margin-top: 1.1rem;
  border-top: 1px solid var(--line);
  padding-top: 1.1rem;
}
.start-date > span {
  color: var(--muted);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.start-date select {
  max-width: 16rem;
  border: 1px solid var(--line);
  background: #ffffff;
  padding: 0.55rem 0.65rem;
  font-size: 0.875rem;
  color: var(--ink);
}
.start-date select:focus {
  border-color: var(--ink);
  outline: none;
}
.start-date small {
  color: var(--muted);
  font-size: 0.75rem;
  line-height: 1.5;
}
.booking-summary {
  align-self: start;
  border: 1px solid var(--ink);
  background: #fffdfa;
  padding: 1.5rem;
  box-shadow: 0 28px 42px -36px rgba(29, 31, 37, 0.85);
}
.summary-category {
  margin-top: 0.35rem;
  color: var(--muted);
  font-size: 0.82rem;
  font-weight: 700;
}
.summary-list {
  display: grid;
  gap: 0;
}
.summary-list div {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  border-top: 1px solid var(--line);
  padding: 0.85rem 0;
}
.summary-list dt {
  color: var(--muted);
  font-size: 0.82rem;
}
.summary-list dd {
  text-align: right;
  font-weight: 700;
}
.summary-total {
  margin-top: 1rem;
  background: var(--ink);
  color: var(--paper);
  padding: 1.1rem;
}
.summary-total span,
.summary-total small {
  display: block;
  color: rgba(250, 249, 246, 0.7);
  font-size: 0.78rem;
}
.summary-total strong {
  display: block;
  margin-top: 0.25rem;
  font-family: 'Figtree', system-ui, sans-serif;
  font-size: 2.3rem;
  font-weight: 700;
  line-height: 1;
}
.summary-total small {
  margin-top: 0.35rem;
}
.summary-included {
  border-top: 1px solid var(--line);
  padding-top: 1rem;
  margin-top: 1rem;
}
.summary-included h4 {
  font-family: 'Figtree', system-ui, sans-serif;
  font-size: 0.92rem;
  font-weight: 800;
}
.summary-included ul {
  display: grid;
  gap: 0.48rem;
  margin-top: 0.75rem;
}
.summary-included li {
  display: flex;
  gap: 0.55rem;
  color: var(--ink);
  font-size: 0.78rem;
  line-height: 1.35;
}
.summary-tick {
  position: relative;
  flex: 0 0 auto;
  width: 0.85rem;
  height: 0.85rem;
  margin-top: 0.08rem;
  border: 2px solid var(--accent);
  border-radius: 999px;
}
.summary-tick::after {
  content: '';
  position: absolute;
  left: 0.2rem;
  top: 0.12rem;
  width: 0.3rem;
  height: 0.48rem;
  border-right: 2px solid var(--accent);
  border-bottom: 2px solid var(--accent);
  transform: rotate(38deg);
}
.summary-action {
  display: flex;
  width: 100%;
  min-height: 3.3rem;
  align-items: center;
  justify-content: center;
  background: var(--ink);
  padding: 0.95rem 1.25rem;
  color: var(--paper);
  font-size: 0.9rem;
  font-weight: 700;
  transition: background-color 0.16s ease;
}
.summary-action:hover {
  background: var(--accent);
}
.booking-summary .summary-action[data-done='true'] {
  background: var(--accent);
}
.summary-payment-note {
  margin-top: 0.9rem;
  border-left: 3px solid var(--accent);
  padding-left: 0.8rem;
  color: var(--muted);
  font-size: 0.78rem;
  line-height: 1.55;
}

.draft-basket {
  border-top: 1px solid var(--line);
  padding-top: 1.25rem;
}
.draft-basket__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
}
.draft-basket__head h4 {
  margin-top: 0.35rem;
  font-family: 'Figtree', system-ui, sans-serif;
  font-size: 1.1rem;
  font-weight: 800;
}
.draft-basket__head > span {
  border: 1px solid var(--line);
  padding: 0.32rem 0.5rem;
  color: var(--muted);
  font-size: 0.72rem;
  font-weight: 700;
}
.basket-line {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  border-top: 1px solid var(--line);
  padding: 0.85rem 0;
}
.basket-line--current {
  margin-top: 0.95rem;
}
.basket-total {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  border-top: 2px solid var(--ink);
  padding-top: 0.9rem;
}
.basket-total span {
  color: var(--muted);
  font-size: 0.8rem;
}
.basket-total strong {
  font-family: 'Figtree', system-ui, sans-serif;
  font-size: 1.2rem;
  font-weight: 800;
}
.basket-line strong,
.basket-line small {
  display: block;
}
.basket-line strong {
  font-size: 0.92rem;
}
.basket-line small {
  margin-top: 0.15rem;
  color: var(--muted);
  font-size: 0.75rem;
}
/* A saved draft line: the station name links back to its page, and the quiet
   remove control sits under the price. */
.basket-line__link {
  text-decoration: none;
  color: inherit;
}
.basket-line__link:hover strong {
  color: var(--accent);
}
.basket-line__end {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.2rem;
}
.basket-line__remove {
  color: var(--muted);
  font-size: 0.72rem;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.basket-line__remove:hover {
  color: var(--accent);
}
/* The discount code holder. UI only until checkout exists; square, like the
   rest of the controls. */
.basket-code {
  display: flex;
  align-items: flex-end;
  gap: 0.5rem;
  margin-top: 1rem;
  border-top: 1px solid var(--line);
  padding-top: 1rem;
}
.basket-code label {
  display: grid;
  flex: 1;
  gap: 0.35rem;
}
.basket-code label span {
  color: var(--muted);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.basket-code input {
  width: 100%;
  border: 1px solid var(--line);
  background: #ffffff;
  padding: 0.5rem 0.65rem;
  font-size: 0.85rem;
}
.basket-code input:focus {
  border-color: var(--ink);
  outline: none;
}
.basket-code button {
  border: 1px solid var(--ink);
  padding: 0.5rem 0.9rem;
  font-size: 0.8rem;
  transition: background-color 0.2s ease, color 0.2s ease;
}
.basket-code button:hover {
  background: var(--ink);
  color: var(--paper);
}
.basket-code__note {
  margin-top: 0.6rem;
  color: var(--muted);
  font-size: 0.75rem;
  line-height: 1.5;
}
.basket-save {
  border: 1px solid rgba(227, 30, 36, 0.32);
  background: rgba(227, 30, 36, 0.06);
  padding: 0.7rem;
  color: var(--ink);
  font-size: 0.76rem;
  line-height: 1.45;
}
.basket-addons {
  margin-top: 1rem;
}
.basket-addons > p {
  color: var(--muted);
  font-size: 0.78rem;
  font-weight: 800;
}
.basket-addon {
  display: flex;
  width: 100%;
  align-items: center;
  justify-content: space-between;
  gap: 0.85rem;
  border: 1px solid var(--line);
  background: #ffffff;
  padding: 0.72rem;
  text-align: left;
  transition: border-color 0.2s ease, background-color 0.2s ease, color 0.2s ease;
}
.basket-addon + .basket-addon {
  margin-top: 0.45rem;
}
.basket-addon:hover {
  border-color: var(--ink);
}
.basket-addon[data-active='true'] {
  border-color: var(--accent);
  background: rgba(227, 30, 36, 0.06);
}
.basket-addon strong,
.basket-addon small,
.basket-addon > span:last-child {
  display: block;
}
.basket-addon strong {
  font-size: 0.86rem;
}
.basket-addon small {
  margin-top: 0.12rem;
  color: var(--muted);
  font-size: 0.72rem;
}
.basket-addon > span:last-child {
  flex: 0 0 auto;
  color: var(--accent);
  font-size: 0.72rem;
  font-weight: 800;
}

.proof-lab {
  border-top: 1px solid var(--line);
  padding-top: 2.5rem;
}
.proof-lab--compact {
  border-top: 0;
  padding-top: 0;
}
.proof-lab--compact .proof-lab__grid {
  grid-template-columns: 1fr;
  margin-top: 0;
}
.proof-lab--compact .poster-proof {
  width: min(100%, 320px);
}
.proof-lab--compact .proof-fields {
  max-width: 34rem;
}
.proof-lab__copy {
  max-width: 40rem;
}
.proof-lab__grid {
  display: grid;
  grid-template-columns: minmax(220px, 320px) minmax(0, 1fr);
  gap: 2rem;
  align-items: center;
  margin-top: 1.6rem;
}
.poster-proof {
  aspect-ratio: 4 / 5;
  border: 8px solid var(--ink);
  background: var(--paper);
  padding: 1rem;
  box-shadow: 0 24px 38px -32px rgba(29, 31, 37, 0.8);
}
.poster-proof__art {
  display: flex;
  height: calc(100% - 4.8rem);
  align-items: flex-end;
  background:
    linear-gradient(135deg, #1d1f25 0 34%, transparent 34%),
    linear-gradient(45deg, transparent 53%, #e31e24 53% 67%, transparent 67%),
    linear-gradient(160deg, #d8d0c2 0%, #f6f0e4 100%);
  padding: 1rem;
  color: var(--paper);
}
.poster-proof__art span {
  max-width: 12rem;
  font-family: 'Figtree', system-ui, sans-serif;
  font-size: 1.35rem;
  font-weight: 800;
  line-height: 1.05;
  text-shadow: 0 1px 16px rgba(0, 0, 0, 0.35);
}
.poster-proof__caption {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  min-height: 4.8rem;
  padding-top: 0.8rem;
}
.poster-proof__caption strong,
.poster-proof__caption span {
  display: block;
}
.poster-proof__caption strong {
  font-size: 0.95rem;
}
.poster-proof__caption span {
  color: var(--muted);
  font-size: 0.78rem;
}
/* The proof's QR. A real code renders inside (see proofQrSvg in station.html);
   the gradient pattern is only the fallback when the encoder is unavailable. */
.proof-qr {
  flex: 0 0 auto;
  width: 3rem;
  height: 3rem;
  border: 4px solid var(--paper);
  outline: 1px solid var(--ink);
  background:
    linear-gradient(90deg, var(--ink) 25%, transparent 25% 50%, var(--ink) 50% 75%, transparent 75%),
    linear-gradient(var(--ink) 25%, transparent 25% 50%, var(--ink) 50% 75%, transparent 75%);
  background-size: 0.75rem 0.75rem;
  opacity: 0.9;
}
.proof-qr:has(svg) {
  background: var(--paper);
  opacity: 1;
}
.proof-qr svg {
  display: block;
  width: 100%;
  height: 100%;
}
.proof-fields {
  display: grid;
  gap: 1rem;
}
.proof-fields label {
  display: grid;
  gap: 0.45rem;
}
.proof-fields label span {
  color: var(--muted);
  font-size: 0.82rem;
}
.proof-fields input {
  width: 100%;
  border: 1px solid var(--line);
  background: #ffffff;
  padding: 0.95rem 1rem;
  color: var(--ink);
  font-size: 0.95rem;
  outline: none;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.proof-fields input:focus {
  border-color: var(--ink);
  box-shadow: 0 0 0 3px rgba(227, 30, 36, 0.12);
}
.approval-strip {
  position: relative;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  padding: 1.2rem 0;
}
.approval-strip div {
  position: relative;
  padding: 1.25rem;
  border-right: 0;
}
.approval-strip div::after {
  content: '';
  position: absolute;
  left: 3.1rem;
  right: -1.15rem;
  top: 2.08rem;
  height: 3px;
  background: var(--accent);
  opacity: 0.32;
}
.approval-strip div:last-child::after {
  display: none;
}
.approval-strip span {
  position: relative;
  z-index: 1;
  display: inline-flex;
  width: 1.65rem;
  height: 1.65rem;
  align-items: center;
  justify-content: center;
  border: 3px solid var(--accent);
  border-radius: 999px;
  background: var(--paper);
  color: var(--accent);
  font-size: 0.72rem;
  font-weight: 800;
}
.approval-strip strong {
  display: block;
  margin-top: 0.75rem;
  font-size: 0.98rem;
}
.approval-strip p {
  margin-top: 0.35rem;
  color: var(--muted);
  font-size: 0.8rem;
  font-weight: 300;
  line-height: 1.45;
}

/* ------------------------------------------------------------------ */
/* The artist dashboard (dashboard.html)                               */
/* ------------------------------------------------------------------ */

/* A draft still in the basket: quiet row with a continue link. */
.dash-draft {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  border: 1px solid var(--line);
  background: #fffdfa;
  padding: 1rem 1.15rem;
}
.dash-draft strong,
.dash-draft small {
  display: block;
}
.dash-draft strong {
  font-size: 0.95rem;
}
.dash-draft small {
  margin-top: 0.2rem;
  color: var(--muted);
  font-size: 0.78rem;
}
.dash-draft__end {
  text-align: right;
}
.dash-draft__end a {
  display: inline-block;
  margin-top: 0.25rem;
  color: var(--accent);
  font-size: 0.78rem;
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* One current or upcoming booking. */
.dash-card {
  border: 1px solid var(--ink);
  background: #fffdfa;
  padding: 1.35rem 1.5rem;
  box-shadow: 0 24px 38px -34px rgba(29, 31, 37, 0.75);
}
.dash-card__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
}
.dash-card__status {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}
.dash-card[data-status='live'] .dash-card__status {
  color: var(--accent);
}
.dash-card h3 {
  margin-top: 0.4rem;
  font-family: 'Figtree', system-ui, sans-serif;
  font-size: 1.45rem;
  font-weight: 700;
}
.dash-card h3 a {
  color: inherit;
  text-decoration: none;
}
.dash-card h3 a:hover {
  color: var(--accent);
}
.dash-card__meta {
  margin-top: 0.3rem;
  color: var(--muted);
  font-size: 0.8rem;
}
.dash-card__price {
  text-align: right;
}
.dash-card__price strong {
  display: block;
  font-size: 1.35rem;
  font-weight: 800;
}
.dash-card__price small {
  color: var(--muted);
  font-size: 0.75rem;
}
.dash-card__dates {
  margin-top: 0.6rem;
  font-size: 0.85rem;
}

/* The booking's journey: the same stops-on-a-line language as the site. */
.dash-steps {
  display: flex;
  margin-top: 1.15rem;
  padding: 0;
  list-style: none;
}
.dash-steps li {
  position: relative;
  flex: 1;
  text-align: center;
}
.dash-steps li::before {
  content: '';
  position: absolute;
  top: 0.5rem;
  left: -50%;
  width: 100%;
  height: 3px;
  background: var(--line);
}
/* first-of-type, not first-child: the Alpine x-for <template> is the list's
   real first child, so first-child would never match a rendered stop. */
.dash-steps li:first-of-type::before {
  display: none;
}
.dash-steps li[data-done='true']::before {
  background: var(--accent);
}
.dash-steps__dot {
  position: relative;
  z-index: 1;
  display: inline-block;
  width: 1.05rem;
  height: 1.05rem;
  border: 3px solid var(--line);
  border-radius: 999px;
  background: var(--paper);
}
.dash-steps li[data-done='true'] .dash-steps__dot {
  border-color: var(--accent);
}
.dash-steps li[data-current='true'] .dash-steps__dot {
  background: var(--accent);
}
.dash-steps__label {
  display: block;
  margin-top: 0.4rem;
  color: var(--muted);
  font-size: 0.72rem;
}
.dash-steps li[data-current='true'] .dash-steps__label {
  color: var(--ink);
  font-weight: 600;
}
.dash-card__foot {
  margin-top: 1.15rem;
  border-top: 1px solid var(--line);
  padding-top: 0.9rem;
}
.dash-card__invoice {
  display: block;
  color: var(--muted);
  font-size: 0.78rem;
}
.dash-card__note {
  display: block;
  margin-top: 0.5rem;
  color: var(--muted);
  font-size: 0.8rem;
  line-height: 1.55;
}

/* A past booking: one quiet divided row. */
.dash-past {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  border-top: 1px solid var(--line);
  padding: 0.95rem 0;
}
.dash-past strong,
.dash-past small {
  display: block;
}
.dash-past strong a {
  color: inherit;
  font-size: 0.95rem;
  text-decoration: none;
}
.dash-past strong a:hover {
  color: var(--accent);
}
.dash-past small {
  margin-top: 0.2rem;
  color: var(--muted);
  font-size: 0.78rem;
}
.dash-past__end {
  text-align: right;
}
.dash-past__end small {
  color: var(--muted);
}

@media (min-width: 1024px) {
  .booking-summary {
    position: sticky;
    top: 6rem;
  }
}
@media (max-width: 760px) {
  .booking-builder__heading {
    display: grid;
  }
  .station-preview {
    min-height: 18rem;
  }
  .station-preview[data-photo='true'] .station-preview__locator {
    width: 11.5rem;
    min-width: 0;
    height: 8.5rem;
  }
  .station-preview[data-photo='true'] .station-preview__tag {
    max-width: 8rem;
  }
  .station-preview__footer {
    left: 1rem;
    right: 1rem;
  }
  .booking-step {
    padding-left: 2.7rem;
  }
  .booking-flow::before {
    left: 1rem;
  }
  .booking-step__marker {
    left: 1rem;
  }
  .booking-step__toggle {
    grid-template-columns: minmax(0, 1fr) 1rem;
    gap: 0.75rem;
  }
  .booking-step__summary {
    grid-column: 1 / -1;
    max-width: none;
    text-align: left;
  }
    .approval-strip {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .approval-strip div:nth-child(2) {
    border-right: 0;
  }
  .approval-strip div:nth-child(2)::after {
    display: none;
  }
  .approval-strip div:nth-child(-n + 2) {
    border-bottom: 1px solid var(--line);
  }
  .proof-lab__grid {
    grid-template-columns: 1fr;
  }
  .poster-proof {
    width: min(100%, 320px);
  }
}
@media (max-width: 520px) {
  .space-choice {
    align-items: flex-start;
    flex-direction: column;
  }
  .space-choice__price {
    width: 100%;
    min-width: 0;
    text-align: left;
  }
    .approval-strip {
    grid-template-columns: 1fr;
  }
  .approval-strip div,
  .approval-strip div:nth-child(2) {
    border-right: 0;
    border-bottom: 1px solid var(--line);
  }
  .approval-strip div::after,
  .approval-strip div:nth-child(2)::after {
    display: block;
    left: 2.05rem;
    right: auto;
    top: 3rem;
    bottom: -0.8rem;
    width: 3px;
    height: auto;
  }
  .approval-strip div:last-child {
    border-bottom: 0;
  }
  .approval-strip div:last-child::after {
    display: none;
  }
}

/* ------------------------------------------------------------------ */
/* Homepage platform scene: a full width band that separates the hero  */
/* from the rest of the page. A Tube train slides in, a figure on the  */
/* platform puts the Art Below poster up on the wall, and the red route */
/* on the poster draws itself in. It plays once on load and is a scene, */
/* not a boxed illustration, so it reads as its own moment rather than  */
/* a templated hero image.                                             */
/* ------------------------------------------------------------------ */

/* The wordmark printed on the poster. Set here so the SVG text picks up Figtree
   rather than the browser default serif. */
.poster-caption {
  font-family: 'Figtree', system-ui, sans-serif;
  font-weight: 600;
  font-size: 15px;
  letter-spacing: -0.01em;
  fill: var(--ink);
}

/* The scene plays only once it scrolls into view: the .scene section is given a
   .play class by an IntersectionObserver, so it is a moment you arrive at rather
   than something that fires unseen while you are still up at the hero. The initial
   (pre-play) state lives on the base rules; the animations live under .scene.play,
   inside a no-preference media query so reduced motion never triggers them. */

/* 1. The train slides in from the right and settles on the track. */
.scene-train {
  transform: translateX(380px);
  opacity: 0;
}
@keyframes sceneTrainIn {
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

/* 2. The figure on the platform fades up once the train has arrived. */
.scene-figure {
  opacity: 0;
  transform: translateY(6px);
  transform-box: fill-box;
  transform-origin: bottom center;
}
@keyframes sceneFigureIn {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* 3. The poster goes up on the wall: it scales up from its base as if lifted. */
.scene-poster {
  opacity: 0;
  transform: scale(0.9);
  transform-box: fill-box;
  transform-origin: bottom center;
}
@keyframes scenePosterUp {
  to {
    opacity: 1;
    transform: scale(1);
  }
}

/* 4. The red route on the poster strokes itself in. pathLength="1" lets us animate
      the dash without needing the real path length. */
.scene-route {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}
@keyframes routeDraw {
  to {
    stroke-dashoffset: 0;
  }
}

@media (prefers-reduced-motion: no-preference) {
  .scene.play .scene-train {
    animation: sceneTrainIn 1.2s cubic-bezier(0.22, 1, 0.36, 1) 0.15s forwards;
  }
  .scene.play .scene-figure {
    animation: sceneFigureIn 0.7s cubic-bezier(0.22, 1, 0.36, 1) 0.95s forwards;
  }
  .scene.play .scene-poster {
    animation: scenePosterUp 0.7s cubic-bezier(0.34, 1.3, 0.64, 1) 1.45s forwards;
  }
  .scene.play .scene-route {
    animation: routeDraw 1.4s cubic-bezier(0.65, 0, 0.35, 1) 2s forwards;
  }
}

@media (prefers-reduced-motion: reduce) {
  .reveal {
    transition: none;
    opacity: 1;
    transform: none;
  }
  /* No scene animation for reduced motion; show the final, settled state. */
  .scene-train,
  .scene-figure,
  .scene-poster {
    opacity: 1;
    transform: none;
  }
  .scene-route {
    stroke-dashoffset: 0;
  }
  /* The train on the browse line carries its position in its transform, so only
     drop the transition here rather than resetting transform (which would knock
     the carriage off the line). It jumps to each stop instead of sliding. */
  .train {
    transition: none;
  }
}

/* ------------------------------------------------------------------ */
/* Intro splash: a brief branded loading moment on the homepage. The   */
/* Art Below poster sits centre screen while a Tube line loads beneath  */
/* it, a red train running past the interchange stops, then it clears   */
/* to reveal the page. Plays once per session, is skippable, and is     */
/* skipped entirely for reduced motion or on repeat visits (a small     */
/* head script adds .skip-intro before first paint).                    */
/* ------------------------------------------------------------------ */

.intro {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--paper);
  transition: opacity 0.6s ease;
}
.skip-intro .intro {
  display: none;
}
.intro.is-hiding {
  opacity: 0;
  pointer-events: none;
}
.intro__inner {
  display: flex;
  width: min(520px, 80vw);
  flex-direction: column;
  align-items: center;
}
.intro__poster {
  display: block;
  width: clamp(220px, 46vw, 320px);
  height: auto;
}
/* The poster IS the loader: its red tube line paints itself in (the run script
   sets stroke-dashoffset as the tube travels), a white dot pops at each of the
   four corners as the train reaches it (25 / 50 / 75 / 100 percent), and the loco
   is the bright head leading the paint. There is no separate loading bar. */
.intro-route {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}
.intro-dot {
  opacity: 0;
  transform: scale(0);
  transform-box: fill-box;
  transform-origin: center;
  transition: opacity 0.25s ease, transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.intro-dot.on {
  opacity: 1;
  transform: scale(1);
}
.intro-loco {
  opacity: 0;
  filter: drop-shadow(0 0 5px rgba(227, 30, 36, 0.55));
  transition: opacity 0.2s ease;
}
.intro-loco.on {
  opacity: 1;
}
.intro-caption {
  font-family: 'Figtree', system-ui, sans-serif;
  font-weight: 600;
  font-size: 24px;
  letter-spacing: -0.01em;
  fill: var(--ink);
}
.intro__pct {
  margin-top: 1.5rem;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.22em;
  color: #6b7079;
}
.intro__skip {
  position: absolute;
  right: 1.5rem;
  bottom: 1.25rem;
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #9a968e;
  background: none;
  border: none;
  cursor: pointer;
}
.intro__skip:hover {
  color: var(--ink);
}
