/* BusMaps.uk — shopfront styles. Light/dark aware, responsive, no deps. */

:root {
  --bg: #ffffff;
  --surface: #f6f8fb;
  --surface-2: #eef2f8;
  --text: #17202e;
  --muted: #59626f;
  --border: #e2e7ef;
  --primary: #1b4db3;
  --primary-ink: #ffffff;
  --primary-soft: #e8eefb;
  --accent: #e08a00;
  --accent-ink: #17202e;
  --accent-tint-ink: #8a5700;
  --ok: #157347;
  --err: #b42318;
  --radius: 14px;
  --shadow: 0 1px 2px rgba(20, 30, 50, .06), 0 8px 24px rgba(20, 30, 50, .06);
  --tab-lift: 0 1px 2px rgba(20, 30, 50, .12);
  --maxw: 1080px;
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0f141b;
    --surface: #161d27;
    --surface-2: #1c2430;
    --text: #e7ecf3;
    --muted: #9aa6b6;
    --border: #273240;
    --primary: #5a8dff;
    --primary-ink: #0b1017;
    --primary-soft: #182238;
    --accent: #f4b451;
    --accent-tint-ink: #ffd78a;
    --ok: #4ac07e;
    --err: #ff7a6b;
    --shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 10px 30px rgba(0, 0, 0, .35);
    --tab-lift: 0 1px 2px rgba(0, 0, 0, .45);
  }
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  font-family: var(--font);
  color: var(--text);
  background: var(--bg);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--primary); text-decoration: none; }
a:hover { text-decoration: underline; }

.container { max-width: var(--maxw); margin: 0 auto; padding: 0 20px; }

/* ---------- header / nav ---------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: saturate(140%) blur(8px);
  border-bottom: 1px solid var(--border);
}
.nav {
  display: flex;
  align-items: center;
  gap: 18px;
  height: 62px;
}
.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 700;
  font-size: 1.05rem;
  color: var(--text);
}
.brand:hover { text-decoration: none; }
.brand .logo { font-size: 1.4rem; }
.nav .spacer { flex: 1; }
.nav a.navlink {
  color: var(--muted);
  font-weight: 500;
  font-size: .95rem;
}
.nav a.navlink:hover { color: var(--text); text-decoration: none; }
.nav .btn { padding: 8px 14px; font-size: .92rem; }
.nav-auth { display: inline-flex; align-items: center; gap: 12px; }
.nav-auth:empty { display: none; }

/* mobile nav: wrap links below on small screens */
@media (max-width: 720px) {
  .nav { height: auto; flex-wrap: wrap; padding: 10px 0; gap: 12px; }
  .nav .spacer { flex-basis: 100%; height: 0; }
}

/* ---------- buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 11px 20px;
  border-radius: 999px;
  border: 1px solid transparent;
  font-weight: 600;
  font-size: 1rem;
  cursor: pointer;
  transition: transform .04s ease, background .15s ease, border-color .15s ease;
}
.btn:hover { text-decoration: none; }
.btn:active { transform: translateY(1px); }
.btn-primary { background: var(--primary); color: var(--primary-ink); }
.btn-primary:hover { background: color-mix(in srgb, var(--primary) 88%, black); color: var(--primary-ink); }
.btn-ghost { background: transparent; color: var(--text); border-color: var(--border); }
.btn-ghost:hover { background: var(--surface); }

/* A disabled button must look disabled. Without these it keeps full colour, the hand
   cursor and the press animation, so it reads as live and then silently does nothing —
   which is how the one instruction on screen can be impossible to follow. */
.btn:disabled, .btn[disabled], .btn[aria-disabled="true"] {
  opacity: .45;
  cursor: not-allowed;
  box-shadow: none;
}
.btn:disabled:active, .btn[disabled]:active, .btn[aria-disabled="true"]:active { transform: none; }
.btn-primary:disabled:hover, .btn-primary[disabled]:hover, .btn-primary[aria-disabled="true"]:hover {
  background: var(--primary); color: var(--primary-ink);
}
.btn-ghost:disabled:hover, .btn-ghost[disabled]:hover, .btn-ghost[aria-disabled="true"]:hover { background: transparent; }

/* ---------- hero ---------- */
.hero {
  padding: 72px 0 40px;
  background:
    radial-gradient(1200px 400px at 80% -10%, var(--primary-soft), transparent 70%);
}
.hero h1 {
  font-size: clamp(2rem, 4.5vw, 3.1rem);
  line-height: 1.1;
  margin: 0 0 16px;
  letter-spacing: -0.02em;
}
.hero p.lead {
  font-size: clamp(1.05rem, 2vw, 1.28rem);
  color: var(--muted);
  max-width: 44ch;
  margin: 0 0 28px;
}
.hero .cta { display: flex; gap: 12px; flex-wrap: wrap; }

.hero-grid { display: grid; grid-template-columns: 1.05fr .95fr; gap: 40px; align-items: center; }
.hero-grid > * { min-width: 0; }
@media (max-width: 860px) { .hero-grid { grid-template-columns: 1fr; gap: 28px; } }
.hero-shot { margin: 0; }
.hero-shot img {
  width: 100%; height: auto; display: block;
  border-radius: var(--radius); border: 1px solid var(--border);
  box-shadow: var(--shadow); background: var(--surface-2);
}
.hero-shot figcaption { color: var(--muted); font-size: .82rem; margin-top: 8px; text-align: center; }

/* ---------- sections ---------- */
section { padding: 46px 0; }
section.alt { background: var(--surface); border-block: 1px solid var(--border); }
h2 { font-size: clamp(1.4rem, 3vw, 1.9rem); margin: 0 0 8px; letter-spacing: -0.01em; }
.section-intro { color: var(--muted); max-width: 60ch; margin: 0 0 28px; }

.grid { display: grid; gap: 20px; }
.grid.cols-2 { grid-template-columns: repeat(2, 1fr); }
.grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
.grid.cols-4 { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 900px) { .grid.cols-3, .grid.cols-4 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 620px) { .grid.cols-2, .grid.cols-3, .grid.cols-4 { grid-template-columns: 1fr; } }

.card {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 22px;
  box-shadow: var(--shadow);
}
.card h3 { margin: 0 0 6px; font-size: 1.12rem; }
.card p { margin: 0; color: var(--muted); font-size: .96rem; }
/* Cards were single-paragraph everywhere until the pricing page; without this
   two <p> in one card butt straight together. */
.card p + p { margin-top: 10px; }
.card ul { margin: 8px 0 0; padding-left: 20px; color: var(--muted); font-size: .96rem; }
.card ul li + li { margin-top: 6px; }
.card .ico { font-size: 1.6rem; line-height: 1; margin-bottom: 10px; }
/* A map filling a card edge to edge (the card needs padding:0 to suit). */
.card img.shot { width: 100%; height: auto; display: block; background: var(--surface-2); }

.badge {
  display: inline-block;
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  padding: 3px 9px;
  border-radius: 999px;
  background: var(--primary-soft);
  color: var(--primary);
  border: 1px solid color-mix(in srgb, var(--primary) 25%, transparent);
}
.badge.place { background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent-tint-ink); border-color: color-mix(in srgb, var(--accent) 35%, transparent); }
/* An output that is set up by arrangement rather than by a tick-box (today the
   where-to-board plan). Wherever it is offered it carries this badge, so nobody
   discovers the cost after asking for it. */
.badge.extra {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--accent-tint-ink);
  border-color: color-mix(in srgb, var(--accent) 40%, transparent);
  text-transform: none;
  letter-spacing: 0;
  vertical-align: middle;
}

/* steps */
.steps { counter-reset: step; display: grid; gap: 16px; grid-template-columns: repeat(4, 1fr); }
@media (max-width: 900px) { .steps { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 520px) { .steps { grid-template-columns: 1fr; } }
.step { position: relative; padding: 20px; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); }
.step::before {
  counter-increment: step; content: counter(step);
  display: flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border-radius: 50%;
  background: var(--primary); color: var(--primary-ink); font-weight: 700; font-size: .95rem;
  margin-bottom: 10px;
}
.step h3 { margin: 0 0 4px; font-size: 1.02rem; }
.step p { margin: 0; color: var(--muted); font-size: .92rem; }

/* examples gallery */
.example { padding: 0; overflow: hidden; }
.example .imgs { display: grid; grid-template-columns: 1fr 1fr; gap: 2px; background: var(--border); }
/* One sheet, not a pair: the boarding plan is a single artefact, so its card
   must not leave half the strip empty. */
.example .imgs.one { grid-template-columns: 1fr; }
.example .imgs a { display: block; background: var(--surface-2); }
.example img { width: 100%; height: auto; display: block; }
.example .body { padding: 16px 18px 18px; }
.example .body h3 { margin: 0 0 6px; display: flex; align-items: center; gap: 10px; }
.example .outputs { margin: 8px 0 0; font-size: .85rem; color: var(--muted); }
@media (max-width: 480px) { .example .imgs { grid-template-columns: 1fr; } }

.pill-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }
.pill { font-size: .82rem; padding: 5px 11px; border-radius: 999px; background: var(--surface-2); border: 1px solid var(--border); color: var(--text); }
/* Complexity-triage bands. Colour carries meaning here, so each pill also says
   its own word — GREEN / AMBER / RED — rather than relying on the swatch. */
.pill.green { background: color-mix(in srgb, var(--ok) 14%, transparent); border-color: color-mix(in srgb, var(--ok) 40%, transparent); color: var(--ok); font-weight: 700; }
.pill.amber { background: color-mix(in srgb, var(--accent) 16%, transparent); border-color: color-mix(in srgb, var(--accent) 40%, transparent); color: var(--accent-tint-ink); font-weight: 700; }
.pill.red { background: color-mix(in srgb, var(--err) 12%, transparent); border-color: color-mix(in srgb, var(--err) 40%, transparent); color: var(--err); font-weight: 700; }

/* ---------- forms ---------- */
.form-wrap { max-width: 640px; }
.field { margin-bottom: 16px; }
.field label { display: block; font-weight: 600; font-size: .92rem; margin-bottom: 6px; }
.field .hint { font-weight: 400; color: var(--muted); font-size: .85rem; }
.field input, .field select, .field textarea {
  width: 100%; padding: 11px 13px;
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--bg); color: var(--text);
  font: inherit; font-size: .98rem;
}
.field textarea { min-height: 110px; resize: vertical; }
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none; border-color: var(--primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 22%, transparent);
}
.field.invalid input, .field.invalid select, .field.invalid textarea { border-color: var(--err); }
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.form-note { color: var(--muted); font-size: .9rem; margin: 4px 0 18px; }
.form-msg { margin-top: 16px; padding: 14px 16px; border-radius: 10px; display: none; }
.form-msg.show { display: block; }
.form-msg.ok { background: color-mix(in srgb, var(--ok) 14%, transparent); border: 1px solid var(--ok); color: var(--ok); }
.form-msg.err { background: color-mix(in srgb, var(--err) 12%, transparent); border: 1px solid var(--err); color: var(--err); }

/* ---------- place search (P9 Part B) ---------- */
.search-block { margin-bottom: 22px; }
.search-block .search-label { display: block; font-weight: 600; font-size: .92rem; margin-bottom: 6px; }
.search-form { display: flex; flex-wrap: wrap; gap: 8px; max-width: 480px; }
.search-form .search-label { flex: 1 0 100%; }
.search-form input[type="search"] {
  flex: 1; min-width: 0; padding: 11px 13px;
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--bg); color: var(--text); font: inherit; font-size: .98rem;
}
.search-form input[type="search"]:focus {
  outline: none; border-color: var(--primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 22%, transparent);
}
.hero .search-block { margin-top: 20px; }
.search-meta { color: var(--muted); font-size: .9rem; margin: 14px 0 0; }
.search-hit-reason { color: var(--muted); font-size: .88rem; margin-top: 2px; }
.search-noresult { border: 1px solid var(--border); border-radius: 12px; padding: 20px 22px; background: var(--surface); }
.search-noresult p { margin: 0 0 12px; }
.search-noresult p:last-child { margin-bottom: 0; }

/* ---------- the national directory of local bus maps (buses-data OA-308) ----------
   Somebody ELSE's map, shown below our own results. The panel is deliberately
   quieter than the grid above it and its cards carry no screenshot: they must
   never be mistaken for a map published through this portal, and the visual
   difference does that work before the "Not ours" badge is read. */
.dir-block { margin-top: 30px; padding-top: 22px; border-top: 1px solid var(--border); }
.dir-block h3 { margin: 0 0 6px; font-size: 1.05rem; }
.dir-note { color: var(--muted); font-size: .9rem; margin: 0 0 16px; max-width: 62ch; }
.dir-empty p { color: var(--muted); font-size: .95rem; margin: 0; max-width: 66ch; }
/* OA-312 — the sentence saying WHERE the place is leads the panel in full colour;
   the request sentence, the other places of that name and the Index's edition
   line sit under the cards in the muted voice the note uses. */
.dir-place { margin: 0 0 10px; max-width: 66ch; }
.dir-ask { margin: 16px 0 0; max-width: 66ch; }
.dir-others { color: var(--muted); font-size: .9rem; margin: 10px 0 0; max-width: 66ch; }
.dir-source { font-size: .82rem; margin: 12px 0 0; }
.dir-empty .dir-empty-note, .dir-empty .dir-source { margin-top: 10px; }
.dir-card { background: var(--surface-2); }
.dir-card .body { padding: 16px 18px; }
.dir-card h3 { margin: 0 0 8px; font-size: 1rem; }
.dir-card p { margin: 0 0 8px; }
.dir-card .outputs { margin: 10px 0 0; font-size: .82rem; color: var(--muted); }
.dir-action { margin: 10px 0 0; font-weight: 600; }
.badge.notours {
  background: color-mix(in srgb, var(--muted) 14%, transparent);
  color: var(--muted);
  border: 1px solid color-mix(in srgb, var(--muted) 35%, transparent);
}

/* OA-308 tier 3 — the letter a reader sends when nobody publishes a map.
   Closed by default: six paragraphs opened inside every card would push the
   second and third directory rows off the screen, and the row itself is the
   answer for most readers. `<pre>` wraps rather than scrolling, because this
   text exists to be READ and retyped as much as to be copied — a reader with no
   JavaScript has no copy button and must be able to see all of it. */
.dir-ask { margin: 14px 0 0; border-top: 1px dashed var(--border); padding-top: 10px; }
.dir-ask summary { cursor: pointer; font-weight: 600; font-size: .92rem; }
.dir-ask-note { color: var(--muted); font-size: .86rem; margin: 10px 0; max-width: 70ch; }
.dir-ask-extra { margin-top: 16px; }
.dir-letter {
  white-space: pre-wrap;
  word-break: break-word;
  font: .84rem/1.55 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px 14px;
  margin: 0 0 10px;
}
.dir-ask-actions { margin: 0 0 4px; }

/* ---------- faq ---------- */
.faq details { border: 1px solid var(--border); border-radius: 12px; padding: 4px 18px; margin-bottom: 12px; background: var(--bg); }
.faq summary { cursor: pointer; font-weight: 600; padding: 14px 0; list-style: none; }
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after { content: "+"; float: right; color: var(--muted); font-weight: 400; font-size: 1.3rem; line-height: 1; }
.faq details[open] summary::after { content: "\2212"; }
.faq details p { margin: 0 0 14px; color: var(--muted); }

/* ---------- footer ---------- */
.site-footer {
  border-top: 1px solid var(--border);
  background: var(--surface);
  padding: 34px 0;
  margin-top: 20px;
  color: var(--muted);
  font-size: .9rem;
}
.site-footer .cols { display: flex; flex-wrap: wrap; gap: 24px 48px; justify-content: space-between; }
.site-footer a { color: var(--text); }
.site-footer .attrib { max-width: 52ch; }
.site-footer .muted { color: var(--muted); }

/* ---------- public map pages (P6) ---------- */
.crumb { margin: 0 0 10px; font-size: .9rem; }

/* an organisation's badge + credit line */
.org-line { display: flex; align-items: center; gap: 10px; font-size: .92rem; color: var(--muted); margin-top: 10px; }
.org-line.big { font-size: 1rem; margin: 0 0 6px; }
.org-line h2 { margin: 0; }
.org-badge {
  --org-accent: var(--primary);
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; flex: none; border-radius: 50%;
  background: color-mix(in srgb, var(--org-accent) 18%, transparent);
  border: 1px solid color-mix(in srgb, var(--org-accent) 45%, transparent);
  /* Tint-on-tint: the badge sits on a 18% wash of its OWN accent, so the accent
     itself is not a legible ink on it -- the same trap --accent-tint-ink exists to
     solve for the amber badges. A fixed token cannot serve here because the accent
     is per-organisation, so darken whatever it is. At 80% every hue in ACCENTS
     (src/branding/index.js) clears WCAG AA with headroom; teal and amber were at
     3.8:1 undarkened. scripts/test-contrast.mjs holds the whole palette to 4.5:1. */
  color: color-mix(in srgb, var(--org-accent) 80%, black); font-weight: 700; font-size: .82rem; line-height: 1;
}
.org-badge.lg { width: 46px; height: 46px; font-size: 1.15rem; }
/* The accent palette is picked for light backgrounds; lift it in dark mode so a
   deep green/red badge still reads against the dark surface. */
@media (prefers-color-scheme: dark) {
  .org-badge {
    color: color-mix(in srgb, var(--org-accent) 40%, white);
    border-color: color-mix(in srgb, var(--org-accent) 55%, white);
    background: color-mix(in srgb, var(--org-accent) 30%, transparent);
  }
}

/* gallery cards (one map, one shot) */
.pub-card .shot { display: block; background: var(--surface-2); border-bottom: 1px solid var(--border); }
.pub-card .shot img { width: 100%; height: auto; display: block; }
.pub-card .shot-none { display: block; padding: 60px 0; text-align: center; color: var(--muted); }
.pub-card .body h3 a { color: var(--text); }

/* the sheet viewer */
.sheet-box { margin: 18px 0 26px; }
.tabs { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
/* These tabs are the only control on a map page that changes what you are
   looking at, and until 2026-08-27 an unchosen one was a --muted word inside a
   hairline --border outline on the page's own white: Peter's report was that it
   was not obvious they were buttons at all. Every unchosen tab is now a raised,
   filled pill in full --text, with a hover that turns it the link colour and a
   press that moves it; the chosen one is SOLID --primary rather than the pale
   wash it was, so "which one am I looking at" is answered by fill and not by
   tint. Both states clear AA on --bg and --surface, light and dark. */
.tab {
  font: inherit; font-size: .92rem; font-weight: 600; cursor: pointer;
  padding: 8px 15px; border-radius: 999px;
  background: var(--surface); color: var(--text);
  border: 1px solid color-mix(in srgb, var(--muted) 40%, var(--border));
  box-shadow: var(--tab-lift);
  transition: background .12s ease, border-color .12s ease, color .12s ease, transform .08s ease;
}
.tab:hover { background: var(--surface-2); color: var(--primary); border-color: color-mix(in srgb, var(--primary) 55%, transparent); }
.tab:active { transform: translateY(1px); box-shadow: none; }
.tab:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.tab.on, .tab.on:hover {
  background: var(--primary); color: var(--primary-ink);
  border-color: var(--primary); box-shadow: none;
}
@media (prefers-reduced-motion: reduce) { .tab { transition: none; } }
/* The tab row's own sentence: what the chosen sheet shows. Sits between the tabs
   and the sheet, because a description that arrives after the artwork it
   describes is not a description. */
.tab-hint { margin: -4px 0 12px; color: var(--muted); font-size: .93rem; max-width: 72ch; }
.tab-hint strong { color: var(--text); }
.sheet {
  display: block; border: 1px solid var(--border); border-radius: var(--radius);
  overflow: hidden; box-shadow: var(--shadow); background: var(--surface-2);
}
.sheet img { width: 100%; height: auto; display: block; }
.dl-row { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 14px; }
.btn-sm { padding: 8px 15px; font-size: .92rem; }
.map-page ul { color: var(--muted); }

.center { text-align: center; }
.mt-0 { margin-top: 0; }
.lead-cta { margin-top: 26px; display: flex; gap: 12px; flex-wrap: wrap; }

/* Seeded demo organisation, not a real customer. NOT part of the pilot chrome —
   demo data stays demo data after the pilot ends. */
.badge.sample {
  background: color-mix(in srgb, var(--err) 14%, transparent);
  color: var(--err);
  border: 1px solid color-mix(in srgb, var(--err) 45%, transparent);
}
.sample-note {
  margin: 12px 0 0;
  padding: 10px 13px;
  border-radius: 10px;
  font-size: .92rem;
  background: color-mix(in srgb, var(--err) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--err) 32%, transparent);
}
.banner-note {
  margin: 14px 0;
  padding: 10px 14px;
  border-radius: 10px;
  font-size: .95rem;
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
}

/* ---------- PILOT: pilot banner ----------
   Injected into every page by /js/site-banner.js. Delete this block and the
   route that serves that script to remove the pilot framing (docs/PILOT.md).
   Loaded by the app pages too — they link /css/styles.css before /app/app.css. */
.pilot-banner {
  background: color-mix(in srgb, var(--accent) 16%, var(--bg));
  border-bottom: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  color: var(--text);
  font-size: .88rem;
  line-height: 1.45;
  padding: 9px 0;
}
.pilot-banner-inner {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
}
.pilot-badge {
  flex: none;
  font-size: .7rem;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  padding: 3px 9px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-ink);
}
.pilot-text { flex: 1 1 320px; min-width: 0; }
.pilot-link { flex: none; font-weight: 600; white-space: nowrap; text-decoration: underline; }

@media (max-width: 720px) {
  .pilot-more { display: none; } /* the link goes to the same words */
}

@media print { .pilot-banner { display: none; } }

/* ---------- local/dev instance banner ----------
   Injected by /js/site-banner.js whenever NODE_ENV isn't "production" — see
   ENVIRONMENT in src/config.js. Deliberately NOT var(--accent): it must stay
   visually distinct from the pilot banner even where both are shown, and
   must not fade if the brand accent colour ever changes. */
.local-banner {
  background: #b91c1c;
  border-bottom: 1px solid #7f1d1d;
  color: #fff;
  font-size: .88rem;
  line-height: 1.45;
  padding: 9px 0;
}
.local-banner-inner {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
}
.local-badge {
  flex: none;
  font-size: .7rem;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  padding: 3px 9px;
  border-radius: 999px;
  background: #fff;
  color: #b91c1c;
}
.local-text { flex: 1 1 320px; min-width: 0; }

@media print { .local-banner { display: none; } }

/* ---------- "What's new" — public/js/whats-new.js ---------- */
.whats-new-item { padding: 14px 0; border-bottom: 1px solid var(--border); }
.whats-new-item:last-child { border-bottom: none; }
.whats-new-date { font-size: .8rem; text-transform: uppercase; letter-spacing: .04em; }
.whats-new-title { font-weight: 700; margin-top: 2px; }
.whats-new-body { margin: 4px 0 0; }

/* ---------- P8a: the public sheet viewer ---------- */
/* The sheet is A4 landscape; the stage keeps that shape (capped so it can never
   eat a whole laptop screen) and the map is transformed inside it. */
.viewer { margin: 0; }
.viewer-bar { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; flex-wrap: wrap; }
.viewer-btn {
  font: inherit; font-weight: 700; line-height: 1; cursor: pointer;
  min-width: 40px; min-height: 40px; padding: 0 12px; border-radius: 10px;
  background: var(--bg); color: var(--text); border: 1px solid var(--border);
}
.viewer-btn:hover:not(:disabled) { background: var(--surface); }
.viewer-btn:disabled { opacity: .45; cursor: default; }
.viewer-btn-wide { font-weight: 600; font-size: .92rem; }
.viewer-hint { color: var(--muted); font-size: .85rem; }
.viewer-stage {
  position: relative; overflow: hidden;
  /* A4 landscape, capped so a tall window does not hand over a stage taller than
     the screen. The cap can leave the stage narrower than its column, so centre it. */
  aspect-ratio: 297 / 210; max-height: 78vh; margin-inline: auto;
  border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow); background: var(--surface-2);
  touch-action: none; cursor: default;
}
.viewer-stage:focus-visible { outline: 3px solid var(--primary); outline-offset: 2px; }
.viewer-stage.zoomed { cursor: grab; }
.viewer-stage.zoomed:active { cursor: grabbing; }
.viewer-canvas { width: 100%; height: 100%; transform-origin: 0 0; }
.viewer-canvas > svg, .viewer-canvas > img {
  display: block; width: 100%; height: 100%; object-fit: contain; user-select: none;
}
/* The sheet is drawn for white paper; it keeps its own white ground in dark mode
   rather than being inverted into something the printed version never looks like. */
.viewer-canvas { background: #fff; }
.viewer-live { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
.sheet-source { color: var(--muted); font-size: .82rem; margin: 12px 0 0; }
.alt-card { margin: 8px 0 26px; }

/* ---------- P8a: notices ---------- */
.notice {
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); padding: 14px 16px; margin: 0 0 18px; font-size: .95rem;
}
.notice-warn {
  background: color-mix(in srgb, var(--accent) 12%, var(--bg));
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
}

/* ---------- P8a: the services (text alternative) page ---------- */
.route-jump { display: flex; flex-wrap: wrap; gap: 8px; margin: 6px 0 22px; }
.route-jump .tab { text-decoration: none; }
.route-card {
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg); padding: 18px 20px; margin: 0 0 16px; box-shadow: var(--shadow);
}
.route-card h3 { margin: 0 0 4px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.route-card h4 { margin: 16px 0 6px; font-size: .95rem; }
.route-card > p.muted { margin: 0; color: var(--muted); font-size: .92rem; }
.route-badge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 2.4em; padding: 3px 9px; border-radius: 8px;
  font-size: .9rem; font-weight: 700; border: 1px solid rgba(0, 0, 0, .18);
}
.route-badge.plain { background: var(--surface-2); color: var(--text); border-color: var(--border); }
.stop-list { margin: 0; padding-left: 1.3em; color: var(--text); }
.stop-list.wrap {
  list-style: none; padding: 0; display: flex; flex-wrap: wrap; gap: 6px 0;
}
.stop-list.wrap li { color: var(--muted); }
.stop-list.wrap li:not(:last-child)::after { content: ' → '; color: var(--border); padding: 0 6px; }
.tag {
  font-size: .72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  padding: 2px 7px; border-radius: 999px; background: var(--surface-2); color: var(--muted);
  border: 1px solid var(--border);
}
.small { font-size: .85rem; }

@media (max-width: 620px) {
  .viewer-stage { max-height: 60vh; }
  .viewer-hint { width: 100%; }
}

/* The nav link for the page you are on (nav-current.js sets aria-current).
   Two signals, and neither of them is colour on its own: the link goes to full
   text colour AND gains a rule beneath it. The rule is an absolutely positioned
   ::after rather than a border, padding or font-weight change, so marking a link
   current cannot alter its box and nudge the rest of the header sideways. */
.nav a.navlink[aria-current="page"] { color: var(--text); position: relative; }
.nav a.navlink[aria-current="page"]::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -7px;
  height: 2px;
  border-radius: 1px;
  background: var(--primary);
}
/* The "Apply to join" button is already solid, so a coloured rule would vanish
   into it — it gets a ring in the text colour instead. */
.nav a.btn[aria-current="page"] { outline: 2px solid var(--text); outline-offset: 2px; }

/* --- The boarding index on a map's services page (OA-010) -------------------
   A "Where to board" sheet's text equivalent is a TABLE — destination, the stop
   you stand at, the walk, the services — and it is the sheet's own accessibility
   argument, so it is styled as a real table rather than squeezed into the list
   markup the rest of this page uses. The table is the one wide thing on the
   page, so it scrolls inside its own box: the page body must never scroll
   sideways on a phone. */
.table-scroll { overflow-x: auto; margin: 8px 0 0; }
.board-table { border-collapse: collapse; width: 100%; font-size: .95rem; }
.board-table th, .board-table td { text-align: left; vertical-align: top; padding: 7px 12px 7px 0; border-bottom: 1px solid var(--border); }
.board-table thead th { color: var(--muted); font-size: .82rem; font-weight: 600; text-transform: uppercase; letter-spacing: .03em; white-space: nowrap; }
.board-table tbody th { font-weight: 600; }
.board-table td { color: var(--muted); white-space: nowrap; }
.board-table td:last-child { white-space: normal; }
/* The second stand a destination can also be caught from. It sits inside the
   row's <th>, so it has to undo the header's weight explicitly — and it is not
   `.muted`, because there is no bare `.muted` rule in this sheet: the grey
   elsewhere on this page comes from `.card p` and `.card ul`, and a <div> in a
   table cell is neither. */
.board-table th .board-also { display: block; font-weight: 400; color: var(--muted); font-size: .85rem; margin-top: 2px; }
.stand-list { margin: 8px 0 0; padding-left: 20px; color: var(--muted); font-size: .96rem; }
.stand-list li { margin: 0 0 4px; }
/* Available to a screen reader, invisible on the page — the table's <caption>.
   Not `display:none`, which would take it out of the accessibility tree too. */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%);
  overflow: hidden;
  white-space: nowrap;
}
