/* ==========================================================================
   ngbus.css — bus performance pages (league table, route pages, hero bus)

   Loaded by the bus shortcodes and layouts, not by head.html, so it costs
   nothing on the rest of the site.

   OWNERSHIP: unlike ngc2.* and the crime chart assets, these files are
   maintained here, in the Hugo repo. Nothing in E:\Road Data writes CSS or JS
   into this site — it writes data/bus/*.json and stops. So this file is safe to
   edit in place, which ngc2.css emphatically is not.

   House rule, inherited from ngc2.css: NO :root block — the host page owns the
   palette — and every var() carries a literal fallback. A component that
   silently loses a colour when dropped into a page is worse than one that
   repeats a hex.

   Colour policy: the bus red is DECORATION, never data. It says "London bus",
   and if it also meant "bad" the reader would have to hold two meanings for one
   hue. Severity is carried by the site's sequential blue ramp
   (--blue-light #E2EEF7 → --blue-dark #004D80), and the single warm accent
   --amber is spent, as everywhere else on this site, on the one number that
   matters.
   ========================================================================== */

.ngbus {
  --bus-red: #d6342c;
  /* Line colours, not fill colours. Copied from the validated trio in
     ngc2-core.js:38-95 — a 2-3px amber line comes in at 2.17:1 against the
     surface, below the 3:1 floor, so the line weight uses a darkened amber. */
  --bus-line-a: #b48544;
  --bus-line-b: #4A5A6B;
  /* A third terminus is rare (two routes today) but real: a route that forks
     has three measured ends. Teal is already the site's second accent. */
  --bus-line-c: #3F8E7E;
  --bus-band: #DDE3DF;
  font-family: var(--sans, 'DM Sans', system-ui, sans-serif);
  color: var(--ink, #1a2332);
}

.ngbus :is(h2, h3) { font-family: var(--sans, 'DM Sans', system-ui, sans-serif); }
.ngbus [hidden] { display: none !important; }

/* Visually hidden but reachable by a screen reader. Table captions and the
   search label are announced, not drawn. */
.ngbus-sr {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}


/* ── THE BUS ───────────────────────────────────────────────────────────────
   Both plates are filtered (fractal-noise grain on the red, a displacement
   wobble on the ink), and a filtered subtree re-rasterises whenever anything
   inside it moves. So the slide is on the <svg> itself and the red plate only
   ever changes opacity. Nothing inside a filtered group is animated; moving the
   plate's transform instead cost ~7x the frame time on a mid-range laptop.

   THE WIREFRAME BUG, and why the animation is built the way it is now
   ------------------------------------------------------------------
   The first version sometimes finished with no red at all — an ink-line bus
   that stayed that way until you scrolled, then popped into colour. Two things
   made that possible, and both are designed out:

   1. The resting state depended on the animation. `animation-fill-mode: both`
      holds the final keyframe, so "red is visible" was a property of a
      COMPLETED animation rather than of the element. Anything that stopped the
      animation reaching its end left the plate at `opacity: 0`.
   2. `will-change: transform` promoted the <svg> to its own compositor layer
      for the life of the page. Chrome intermittently fails to paint SVG filter
      output into a freshly promoted layer; the content stays blank until
      something forces a repaint. Scrolling forces a repaint — which is exactly
      the symptom that was reported.

   So: the red plate is opaque in plain CSS, always, and `will-change` is gone.
   `backwards` fill means the keyframes can only ever hide something DURING the
   run; the moment it ends the element falls back to its own CSS. The wireframe
   state is unreachable except in the first 360ms of a run.

   AND THE ANIMATION STARTS FROM CSS, NOT FROM SCRIPT
   -------------------------------------------------
   An earlier version had JavaScript add an `is-running` class once the bus
   scrolled into view. That reintroduced a different visible fault: CSS painted
   the bus at rest, then the class arrived a frame or two later, `backwards`
   fill snapped it off-stage, and it drove in again. The bus appeared, vanished
   and re-entered.

   Declaring the animation here means the very first painted frame is already
   the first frame of the run — there is no settled state to flash. The script
   is now only a tidy-up: it strips the class once the run is over so nothing
   animation-shaped is left on the element. It has nothing to arm, so a browser
   that never loads it still sees the whole animation and still ends correct.
   ------------------------------------------------------------------------- */

.ngbus-bus {
  /* The motion, in one place. `/labs/bus/` overrides exactly these to field-test
     alternatives, so whatever wins there ports back as four numbers. */
  /* Variant D from /labs/bus/, chosen 28 Aug. The bus starts a length and a half
     outside the stage and brakes at the stop. The first version travelled 7% of
     its own width inside a box that never clipped it, so there was nothing for
     it to arrive from — the note every tester gave, and the reason these numbers
     are as large as they are. */
  --bus-travel: -150%;        /* how far left it starts, as a share of its width */
  --bus-scale-from: 1;        /* <1 grows into place; D does not use it */
  --bus-duration: 1250ms;
  --bus-delay: 0ms;           /* set when something else has to happen first */
  --bus-plate-delay: 360ms;   /* how late the red plate lands */
  --bus-plate-duration: 420ms;
  /* Fast in, then settles hard — something with brakes rather than a glide. */
  --bus-ease: cubic-bezier(0.16, 0.84, 0.28, 1);

  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

/* ── THE STAGE: a bus pulling up at a stop ─────────────────────────────────
   The stop is scenery and never moves. It is the fixed thing that makes the
   moving thing read as an arrival.

   The clipping at the left edge is not tidying-up, it IS the effect: the bus
   begins a length and a half outside the stage and drives in.

   Widths are shares of the stage, and both SVGs are drawn at the SAME scale
   because the stop's width is set to 160/420 of the bus's — 160 and 420 being
   their viewBox widths. Bottom-aligning then lines the two painted roads up,
   because bus-stop.html pads its viewBox to give the stop the same footroom the
   bus has. Change the bus's 78% and the stop follows automatically.
   ------------------------------------------------------------------------- */

.ngbus-stage {
  position: relative;
  /* One number governs the pair. The stop derives from it, so they cannot drift
     out of scale — and the total has to stay under 100% or the stop runs off the
     right-hand edge and the stage's own overflow:hidden crops its flag. */
  --stage-bus: 82%;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
}
.ngbus-stage .ngbus-bus { flex: 0 0 auto; width: var(--stage-bus); }
.ngbus-stage .ngbus-stop {
  flex: 0 0 auto;
  width: calc(var(--stage-bus) * 160 / 420);
  /* Negative on purpose. The stop's flag occupies only x=52-108 of its 160-unit
     canvas, so most of its box is empty air; laid out honestly the bus would
     halt a third of a bus-length short of the flag. Pulling it back puts the
     flag just ahead of the bus's front, where a real one stands. The two road
     lines overlap into one continuous road, which is the other reason this
     works rather than merely fits.

     The two numbers are tied: the gap between the bus's front and the flag is
     0.172 x --stage-bus + this margin, so raising the bus size without pulling
     the margin down opens the gap again. At 82% and -10% the gap is ~4% of the
     stage and the flag's right edge lands at ~93%, which fills the column
     without touching the edge. */
  margin-left: -10%;
  display: block;
  height: auto;
}
.ngbus-stage--nostop .ngbus-bus { width: 100%; }

/* Each drawing's own road, suppressed when it is standing on the stage's. */
.ngbus-stage .ngbus-road { display: none; }

.ngbus-stage__road {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  /* The road sits 20 units above the bottom of the bus's 420-wide viewBox, and
     one unit is (--stage-bus / 420) of the stage's WIDTH. A percentage padding
     resolves against the containing block's width — which is the reference
     needed here, and the reason this is padding rather than height. The box is
     40 units deep so the line can sit at its middle. */
  padding-bottom: calc(var(--stage-bus) * 40 / 420);
  pointer-events: none;
}
.ngbus-stage__road svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

/* The stop is scenery: fully drawn in plain CSS, never waiting on a script. */
.ngbus-stop__red { opacity: 0.92; }

.ngbus-bus__blind {
  font-family: var(--sans, 'DM Sans', system-ui, sans-serif);
  font-weight: 600;
  letter-spacing: 0.06em;
}

/* The resting state, owned by plain CSS and never by an animation. */
.ngbus-bus__red { opacity: 0.92; }

@keyframes ngbus-pull-in {
  from { transform: translate3d(var(--bus-travel), 0, 0) scale(var(--bus-scale-from)); }
  to   { transform: translate3d(0, 0, 0) scale(1); }
}
@keyframes ngbus-plate-down {
  from { opacity: 0; }
  /* No `to`: the element's own 0.92 is the end state, so the keyframes cannot
     become the only thing keeping the plate visible. */
}

.ngbus-bus--anim {
  animation: ngbus-pull-in var(--bus-duration) var(--bus-ease) var(--bus-delay) backwards;
  /* No will-change. An active transform animation gets composited on its own
     anyway, transiently; the persistent hint was what let the filter raster go
     missing. */
}
.ngbus-bus--anim .ngbus-bus__red {
  /* The red plate lands a beat after the ink. That lag is the whole screenprint
     gag, and 220ms is about where it reads as a second pass rather than a bug.
     `backwards`, not `both`: the from-state applies during the delay only, and
     afterwards the plate falls back to its own opacity rather than to a value
     the animation has to survive long enough to set. */
  animation: ngbus-plate-down var(--bus-plate-duration) ease-out var(--bus-plate-delay) backwards;
}

@media (prefers-reduced-motion: reduce) {
  /* No fill to unwind: with `backwards`, an animation that never runs leaves the
     element exactly as the rules above drew it. */
  .ngbus-bus--anim,
  .ngbus-bus--anim .ngbus-bus__red { animation: none; }
}


/* ── HERO ──────────────────────────────────────────────────────────────── */

.ngbus-hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: 1.5rem 2rem;
  align-items: center;
  margin: 0 0 2.25rem;
}

.ngbus-hero__figure { margin: 0; }

.ngbus-hero__stat {
  font-family: var(--serif, 'Libre Baskerville', Georgia, serif);
  font-size: clamp(2.4rem, 7vw, 3.4rem);
  line-height: 1.05;
  color: var(--blue-dark, #004d80);
  font-variant-numeric: tabular-nums;
}
.ngbus-hero__stat small {
  display: block;
  margin-top: 0.1rem;
  font-family: var(--sans, 'DM Sans', system-ui, sans-serif);
  font-size: 0.82rem;
  font-weight: 400;
  letter-spacing: 0.02em;
  line-height: 1.45;
  color: var(--mid-grey, #8a9490);
}

.ngbus-hero__caption {
  margin-top: 0.55rem;
  font-size: 0.82rem;
  color: var(--mid-grey, #8a9490);
}

@media (max-width: 640px) {
  .ngbus-hero { grid-template-columns: 1fr; gap: 1rem; }
  .ngbus-hero__figure { max-width: 340px; }
}


/* ── CONTROLS ──────────────────────────────────────────────────────────────
   Chips match .ngl2-chip on the rail page: blue when active, not amber. Amber
   is the data highlight colour on this site, and spending it on UI chrome makes
   the chrome compete with the numbers.
   ------------------------------------------------------------------------- */

.ngbus-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
  margin: 0 0 0.9rem;
}

.ngbus-chips { display: flex; flex-wrap: wrap; gap: 0.35rem; }

.ngbus-chip {
  font: inherit;
  font-size: 0.82rem;
  line-height: 1;
  padding: 0.45rem 0.7rem;
  border: 1px solid var(--warm-grey, #dde3df);
  border-radius: 999px;
  background: var(--white, #ffffff);
  color: var(--ink, #1a2332);
  cursor: pointer;
  transition: background 0.18s, border-color 0.18s, color 0.18s;
}
.ngbus-chip:hover:not(:disabled) { border-color: var(--blue-muted, #5a9fd4); }
.ngbus-chip[aria-pressed="true"] {
  background: var(--blue-light, #E2EEF7);
  border-color: var(--blue, #0070BA);
  color: var(--blue-dark, #004d80);
  font-weight: 600;
}
.ngbus-chip:disabled { opacity: 0.45; cursor: not-allowed; }
.ngbus-chip:focus-visible,
.ngbus-search input:focus-visible {
  outline: 2px solid var(--blue, #0070BA);
  outline-offset: 2px;
}

.ngbus-search { margin-left: auto; }
.ngbus-search input {
  font: inherit;
  font-size: 0.85rem;
  padding: 0.45rem 0.7rem;
  min-width: 12rem;
  border: 1px solid var(--warm-grey, #dde3df);
  border-radius: 4px;
  background: var(--white, #ffffff);
  color: var(--ink, #1a2332);
}
.ngbus-search input::placeholder { color: var(--mid-grey, #8a9490); }

.ngbus-count {
  font-size: 0.78rem;
  color: var(--mid-grey, #8a9490);
  font-variant-numeric: tabular-nums;
}

/* Frequent / timetabled. A heavier control than the view chips beneath it,
   because it changes what is being measured, not just which rows are shown. */
.ngbus-tabs {
  display: flex;
  gap: 0;
  margin: 0 0 0.5rem;
  border-bottom: 1px solid var(--warm-grey, #dde3df);
}
.ngbus-tab {
  font: inherit;
  font-size: 0.92rem;
  font-weight: 600;
  padding: 0.55rem 0.9rem 0.5rem;
  margin-bottom: -1px;
  border: 0;
  border-bottom: 3px solid transparent;
  background: none;
  color: var(--mid-grey, #8a9490);
  cursor: pointer;
}
.ngbus-tab:hover { color: var(--blue-dark, #004d80); }
.ngbus-tab[aria-pressed="true"] { color: var(--blue-dark, #004d80); border-bottom-color: var(--blue, #0070BA); }
.ngbus-tab:focus-visible { outline: 2px solid var(--blue, #0070BA); outline-offset: 2px; }
.ngbus-tab__n {
  font-size: 0.72rem; font-weight: 600; font-variant-numeric: tabular-nums;
  margin-left: 0.3rem; padding: 0.1rem 0.4rem; border-radius: 999px;
  background: var(--warm-grey, #dde3df); color: var(--ink, #1a2332);
}
.ngbus-tab[aria-pressed="true"] .ngbus-tab__n { background: var(--blue-light, #E2EEF7); color: var(--blue-dark, #004d80); }
.ngbus-tabnote { font-size: 0.85rem; line-height: 1.55; color: var(--deep, #243142); margin: 0 0 0.8rem; }

@media (max-width: 560px) {
  .ngbus-search { margin-left: 0; width: 100%; }
  .ngbus-search input { width: 100%; }
}


/* ── THE LEAGUE TABLE ──────────────────────────────────────────────────────
   The first table on this site. Everything data-shaped here has been a chart
   with a tooltip; 600 routes are a list, and pretending otherwise would mean
   hiding 599 of them behind a hover.

   The table scrolls inside its own box. The page body must never scroll
   sideways — on a phone that is the difference between a table and a mess.
   ------------------------------------------------------------------------- */

.ngbus-tablewrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  border: 1px solid var(--warm-grey, #dde3df);
  border-radius: 6px;
  background: var(--white, #ffffff);
  max-height: 34rem;
  overflow-y: auto;
}

.ngbus-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.875rem;
  font-variant-numeric: tabular-nums;
}

.ngbus-table th,
.ngbus-table td {
  padding: 0.5rem 0.75rem;
  text-align: right;
  white-space: nowrap;
  border-bottom: 1px solid var(--warm-grey, #dde3df);
}
.ngbus-table th:first-child,
.ngbus-table td:first-child,
.ngbus-table th:nth-child(2),
.ngbus-table td:nth-child(2) { text-align: left; }

.ngbus-table thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--paper, #f3f6f4);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--mid-grey, #8a9490);
  border-bottom: 1px solid var(--warm-grey, #dde3df);
}

.ngbus-table thead th button {
  font: inherit;
  color: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}
.ngbus-table thead th button:hover { color: var(--blue, #0070BA); }
.ngbus-table thead th[aria-sort] button { color: var(--blue-dark, #004d80); }
.ngbus-table thead th[aria-sort] button::after {
  content: " \25BE";                                   /* ▾ */
  font-size: 0.9em;
}
.ngbus-table thead th[aria-sort="ascending"] button::after { content: " \25B4"; }

.ngbus-table tbody tr:hover { background: var(--blue-light, #E2EEF7); }
/* The whole row navigates (see ngbus-table.js). Three testers out of three
   tried to click the row rather than the route number, so the hover highlight
   was promising something only the link delivered. */
.ngbus-table tbody tr[data-href] { cursor: pointer; }
.ngbus-table tbody tr:last-child td { border-bottom: 0; }

.ngbus-rank { color: var(--mid-grey, #8a9490); width: 2.5rem; }

.ngbus-route a {
  font-weight: 700;
  font-size: 0.95rem;
  text-decoration: none;
  color: var(--blue-dark, #004d80);
}
.ngbus-route a:hover { color: var(--ink, #1a2332); text-decoration: underline; }
.ngbus-route__where {
  display: block;
  font-weight: 400;
  font-size: 0.75rem;
  color: var(--mid-grey, #8a9490);
  white-space: normal;
  max-width: 20rem;
}

/* The headline number, with its own magnitude behind it. A bar in the site's
   sequential blue rather than a red/green scale: this is one quantity getting
   larger, not a good/bad judgement, and a diverging scale would assert a
   midpoint the data does not have. */
.ngbus-metric { position: relative; }
.ngbus-metric__bar {
  position: absolute;
  right: 0.75rem;
  bottom: 0.3rem;
  height: 3px;
  background: var(--blue, #0070BA);
  opacity: 0.42;
  border-radius: 2px;
  pointer-events: none;
}
.ngbus-metric__val { position: relative; font-weight: 600; }
.ngbus-metric--lead .ngbus-metric__val { color: var(--blue-dark, #004d80); }

/* "no data" is a first-class state, not an empty cell. A dash reads as a small
   number and a zero reads as perfect punctuality; both would flatter a route we
   simply did not watch. */
.ngbus-nd {
  color: var(--mid-grey, #8a9490);
  font-size: 0.74rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-variant-numeric: normal;
  border-bottom: 1px dotted var(--mid-grey, #8a9490);
  cursor: help;
}

/* The timetable's own figure, beside the measured one in the same cell: a
   reference, so it reads as quieter than the number it explains. */
.ngbus-tt {
  font-size: 0.74rem; color: var(--mid-grey, #8a9490); cursor: help;
  font-variant-numeric: tabular-nums;
}
.ngbus-tt::before { content: "vs "; }

.ngbus-delta { font-size: 0.82rem; }
.ngbus-delta--up { color: var(--blue-dark, #004d80); }      /* waits got longer */
.ngbus-delta--down { color: var(--teal, #3F8E7E); }         /* waits got shorter */
.ngbus-delta--flat { color: var(--mid-grey, #8a9490); }
/* Timetabled routes: a rise in on-time share is good news, so these are named
   for what the change means rather than which way the number went. */
.ngbus-delta--worse { color: var(--blue-dark, #004d80); }
.ngbus-delta--better { color: var(--teal, #3F8E7E); }

.ngbus-spark { display: block; width: 78px; height: 22px; margin-left: auto; }
.ngbus-spark__line { fill: none; stroke: var(--bus-line-a, #b48544); stroke-width: 1.6;
                     stroke-linecap: round; stroke-linejoin: round; }
.ngbus-spark__dot { fill: var(--bus-line-a, #b48544); }
.ngbus-spark__gap { fill: var(--bus-band, #DDE3DF); opacity: 0.75; }

.ngbus-empty {
  padding: 2rem 1rem;
  text-align: center;
  color: var(--mid-grey, #8a9490);
  font-size: 0.9rem;
}

.ngbus-foot {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
  justify-content: space-between;
  margin-top: 0.6rem;
  font-size: 0.75rem;
  color: var(--mid-grey, #8a9490);
}

@media (max-width: 640px) {
  .ngbus-table th, .ngbus-table td { padding: 0.45rem 0.55rem; }
  .ngbus-route__where { max-width: 11rem; }
}


/* ── ROUTE TIMELINE CHART ─────────────────────────────────────────────────── */

.ngbus-chart {
  margin: 1.5rem 0 0.5rem;
  padding: 1.25rem 1.25rem 1rem;
  border: 1px solid var(--warm-grey, #dde3df);
  border-radius: 6px;
  background: var(--white, #ffffff);
}
.ngbus-chart__title {
  font-size: 1.05rem;
  font-weight: 600;
  margin-bottom: 0.15rem;
}
.ngbus-chart__subtitle {
  font-size: 0.82rem;
  color: var(--mid-grey, #8a9490);
  margin-bottom: 0.85rem;
}
.ngbus-chart__plot svg { display: block; width: 100%; height: auto; }

.ngbus-ax { fill: var(--mid-grey, #8a9490); font-size: 11px; font-variant-numeric: tabular-nums; }
.ngbus-grid { stroke: var(--warm-grey, #dde3df); stroke-width: 1; }
.ngbus-base { stroke: var(--mid-grey, #8a9490); stroke-width: 1.5; }
.ngbus-hole { fill: var(--bus-band, #DDE3DF); opacity: 0.55; }
/* A weaker version of the same problem, so a weaker version of the same mark:
   the week published, but on visibly less watching than its neighbours. */
.ngbus-thin { fill: var(--bus-band, #DDE3DF); opacity: 0.22; }
.ngbus-hole__label { fill: var(--mid-grey, #8a9490); font-size: 10.5px; letter-spacing: 0.04em; }
.ngbus-line { fill: none; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
.ngbus-line--a { stroke: var(--bus-line-a, #b48544); }
.ngbus-line--b { stroke: var(--bus-line-b, #4A5A6B); }
.ngbus-dot--a { fill: var(--bus-line-a, #b48544); }
.ngbus-dot--b { fill: var(--bus-line-b, #4A5A6B); }
.ngbus-line--c { stroke: var(--bus-line-c, #3F8E7E); }
.ngbus-dot--c { fill: var(--bus-line-c, #3F8E7E); }

.ngbus-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1rem;
  margin-top: 0.6rem;
  font-size: 0.78rem;
  color: var(--mid-grey, #8a9490);
}
.ngbus-legend__item { display: inline-flex; align-items: center; gap: 0.35rem; }
.ngbus-legend__swatch { width: 14px; height: 3px; border-radius: 2px; display: inline-block; }
/* Shadings read as areas, so their key is a block rather than a line. */
.ngbus-legend__band { width: 14px; height: 11px; border-radius: 2px; display: inline-block; }
.ngbus-legend__band.ngbus-hole { background: var(--bus-band, #DDE3DF); opacity: 0.75; }
.ngbus-legend__band.ngbus-thin { background: var(--bus-band, #DDE3DF); opacity: 0.35; }

.ngl2-cov { display: block; margin-top: 0.2rem; color: var(--mid-grey, #8a9490); }

.ngbus-thinmark {
  font-size: 0.72rem; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--mid-grey, #8a9490); border-bottom: 1px dotted var(--mid-grey, #8a9490);
  cursor: help; font-variant-numeric: normal;
}


/* ── ROUTE PAGE ───────────────────────────────────────────────────────────── */

.ngbus-strap {
  font-family: var(--serif, 'Libre Baskerville', Georgia, serif);
  font-size: 1.12rem;
  line-height: 1.6;
  color: var(--deep, #243142);
  margin: 0 0 1.5rem;
  padding-left: 1rem;
  border-left: 3px solid var(--blue-light, #E2EEF7);
}

.ngbus-termini {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  gap: 1rem;
  margin: 1.5rem 0;
}
.ngbus-terminus {
  padding: 1rem 1.1rem;
  border: 1px solid var(--warm-grey, #dde3df);
  border-radius: 6px;
  background: var(--white, #ffffff);
}
.ngbus-terminus__name {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--mid-grey, #8a9490);
  margin-bottom: 0.5rem;
}
.ngbus-terminus dl { display: grid; grid-template-columns: 1fr auto; gap: 0.3rem 1rem; }
.ngbus-terminus dt { font-size: 0.82rem; color: var(--mid-grey, #8a9490); }
.ngbus-terminus dd {
  font-size: 0.95rem;
  font-weight: 600;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Sits between the data caveats and the disclaimer: the last thing on the page
   that is of any practical use to someone who actually wants to catch the bus. */
.ngbus-tfl {
  margin-top: 1.5rem;
  font-size: 0.9rem;
  line-height: 1.6;
}

.ngbus-attr {
  margin-top: 2rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--warm-grey, #dde3df);
  font-size: 0.74rem;
  line-height: 1.55;
  color: var(--mid-grey, #8a9490);
}

.ngbus-az {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin: 1.5rem 0;
}
.ngbus-az a {
  display: inline-block;
  min-width: 3.1rem;
  padding: 0.35rem 0.5rem;
  text-align: center;
  font-size: 0.85rem;
  font-weight: 600;
  text-decoration: none;
  border: 1px solid var(--warm-grey, #dde3df);
  border-radius: 4px;
  background: var(--white, #ffffff);
  color: var(--blue-dark, #004d80);
}
.ngbus-az a:hover { border-color: var(--blue, #0070BA); background: var(--blue-light, #E2EEF7); }
.ngbus-az a[data-nodata="1"] { color: var(--mid-grey, #8a9490); }


/* ── ROUTE PAGE: outages, weekly figures, hover readout ─────────────────── */

.ngbus-holes { margin: 1.25rem 0 0; }
.ngbus-holes > summary {
  cursor: pointer;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--blue-dark, #004d80);
  padding: 0.4rem 0;
}
.ngbus-holes > summary:focus-visible { outline: 2px solid var(--blue, #0070BA); outline-offset: 2px; }
.ngbus-holes__hint {
  font-size: 0.8rem; line-height: 1.55; color: var(--mid-grey, #8a9490);
  margin: 0.3rem 0 0.6rem;
}

.ngbus-holelist { list-style: none; margin: 0; padding: 0; font-size: 0.82rem; line-height: 1.5; }
.ngbus-holelist li {
  display: grid;
  grid-template-columns: 10.5rem 1fr;
  gap: 0 0.9rem;
  padding: 0.35rem 0;
  border-bottom: 1px solid var(--warm-grey, #dde3df);
}
.ngbus-holelist li:last-child { border-bottom: 0; }
.ngbus-holelist__when { font-weight: 600; font-variant-numeric: tabular-nums; }
.ngbus-holelist__why { color: var(--mid-grey, #8a9490); }
@media (max-width: 560px) {
  .ngbus-holelist li { grid-template-columns: 1fr; gap: 0.1rem; }
}

.ngbus-weeks { margin: 1.25rem 0 0; }
.ngbus-weeks summary {
  cursor: pointer;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--blue-dark, #004d80);
  padding: 0.4rem 0;
}
.ngbus-weeks summary:focus-visible { outline: 2px solid var(--blue, #0070BA); outline-offset: 2px; }
.ngbus-weeks .ngbus-tablewrap { margin-top: 0.5rem; max-height: 26rem; }
.ngbus-weeks__hint { font-size: 0.8rem; line-height: 1.55; color: var(--mid-grey, #8a9490); margin: 0.3rem 0 0; }
.ngbus-weeks .ngbus-table th:first-child,
.ngbus-weeks .ngbus-table td:first-child { text-align: left; }
.ngbus-weeks .ngbus-table th:nth-child(2),
.ngbus-weeks .ngbus-table td:nth-child(2) { text-align: right; }

/* The readout follows the plot, so it is positioned inside the chart card and
   offset by a percentage of the plot width rather than by pixels — the SVG is
   fluid and a pixel offset would drift as the column narrows. */
.ngbus-chart { position: relative; }
/* Top-pinned, and set left or right by the script to the side of the card away
   from the pointer, so it never covers the weeks being read. */
.ngbus-tip {
  position: absolute;
  top: 4.5rem;
  z-index: 2;
  pointer-events: none;
  padding: 0.45rem 0.6rem;
  border: 1px solid var(--warm-grey, #dde3df);
  border-radius: 4px;
  background: var(--white, #ffffff);
  box-shadow: 0 2px 10px rgba(26, 35, 50, 0.12);
  font-size: 0.78rem;
  line-height: 1.45;
  white-space: normal;
  max-width: 20rem;
  font-variant-numeric: tabular-nums;
}
.ngbus-tip[hidden] { display: none; }
/* The named gap behind a week, set apart from the figures above it. */
.ngl2-gap {
  display: block;
  margin-top: 0.35rem;
  padding-top: 0.35rem;
  border-top: 1px solid var(--warm-grey, #dde3df);
  color: var(--mid-grey, #8a9490);
  font-variant-numeric: normal;
}
.ngl2-gap strong { color: var(--ink, #1a2332); }

.ngbus-hit { cursor: crosshair; }
.ngbus-hit--replay { cursor: pointer; }        /* day view: opens the replay */
.ngbus-replaylink { margin: .4rem 0 1.4rem; font-size: .95rem; }
.ngbus-watch { white-space: nowrap; font-weight: 600; }
.ngbus-cursor { stroke: var(--mid-grey, #8a9490); stroke-width: 1; stroke-dasharray: 3 3; pointer-events: none; }
/* The same measure as the solid line, but as the timetable alone would give it:
   the gap between the two is what the buses are doing. */
.ngbus-line--tt { stroke-dasharray: 5 4; opacity: 0.55; stroke-width: 1.8; }
.ngbus-legend__swatch--tt {
  background: none !important;
  border-top: 2px dashed var(--mid-grey, #8a9490);
  height: 0; align-self: center;
}
.ngl2-tt { color: var(--mid-grey, #8a9490); }
.ngbus-ax--year { fill: var(--ink, #1a2332); font-weight: 600; }
/* The span chips sit apart from the measure chips: one says what, the other when. */
.ngbus-chips--range { margin-left: auto; }
@media (max-width: 560px) { .ngbus-chips--range { margin-left: 0; } }

@media (max-width: 640px) {
  /* Fingers do not hover. The weekly figures table below the chart is the
     touch-friendly way to read exact numbers, and it is always there. */
  .ngbus-tip { display: none; }
}


/* ── CURTAILMENTS ─────────────────────────────────────────────────────────
   Journeys turned back before the end of the route. A different failure from a
   long wait — the bus came, it just did not go where it said — so it gets its
   own column and its own block rather than being folded into excess wait.

   Same colour policy as everything else here: severity rides the site's
   sequential blue, and the bus red stays decoration. Nothing on this page uses
   red to mean bad.
   ------------------------------------------------------------------------ */

.ngbus-curt { font-variant-numeric: tabular-nums; }
/* The backend flags routes that cross its reporting threshold — those are the
   ones with a detail block on their page, and the only ones where this figure
   is the notable thing about the route. */
.ngbus-curt--flagged { font-weight: 600; color: var(--blue-dark, #004d80); }

.ngbus-curtblock {
  margin: 2rem 0 0;
  padding: 1.25rem 1.25rem 1.1rem;
  border: 1px solid var(--warm-grey, #dde3df);
  border-radius: 6px;
  background: var(--white, #ffffff);
}
.ngbus-curtblock__title { font-size: 1.05rem; font-weight: 600; margin-bottom: 0.5rem; }
.ngbus-curtblock__sub {
  font-size: 0.72rem; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--mid-grey, #8a9490); margin: 1.25rem 0 0.5rem;
}
.ngbus-curtblock__lead {
  font-size: 0.92rem; line-height: 1.6; color: var(--ink, #1a2332); margin-bottom: 1rem;
}
.ngbus-curtblock__lead strong { font-variant-numeric: tabular-nums; }
.ngbus-curtblock__note {
  font-size: 0.78rem; line-height: 1.5; color: var(--mid-grey, #8a9490); margin-top: 0.5rem;
}

.ngbus-hours { display: block; width: 100%; height: auto; }
.ngbus-hours__bar { fill: var(--blue, #0070BA); opacity: 0.55; }
/* An hour nobody watched, shaded rather than drawn as a zero — the same rule the
   sparkline and the timeline follow. */
.ngbus-hours__gap { fill: var(--bus-band, #DDE3DF); opacity: 0.6; }
.ngbus-hours__base { stroke: var(--mid-grey, #8a9490); stroke-width: 1; }
.ngbus-hours__ax {
  fill: var(--mid-grey, #8a9490); font-size: 10.5px;
  font-family: var(--sans, 'DM Sans', system-ui, sans-serif);
  font-variant-numeric: tabular-nums;
}
.ngbus-hours__peak { font-size: 10px; }
.ngbus-hours__grid { stroke: var(--warm-grey, #dde3df); stroke-width: 1; }
.ngbus-hours__unit { font-size: 10px; }
.ngbus-hours__hit { fill: transparent; cursor: help; }
.ngbus-hours__col:hover .ngbus-hours__bar { opacity: 0.9; }

.ngbus-turnstops { list-style: none; margin: 0; padding: 0; }
.ngbus-turnstops li {
  display: flex; justify-content: space-between; gap: 1rem;
  padding: 0.4rem 0; border-bottom: 1px solid var(--warm-grey, #dde3df);
  font-size: 0.88rem;
}
.ngbus-turnstops li:last-child { border-bottom: 0; }
.ngbus-turnstops__count { font-variant-numeric: tabular-nums; font-weight: 600; }


/* ── ROUTE PAGE: about this data ─────────────────────────────────────────
   Facts about the observation rather than the service — how much of the week
   we collected, how many departures we saw, which listed termini produce no
   figure. Kept apart from the terminus cards so nobody reads "coverage 100%"
   as a verdict on the buses.
   ------------------------------------------------------------------------ */

.ngbus-about { margin: 1.75rem 0 0; }
.ngbus-about > summary {
  cursor: pointer;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--blue-dark, #004d80);
  padding: 0.4rem 0;
}
.ngbus-about > summary:focus-visible { outline: 2px solid var(--blue, #0070BA); outline-offset: 2px; }
.ngbus-about__facts { margin: 0.4rem 0 0; font-size: 0.85rem; line-height: 1.6; }
.ngbus-about__facts dt {
  font-size: 0.72rem; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--mid-grey, #8a9490); margin: 0.9rem 0 0.15rem;
}
.ngbus-about__facts dt:first-child { margin-top: 0.3rem; }
.ngbus-about__facts dd { margin: 0; color: var(--ink, #1a2332); }
.ngbus-about__facts strong { font-variant-numeric: tabular-nums; }
.page-content .ngbus-about__list { list-style: none; margin: 0 0 0.35rem; padding: 0; font-size: inherit; line-height: inherit; color: inherit; font-variant-numeric: tabular-nums; }
.page-content .ngbus-about__list li { font-size: inherit; line-height: inherit; margin: 0; padding: 0; }
.ngbus-about .ngbus-holes { margin-top: 1rem; }

/* A label with an explanation behind it. The same dotted underline as "no data",
   so the page has one convention for "hover me for why". */
.ngbus-help { border-bottom: 1px dotted var(--mid-grey, #8a9490); cursor: help; }


/* ── ROUTE PAGE: the worst gap, and the time-of-day bands ─────────────────
   Both came with the timing-point measure: a gap is now a place and a moment
   on the route, and the day splits into bands a rider recognises.
   ------------------------------------------------------------------------ */

.ngbus-worstgap {
  margin: 0 0 1.5rem;
  padding: 0.85rem 1rem;
  border-left: 3px solid var(--warm-grey, #dde3df);
  background: var(--white, #ffffff);
  font-size: 0.9rem;
  line-height: 1.6;
}
.ngbus-worstgap strong { font-variant-numeric: tabular-nums; }

/* Part of the figure above it, not a figure of its own — so it sits under the
   number in small type rather than beside it in the same weight. */
.ngbus-terminus__sub {
  display: block;
  font-size: 0.72rem;
  font-weight: 400;
  line-height: 1.4;
  color: var(--mid-grey, #8a9490);
  font-variant-numeric: normal;
}

.ngbus-bands { margin: 2rem 0 0; }
.ngbus-bands__title { font-size: 1.05rem; font-weight: 600; margin-bottom: 0.35rem; }
.ngbus-bands__lead {
  font-size: 0.85rem; line-height: 1.6; color: var(--mid-grey, #8a9490); margin: 0 0 0.9rem;
}
.page-content .ngbus-bandlist {
  list-style: none; margin: 0; padding: 0;
  font-size: inherit; line-height: inherit; color: inherit;
}
.page-content .ngbus-bandlist li {
  display: grid;
  grid-template-columns: 11rem 1fr 3.2rem;
  align-items: center;
  gap: 0 0.75rem;
  margin: 0;
  padding: 0.35rem 0;
  font-size: 0.85rem;
}
.ngbus-bandlist__label { color: var(--ink, #1a2332); }
.ngbus-bandlist__bar {
  position: relative;
  height: 10px;
  border-radius: 2px;
  background: var(--warm-grey, #dde3df);
  overflow: visible;
}
.ngbus-bandlist__fill {
  display: block; height: 100%;
  border-radius: 2px;
  background: var(--blue, #0070BA);
  opacity: 0.55;
}
/* The timetable's own share, as a mark on the bar rather than a second bar:
   it is a reference line, not a competing quantity. */
.ngbus-bandlist__tt {
  position: absolute; top: -3px; bottom: -3px; width: 2px;
  background: var(--ink, #1a2332);
  opacity: 0.55;
  cursor: help;
}
.ngbus-bandlist__val { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; }
.ngbus-bandlist__ewt {
  grid-column: 2 / -1;
  font-size: 0.74rem; color: var(--mid-grey, #8a9490); font-variant-numeric: tabular-nums;
}
@media (max-width: 560px) {
  .page-content .ngbus-bandlist li { grid-template-columns: 1fr 3.2rem; }
  .ngbus-bandlist__label { grid-column: 1 / -1; }
  /* Full width, or the excess-wait note wraps to three lines in the value column. */
  .ngbus-bandlist__ewt { grid-column: 1 / -1; }
}

.ngbus-tfl-compare { margin: 2rem 0 0; }
.ngbus-tfl-compare__when {
  display: block; font-size: 0.74rem; color: var(--mid-grey, #8a9490); font-variant-numeric: tabular-nums;
}


/* Journeys cut short, per direction (28 Sep): the week's count for the
   direction, then where its buses turned round. */
.ngbus-curtblock__dirline {
  font-size: 0.88rem; line-height: 1.55; margin: 0 0 0.5rem; font-variant-numeric: tabular-nums;
}
.ngbus-turnstops__far {
  display: block; font-size: 0.74rem; color: var(--mid-grey, #8a9490); font-variant-numeric: tabular-nums;
}


/* Where along the route the wait from buses cut short lands (29 Sep): one bar
   per stop, pale for the wait without the cut-short buses, strong for what they
   added, a tick for the timetable's wait. */
.ngbus-stopcost { display: block; width: 100%; height: auto; margin: 0.25rem 0 0.25rem; }
.ngbus-stopcost__base { fill: var(--blue, #0070BA); opacity: 0.25; }
.ngbus-stopcost__add { fill: var(--blue-dark, #004d80); opacity: 0.85; }
.ngbus-stopcost__tt { stroke: var(--ink, #1a2332); stroke-width: 1.2; opacity: 0.6; }
.ngbus-stopcost__worst {
  fill: var(--blue-dark, #004d80); font-size: 10.5px; font-weight: 600;
  font-family: var(--sans, 'DM Sans', system-ui, sans-serif);
}
.ngbus-stopcost__col:hover .ngbus-stopcost__base { opacity: 0.45; }

/* ---- TfL notices (ngbus-notices-box.js; the replay has its own panel) ----
   Outlined in amber: amber is the colour kept for the one thing that matters,
   and a diversion is usually why the reader came. TfL's text is shown exactly
   as sent, so it gets the plain reading face, not the page's display one. */
.ngbus-notices {
  margin: 1.4rem 0 1.8rem;
  padding: 1rem 1.2rem 0.8rem;
  border: 2px solid var(--amber, #E0A24E);
  border-radius: 8px;
  background: #fffaf2;
}
.ngbus-notices[hidden] { display: none; }
.ngn-title { margin: 0 0 0.4rem; font-size: 1.15rem; }
.ngn-group { margin: 0.8rem 0 0.3rem; font-size: 0.95rem; text-transform: none; }
.ngn-group--later { color: var(--mid-grey, #8a9490); }
.ngn-list { list-style: none; margin: 0; padding: 0; }
.ngn-item { padding: 0.5rem 0; border-top: 1px solid #f1e2c6; }
.ngn-item:first-child { border-top: 0; }
.ngn-head { font-weight: 600; }
.ngn-text { white-space: pre-line; line-height: 1.45; }
.ngn-meta { margin-top: 0.2rem; font-size: 0.85rem; color: var(--mid-grey, #6b746f); }
.ngn-item--unplanned .ngn-meta::before { content: "\25CF\00A0"; color: var(--amber, #E0A24E); }
.ngn-source { margin: 0.6rem 0 0; font-size: 0.8rem; color: var(--mid-grey, #6b746f); }

/* the chart's day view: a day with a TfL notice in force */
.ngbus-noticemark { fill: var(--amber, #E0A24E); }
.ngbus-legend__notice {
  display: inline-block; width: 0; height: 0; margin-right: 6px; vertical-align: middle;
  border-left: 5px solid transparent; border-right: 5px solid transparent;
  border-top: 8px solid var(--amber, #E0A24E);
}
