/* Static pages (About / Examples / Pricing / Terms / Privacy, and the two
   /pro pages). Deliberately standalone — no bundle, no App.css: these must
   render even if the app never boots, and crawlers should get plain HTML.
   /tokens.css is linked first on every page and holds the brand primitives
   (and the wordmark @font-face) the app shares; everything below is page
   semantics built out of them.

   Two registers share this sheet:
   · document (Terms, Privacy) — one measure, generous leading, a contents
     list, no decoration. Body class `doc`.
   · product (About, Examples, Pricing, /pro/*) — the same type, plus live
     embeds of Voyarc itself and the pricing columns. Body class `product`. */

:root {
  color-scheme: light dark;
  --paper: var(--voyarc-paper, #ffffff);
  --ink: var(--voyarc-ink, #2c2c2c);
  --muted: var(--voyarc-muted-light, #5c6270);
  --rule: rgba(44, 44, 44, 0.14);
  --accent: var(--voyarc-pink, #d6336c);
  /* Plates: contents lists, code blocks, the frame an embed sits in. */
  --surface: #f4f5f8;
  --surface-line: rgba(44, 44, 44, 0.1);
  /* The reading measure, 65–70ch: one number, so both registers agree. */
  --measure: 68ch;
  /* The route log's four changes, each a text/background pair for the chip
     and a lit line for the map. The map colours are read on Midnight only,
     so they do not turn over in dark mode; the chips sit on paper and do. */
  --chip-new: #1f7a4d;
  --chip-new-bg: #e3f4ea;
  --chip-back: #1c5fa8;
  --chip-back-bg: #e2edf9;
  --chip-more: #6a4fb3;
  --chip-more-bg: #ece7f8;
  --chip-cut: #b3261e;
  --chip-cut-bg: #fbe5e3;
  --map-new: #4cd48a;
  --map-back: #6fb3ff;
  --map-more: #c9a8ff;
  --map-cut: #ff6b61;
  /* The ground those maps are drawn on: the app's own Dark look, sea, earth
     and borders. Read on the map plate only, so they do not turn over in dark
     mode either. Every generated page sets these in its head straight from
     HOUSE_COLORS.dark (src/lib/houseStyle.ts); these are what anything else
     using the sheet falls back to. */
  --log-water: #0d1119;
  --log-land: #1f2632;
  --log-border: #303a48;
}

@media (prefers-color-scheme: dark) {
  :root {
    --paper: var(--voyarc-midnight, #060913);
    --ink: #e9eaf0;
    --muted: var(--voyarc-muted-dark, #a6abbc);
    --rule: rgba(255, 255, 255, 0.14);
    --accent: var(--voyarc-pink-lift, #f06595);
    --surface: #0d1120;
    --surface-line: rgba(255, 255, 255, 0.09);
    --chip-new: #7fd6a6;
    --chip-new-bg: #143424;
    --chip-back: #8fbdf2;
    --chip-back-bg: #142a45;
    --chip-more: #c3b2f2;
    --chip-more-bg: #2a2246;
    --chip-cut: #f29a94;
    --chip-cut-bg: #46201d;
  }
}

* {
  box-sizing: border-box;
}

/* Several controls here ship hidden and are unhidden by script (the billing
   toggle, "Manage plan", the staging restore link). An author
   `display` rule outranks the UA sheet's [hidden] whatever its specificity,
   so the sheet has to say it itself — otherwise a page without JavaScript
   shows every one of them, dead. */
[hidden] {
  display: none !important;
}

/* Said to a screen reader and to nothing else — the comparison table's
   caption, which the columns already say in type. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Chrome on macOS can leave the page stuck rubber-banded above its top after
   a hard trackpad flick (blank band above the header, nav unclickable until
   reload). The doc pages have nothing to pull down to, so refuse to overscroll.
   Vertical only: a `pre` block or the plan table still scrolls sideways. */
html {
  overscroll-behavior-y: none;
}

body {
  margin: 0;
  padding: 28px 20px 64px;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-ui, system-ui, sans-serif);
  /* ~17px: these are pages you read, not a panel you work in. */
  font-size: 1.0625rem;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
}

/* One width for every page: the route log's, which is also the width Examples
   needs for its 16:9 embeds. Header, nav and footer line up across the set. */
.wrap {
  max-width: 62rem;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 28px;
}

/* The container is that width everywhere, so a block that is meant to be read
   — routes' title and lede, examples' and pricing's copy — says so and keeps
   the reading measure. */
.wrap .prose {
  max-width: var(--measure);
}

/* ---------- shared header / footer ---------- */

.page-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 6px 20px;
  padding-bottom: 18px;
  border-bottom: 1px solid var(--rule);
}

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font: 700 1.05rem/1.2 var(--font-brand, system-ui, sans-serif);
  letter-spacing: -0.01em;
  color: var(--ink);
  text-decoration: none;
}

.wordmark svg {
  color: var(--accent);
}

.head-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 2px 16px;
  font-size: 0.9rem;
}

.head-nav a {
  color: var(--muted);
  text-decoration: none;
}

.head-nav a:hover {
  color: var(--accent);
  text-decoration: underline;
}

.head-nav a[aria-current='page'] {
  color: var(--ink);
  font-weight: 600;
}

.page-foot {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding-top: 18px;
  border-top: 1px solid var(--rule);
  font-size: 0.85rem;
  line-height: 1.6;
  color: var(--muted);
}

.page-foot p {
  margin: 0;
  text-wrap: pretty;
}

.foot-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 2px 16px;
}

/* The two accounts that exist, icon-only like the app panel: the muted
   footer grey at rest, the accent on hover, glyphs the size of the text. */
.foot-social {
  display: flex;
  align-items: center;
  gap: 14px;
}

.foot-social a {
  display: inline-flex;
  align-items: center;
  color: var(--muted);
  transition: color 0.15s;
}

.foot-social a:hover {
  color: var(--accent);
}

.foot-social svg {
  width: 14px;
  height: 14px;
  flex: none;
}

/* ---------- back to top ---------- */

/* The way back up a long page, floating clear of the measure. It ships
   `hidden` and the page's own script unhides it, so a reader without
   JavaScript never gets a button that cannot scroll; past the first screen
   the script adds `show` and it fades in. Above the pricing table's sticky
   row heads (z-index 1) and nothing else on these pages. */
.to-top {
  position: fixed;
  right: 18px;
  bottom: 18px;
  z-index: 5;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: 1px solid var(--surface-line);
  border-radius: 50%;
  background: var(--paper);
  color: var(--ink);
  box-shadow: 0 4px 16px rgba(6, 9, 19, 0.18);
  /* Out of the tab order and out of the accessibility tree until it is worth
     reaching: `visibility` cannot tween, so it is held to the end of the fade
     on the way out and dropped at once on the way in. */
  visibility: hidden;
  opacity: 0;
  transform: translateY(8px);
  pointer-events: none;
  cursor: pointer;
  transition: opacity 0.18s ease, transform 0.18s ease,
    visibility 0s linear 0.18s;
}

.to-top.show {
  visibility: visible;
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
  transition-delay: 0s;
}

.to-top:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.to-top:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .to-top {
    transition: none;
  }
}

/* A phone has less room beside the text, and a thumb reaches the corner. */
@media (max-width: 760px) {
  .to-top {
    right: 14px;
    bottom: 14px;
  }
}

/* ---------- text ---------- */

/* Block layout, not flex: adjacent heading/paragraph margins should collapse
   rather than stack into airy gaps between sections. */
main {
  display: block;
}

h1 {
  margin: 0 0 6px;
  font: 700 clamp(1.75rem, 1.3rem + 2vw, 2.3rem) / 1.15 var(--font-brand, system-ui, sans-serif);
  letter-spacing: -0.02em;
  text-wrap: balance;
}

h2 {
  margin: 34px 0 8px;
  font: 700 1.15rem/1.3 var(--font-brand, system-ui, sans-serif);
  letter-spacing: -0.01em;
  text-wrap: balance;
}

h3 {
  margin: 22px 0 6px;
  font: 700 0.98rem/1.35 var(--font-brand, system-ui, sans-serif);
}

p,
ul,
ol {
  margin: 0 0 16px;
}

ul,
ol {
  padding-left: 22px;
}

li {
  margin-bottom: 6px;
}

.lede {
  font-size: 1.15rem;
  line-height: 1.6;
  color: var(--muted);
  text-wrap: pretty;
}

.meta {
  font-size: 0.85rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

a {
  color: var(--accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}

code {
  padding: 1px 5px;
  border-radius: 5px;
  background: color-mix(in srgb, var(--ink) 8%, transparent);
  font-family: var(--font-mono, monospace);
  font-size: 0.9em;
  white-space: nowrap;
}

/* File samples (the FAQ's format previews). A CSV row that wraps stops being
   a CSV row, so these scroll sideways inside their own box rather than
   pushing the page wider than the phone it is read on. */
pre {
  margin: 0 0 18px;
  padding: 12px 14px;
  overflow-x: auto;
  border: 1px solid var(--surface-line);
  border-radius: 10px;
  background: var(--surface);
  font-family: var(--font-mono, monospace);
  font-size: 0.82rem;
  line-height: 1.55;
}

/* ---------- document register (Terms, Privacy) ---------- */

.doc main {
  counter-reset: section;
}

.doc .lede {
  margin-bottom: 26px;
}

/* Numbers come from a counter, not the copy: the legal wording is untouched
   and the contents list below can't drift out of step with it. */
.doc main > h2 {
  counter-increment: section;
  scroll-margin-top: 18px;
}

.doc main > h2::before {
  content: counter(section) '. ';
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.toc {
  margin: 0 0 6px;
  padding: 16px 20px 6px;
  border: 1px solid var(--surface-line);
  border-radius: 10px;
  background: var(--surface);
}

.toc h2 {
  margin: 0 0 10px;
  font: 700 0.75rem/1.3 var(--font-brand, system-ui, sans-serif);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted);
}

.toc ol {
  margin: 0 0 10px;
  padding-left: 22px;
  columns: 2;
  column-gap: 28px;
  font-size: 0.94rem;
  line-height: 1.55;
  font-variant-numeric: tabular-nums;
}

.toc li {
  margin-bottom: 4px;
  break-inside: avoid;
}

.toc a {
  color: var(--ink);
  text-decoration-color: var(--rule);
}

.toc a:hover {
  color: var(--accent);
}

@media (max-width: 640px) {
  .toc ol {
    columns: 1;
  }
}

/* ---------- product register (About, Examples) ---------- */

.example {
  margin: 0 0 52px;
  padding-top: 8px;
}

.example > h2 {
  margin-top: 0;
  font-size: 1.35rem;
}

/* The hero: Black Marble on the globe, full width of the wide wrap, on its
   own dark plate whichever way the OS is leaning. */
.example.hero {
  margin-bottom: 60px;
  padding: 22px 22px 6px;
  /* A hairline, because in dark mode the plate and the page are the same
     midnight and the hero would otherwise have no edge at all. */
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 14px;
  background: var(--voyarc-midnight, #060913);
  color: #e9eaf0;
}

.example.hero h2,
.example.hero .story {
  color: #e9eaf0;
}

.example.hero .numbers,
.example.hero .caveat {
  color: #a6abbc;
}

.example.hero a {
  color: var(--voyarc-pink-lift, #f06595);
}

.example.hero .route-code code {
  background: rgba(255, 255, 255, 0.08);
  color: #e9eaf0;
}

.example.hero .copy {
  border-color: rgba(255, 255, 255, 0.18);
  color: #e9eaf0;
}

/* The numbers sentence: figures line up, so tabular. */
.numbers {
  margin: 0 0 14px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  text-wrap: pretty;
}

/* Route text is monospace (typography doctrine) and must wrap: a 24-route
   fan-out is 200 characters, and nothing on these pages scrolls sideways. */
.route-code {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 8px;
  margin: 0 0 16px;
}

.route-code code {
  /* Shrink-to-fit until the text is long enough to need the width — a
     two-route example shouldn't sit in a 900px box. */
  flex: 0 1 auto;
  max-width: 100%;
  min-width: 0;
  padding: 8px 10px;
  border-radius: 8px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font-size: 0.86rem;
  line-height: 1.5;
}

.copy {
  flex: 0 0 auto;
  padding: 7px 12px;
  border: 1px solid var(--rule);
  border-radius: 8px;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 0.82rem;
  cursor: pointer;
}

.copy:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* 16:9, sized by the box rather than the content, so the page doesn't reflow
   when a section swaps its poster for the live map. */
.embed {
  position: relative;
  aspect-ratio: 16 / 9;
  margin: 0 0 14px;
  border: 1px solid var(--surface-line);
  border-radius: 10px;
  overflow: hidden;
  background: var(--surface);
}

.embed iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

/* The still each frame wears until the section is scrolled to — a capture of
   the very map the iframe boots (npm run build:examples -- --posters), so the
   swap lands on the picture that was already there. Without JS it stays, and
   it is a link to the same map in Voyarc. */
.poster {
  position: absolute;
  inset: 0;
  display: block;
  color: #fff;
}

.poster img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Said before it is live: this map moves. */
.poster-play {
  position: absolute;
  top: 50%;
  left: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 54px;
  height: 54px;
  /* The triangle's mass sits left of its box; nudge it back to centre. */
  padding-left: 4px;
  border-radius: 50%;
  background: rgba(12, 14, 22, 0.55);
  box-shadow: 0 2px 14px rgba(0, 0, 0, 0.3);
  /* Its own colour, not the link's: the hero's pink is for text. */
  color: #fff;
  transform: translate(-50%, -50%);
}

.poster:hover .poster-play {
  background: var(--accent);
}

.embed.live .poster {
  display: none;
}

/* The comparison under the Examples hero. Every embed on our own page is the
   licensed one (our origin is always licensed to itself), so the free result
   has to be shown beside it rather than described — two 16:9 frames while
   there is room for them, stacked on a phone. */
.compare-embeds {
  margin: 0 0 52px;
}

.compare-embeds > h2 {
  margin-top: 0;
  font-size: 1.35rem;
}

.compare {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
  margin: 0 0 12px;
}

.compare figure {
  /* min-width: a grid track otherwise takes its floor from the iframe. */
  min-width: 0;
  margin: 0;
}

.compare .embed {
  margin: 0;
}

.compare figcaption {
  margin-top: 8px;
  color: var(--muted);
  font-size: 0.9rem;
  text-wrap: pretty;
}

@media (max-width: 820px) {
  .compare {
    grid-template-columns: minmax(0, 1fr);
  }
}

.example .story {
  margin: 0 0 12px;
  text-wrap: pretty;
}

/* The Race section reads like an example but cannot be one: no URL carries a
   flown track, so it is a still and its copy, never a live embed. Its own
   class keeps it out of every ".example" rule and assertion. */
.race {
  margin: 0 0 52px;
  padding-top: 8px;
}

.race > h2 {
  margin-top: 0;
  font-size: 1.35rem;
}

.race .story {
  margin: 0 0 12px;
  text-wrap: pretty;
}

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

.open-in-app {
  display: inline-block;
  margin: 0 0 14px;
  font-weight: 600;
  text-decoration: none;
}

.open-in-app:hover {
  text-decoration: underline;
}

figure {
  margin: 0 0 22px;
}

figcaption {
  margin-top: 8px;
  font-size: 0.9rem;
  color: var(--muted);
  text-wrap: pretty;
}

/* ---------- pricing (product register) ---------- */

/* One comparison table, four columns: the plans are read across, so they are
   one table rather than four lists that have to be kept in step. It never
   makes the page scroll sideways — the wrapper scrolls instead, and the
   feature column stays put while it does. */
.plan-matrix-wrap {
  margin-bottom: 20px;
  overflow-x: auto;
  /* Room for the sticky column's shadow, and for a focus ring on a CTA. */
  padding-bottom: 4px;
}

.plan-matrix {
  width: 100%;
  /* Separate, not collapsed: a collapsed table drops border-radius, and the
     column heads are plates like the cards they replace. */
  border-collapse: separate;
  border-spacing: 0;
  /* Fixed, so the four plans are four columns of exactly the same width and
     the labels take what is left: the heads are compared across, and columns
     sized by their own copy would put the prices at four widths. */
  table-layout: fixed;
  /* Below this the columns would squeeze the blurbs into slivers; the wrapper
     scrolls instead. */
  min-width: 48rem;
  text-align: left;
}

/* The column heads: what the plan is, what it costs, its button. The 1px
   height is the old table trick, not a size: a cell never shrinks below its
   content, but naming a height at all is what lets the grid inside it ask
   for 100% of the row (below, `.plan-head`). */
.plan-matrix th.plan {
  width: 18.5%;
  height: 1px;
  padding: 18px 16px 20px;
  border: 1px solid var(--surface-line);
  border-radius: 14px 14px 0 0;
  background: var(--surface);
  vertical-align: top;
  font-weight: 400;
}

/* One head, in slots: name, pitch, price, the line under it, button. The
   pitch row is the only flexible one, so it swallows every difference in
   blurb length (and the "Pro is active until…" line the script fills in)
   while the price and the button stay level across the four columns. A
   percentage height in a table cell resolves against the row, which is what
   lets the grid fill the head; where it does not, the head simply stacks. */
.plan-head {
  display: grid;
  /* One column, allowed to be narrower than its widest child: without the
     minmax the billing toggle's own minimum would push Pro's head wider than
     its cell on a phone. */
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto 1fr;
  gap: 8px;
  height: 100%;
}

/* Pro is the recommendation, so it is the one with an edge. */
.plan-matrix th.plan.featured,
.plan-matrix td.featured {
  background: color-mix(in srgb, var(--accent) 7%, var(--surface));
}

.plan-matrix th.plan.featured {
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
}

.plan h2 {
  margin: 0;
  font-size: 1.05rem;
}

/* The labels take what they need at this width and no more — half of the
   fifteen wrap to two lines either way — so the four plans get the rest,
   in four equal columns. */
.plan-matrix .corner {
  width: 26%;
}

/* The empty corner and the feature labels stay legible while the table is
   scrolled sideways. */
.plan-matrix .corner,
.plan-matrix tbody th {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--paper);
}

.plan-matrix tbody th {
  padding: 12px 14px 12px 0;
  border-top: 1px solid var(--rule);
  font-size: 0.92rem;
  font-weight: 500;
  text-wrap: pretty;
}

.plan-matrix tbody td {
  padding: 12px 16px;
  border-top: 1px solid var(--rule);
  vertical-align: middle;
}

/* In or out, then the qualifier: "1080p", "first 25 legs". */
.mark {
  display: block;
}

.mark.yes {
  color: var(--accent);
}

.mark.no {
  color: var(--muted);
  opacity: 0.65;
}

.cell-note {
  display: block;
  margin-top: 2px;
  color: var(--muted);
  font-size: 0.78rem;
  line-height: 1.3;
}

/* Every column has one, in dollars or in words ("Per domain"). */
.price {
  margin: 4px 0 0;
  font: 700 1.5rem/1.2 var(--font-brand, system-ui, sans-serif);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}

/* The line under the price — how it is billed, or what the price covers.
   Three lines' worth of room whether it needs them or not, so the prices
   above it stay level: Pro's line names the period it is not showing ("or
   US$39 a year — US$3.25 a month, save 46%"), and that runs to three lines
   in a column this width anywhere from 360 to 1920. */
.billed,
.price-note {
  min-height: 4.05em;
  margin: 0;
  font-size: 0.82rem;
  line-height: 1.35;
  color: var(--muted);
}

/* The period Pro is not showing, offered where the reader is already looking:
   a link in the run of the line that clicks the toggle above for them. It has
   to read as text — the type is inherited, so the two states of `.billed` are
   the same three lines and the prices stay level.

   A button is an atomic inline box whatever its `display` (Blink forces
   inline-block), so it cannot break across lines like the words around it:
   left at 100% it would never fit after "or" and would drop a line, making
   this note four deep. Held a little narrower than its slot it sits on the
   first line and wraps inside itself instead, and `vertical-align: top` keeps
   that first line's top where the text's would be. */
.switch-plan {
  display: inline;
  max-width: calc(100% - 1.5em);
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  font: inherit;
  text-align: left;
  vertical-align: top;
  cursor: pointer;
}

.switch-plan:hover {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}

.switch-plan:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-radius: 4px;
  text-decoration: underline;
}

/* Disabled with the toggle once this browser has Pro: there is nothing left
   to switch to, so the offer stops looking like something to click. */
.switch-plan:disabled {
  color: inherit;
  cursor: default;
  text-decoration: none;
}

.plan-state {
  margin: 8px 0 0;
  font-size: 0.82rem;
  color: var(--ink);
  font-weight: 600;
}

.plan .blurb {
  margin: 0;
  font-size: 0.94rem;
  line-height: 1.45;
  text-wrap: pretty;
}

/* Buttons only work with JavaScript (they call /api/checkout), so they ship
   as links to the restore page and are upgraded in place — a reader without
   it still lands somewhere useful. */
.cta {
  display: inline-block;
  margin-top: 14px;
  padding: 10px 14px;
  border: 1px solid var(--accent);
  border-radius: 10px;
  background: transparent;
  color: var(--accent);
  font: 600 0.95rem/1.3 var(--font-ui, system-ui, sans-serif);
  text-align: center;
  text-decoration: none;
  cursor: pointer;
}

/* One button per column, the width of the column it belongs to and the last
   row of its grid — the copy is short enough to stay on one line. */
.plan-matrix th.plan .cta {
  display: block;
  width: 100%;
  margin-top: 2px;
  padding: 10px 8px;
}

.cta.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--voyarc-paper, #fff);
}

.cta:hover {
  filter: brightness(1.06);
}

.cta.manage {
  margin-top: 8px;
}

.plan-toggle {
  display: flex;
  gap: 4px;
  margin: 0;
  padding: 3px;
  border: 1px solid var(--surface-line);
  border-radius: 10px;
}

.plan-toggle button {
  flex: 1;
  padding: 5px 8px;
  border: none;
  border-radius: 7px;
  background: transparent;
  color: var(--muted);
  font: 600 0.8rem/1.3 var(--font-ui, system-ui, sans-serif);
  cursor: pointer;
}

.plan-toggle button.active {
  background: var(--paper);
  color: var(--ink);
}

/* Only ever filled by the script, and only when something went wrong. */
.plan-note:empty {
  display: none;
}

.plan-note {
  color: var(--accent);
}

.faq h3 {
  margin-top: 20px;
}

/* Where the plan columns stop being wide enough for the price and the button
   copy to hold one line each, the heads give back some of their padding and
   the price comes down a size. Below 820px or so the wrapper is scrolling the
   table sideways as well, and the columns are at their narrowest. */
@media (max-width: 1024px) {
  .plan-matrix th.plan {
    padding: 14px 10px 16px;
  }

  /* A price is one unbreakable word: at this width the big size would run
     out of the column rather than wrap. */
  .price {
    font-size: 1.2rem;
  }

  .plan-matrix th.plan .cta {
    padding: 9px 4px;
    font-size: 0.88rem;
  }

  /* The two toggle buttons want more room than the column has; their own
     padding is the part that can go. */
  .plan-toggle button {
    padding: 5px 4px;
  }

  .plan-matrix tbody td {
    padding: 12px 10px;
  }
}

/* Where the columns bottom out (the table is at its min-width and scrolling
   sideways), Pro's monthly note runs to four lines rather than three: the
   offer inside it is a button, and a button cannot break across lines, so it
   wraps a word or so earlier than the sentence around it would. Every column
   reserves the fourth line, not just Pro's — the reservation is what keeps
   the four prices and the four buttons level. */
@media (max-width: 860px) {
  .billed,
  .price-note {
    min-height: 5.4em;
  }
}

/* ---------- the /pro pages ---------- */

.pro-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 18px 0 16px;
}

.pro-actions .cta {
  margin-top: 0;
}

/* /pro/activate holds its way back shut until the token is in localStorage:
   clicking it while /api/activate is still in flight aborts the call that
   writes it, and the map boots free. Muted, and not clickable, until then. */
.cta[aria-disabled='true'] {
  border-color: var(--rule);
  background: transparent;
  color: var(--muted);
  cursor: default;
  pointer-events: none;
}

/* "Email me a link": one field, one button, on one line where there is room. */
.restore-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin: 18px 0 14px;
}

.restore-form label {
  flex: 1 0 100%;
  font-size: 0.82rem;
  color: var(--muted);
}

.restore-form input {
  flex: 1 1 16rem;
  min-width: 0;
  padding: 10px 12px;
  border: 1px solid var(--rule);
  border-radius: 10px;
  background: var(--paper);
  color: var(--ink);
  font: inherit;
  font-size: 0.95rem;
}

.restore-form input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.restore-form .cta {
  flex: 0 0 auto;
  margin-top: 0;
}

/* ---------- the route log (/routes and everything under it) ---------- */

/* Generated by scripts/build-route-log.mjs, so every class here is written by
   that script and nothing else on the site uses them. Body class `routes`
   scopes the lot — `.embed` above already belongs to the Examples iframes,
   and the log's map plate would otherwise land on top of it. */

/* ---- the week on one map ---- */

.routes .hero {
  margin: 0 0 32px;
}

.routes .frame {
  position: relative;
}

/* The plate every map sits on is the sea; the ground over it is the app's own
   basemap, cut out of the same Protomaps archive the app renders and written
   into the page by scripts/route-log-basemap.mjs. Every colour comes from the
   app's Dark look, because these are the app's own route colours and they are
   drawn for that ground. */
.routes .map,
.routes .thumb,
.routes .mini,
.routes .embed {
  background: var(--log-water);
  overflow: hidden;
}

.routes .map {
  position: relative;
  aspect-ratio: 2 / 1;
  border-radius: 8px;
}

.routes .map svg,
.routes .thumb svg,
.routes .mini svg,
.routes .embed svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* The SVG's own ink. Every colour a route is drawn in comes from a class on
   the path, so the palette stays in this file with the rest of the tokens
   and the generated HTML carries no hex at all. */
.routes .map text,
.routes .thumb text,
.routes .embed text {
  font-family: var(--font-mono, monospace);
  font-size: 12px;
  fill: #e8eaf0;
}

.routes .thumb text {
  font-size: 13px;
}

/* The basemap: the sea the frame is painted, the land on it, the water on the
   land, and the borders across both. `.land` is the fallback drawing — the
   Natural Earth coastline path, for a build that could reach no tiles. */
.routes .sea,
.routes .water {
  fill: var(--log-water);
}

.routes .earth,
.routes .land {
  fill: var(--log-land);
}

.routes .border {
  fill: none;
  stroke: var(--log-border);
  stroke-width: 0.75;
}

/* A hero and an entry's embed are 960 units across, a thumbnail 320 and a
   past-week chip 56: a pixel of border on the big ones, and the floor on the
   rest, so a line never falls under half a pixel. */
.routes .map .border,
.routes .embed .border {
  stroke-width: 1;
}

/* State lines only appear close in, and never at the weight of a country: the
   app fades them in over z4–z6 and a static map has no ramp to do it with. */
.routes .border.region {
  stroke-opacity: 0.5;
}

/* The coastline path itself, once a page: a box with no size, above every map
   that draws it. Block rather than the inline default — an inline SVG here
   would open a line's worth of space at the top of the page. */
.routes .defs {
  display: block;
}

.routes .arc {
  fill: none;
  stroke: currentColor;
  stroke-width: 2.2;
  stroke-linecap: round;
}

.routes .glow {
  fill: none;
  stroke: currentColor;
  stroke-opacity: 0.22;
  stroke-width: 8;
  stroke-linecap: round;
}

.routes .dot {
  fill: currentColor;
  stroke: var(--voyarc-midnight, #060913);
  stroke-width: 1.5;
}

/* Every map says what changed, so a route is drawn in the colour of its
   change wherever it appears — on its week's map, on its own page and on the
   thumbnail in the list. Each class sets `color` alone — the arc strokes it,
   the dot fills with it, the legend's bullet is a block of it — so no rule
   here has to know which of the four it is painting. */
.routes .new {
  color: var(--map-new);
}

.routes .back {
  color: var(--map-back);
}

.routes .more {
  color: var(--map-more);
}

.routes .cut {
  color: var(--map-cut);
}

.routes .brand {
  position: absolute;
  left: 14px;
  top: 12px;
  padding: 3px 10px;
  border-radius: 4px;
  background: rgba(6, 9, 19, 0.72);
  font: 700 15px/1.4 var(--font-brand, system-ui, sans-serif);
  color: #e8eaf0;
}

.routes .credit {
  position: absolute;
  right: 12px;
  bottom: 12px;
  padding: 2px 7px;
  border-radius: 4px;
  background: rgba(6, 9, 19, 0.72);
  font-size: 11px;
  color: #a6abbc;
}

.routes .legend {
  position: absolute;
  left: 14px;
  bottom: 12px;
  display: grid;
  gap: 4px;
  padding: 8px 10px;
  border-radius: 6px;
  background: rgba(6, 9, 19, 0.78);
  font-size: 12px;
  line-height: 1.45;
  color: #e8eaf0;
}

.routes .legend b {
  margin-bottom: 2px;
  font-weight: 600;
}

.routes .legend span {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}

.routes .legend i {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: currentColor;
}

/* The line under the hero: what the week is, then the two ways into it. */
.routes .under {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 14px;
  margin-top: 10px;
  font-size: 0.88rem;
  color: var(--muted);
}

.routes .under .title {
  font: 700 1.05rem/1.35 var(--font-brand, system-ui, sans-serif);
  letter-spacing: -0.01em;
  color: var(--ink);
}

.routes .under .acts {
  display: flex;
  gap: 8px;
  margin-left: auto;
}

/* ---- past weeks ---- */

.routes .past {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px;
  margin-top: 14px;
  font-size: 0.82rem;
  color: var(--muted);
}

.routes .past .k {
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.routes .past a {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 4px 10px 4px 4px;
  border: 1px solid var(--rule);
  border-radius: 6px;
  color: var(--ink);
  text-decoration: none;
}

.routes .past a:hover {
  border-color: var(--ink);
}

/* Its own 56×28 drawing, not the hero shrunk: at this size a hero's strokes
   would be a tenth of a pixel wide and the codes unreadable, so the build
   draws the minis without labels or glow and they need only be sized. */
.routes .mini {
  width: 56px;
  height: 28px;
  border-radius: 3px;
}

.routes .mini .arc {
  stroke-width: 1.5;
}

.routes .past a small {
  color: var(--muted);
}

/* ---- the filter bar ---- */

.routes .toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px;
  padding: 10px 0;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}

.routes .toolbar .result {
  margin: 0 0 0 auto;
  font-size: 0.88rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.routes .seg {
  display: inline-flex;
  gap: 4px;
  margin-right: 4px;
  padding-right: 8px;
  border-right: 1px solid var(--rule);
}

.routes .seg button {
  padding: 6px 11px;
  border: 1px solid transparent;
  border-radius: 6px;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 0.88rem;
  cursor: pointer;
}

.routes .seg button:hover:not(:disabled):not([aria-pressed='true']) {
  background: var(--surface);
}

/* The chosen chip wears its change's own colours — the same pair the badge
   on every matching entry wears, so the bar and the cards agree at a glance.
   "All" has no colour of its own and takes the ink. */
.routes .seg button[aria-pressed='true'] {
  background: var(--ink);
  color: var(--paper);
  font-weight: 600;
}

.routes .seg button[aria-pressed='true'][data-key='new'] {
  background: var(--chip-new-bg);
  color: var(--chip-new);
}

.routes .seg button[aria-pressed='true'][data-key='back'] {
  background: var(--chip-back-bg);
  color: var(--chip-back);
}

.routes .seg button[aria-pressed='true'][data-key='more'] {
  background: var(--chip-more-bg);
  color: var(--chip-more);
}

.routes .seg button[aria-pressed='true'][data-key='cut'] {
  background: var(--chip-cut-bg);
  color: var(--chip-cut);
}

/* Greyed, never hidden: an option that would empty the page still says the
   log has nothing of that kind under the filters already set. */
.routes .seg button:disabled,
.routes .menu-list button:disabled {
  color: var(--muted);
  opacity: 0.4;
  cursor: default;
}

.routes .menu {
  position: relative;
}

.routes .menu summary {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px 6px 12px;
  border: 1px solid var(--rule);
  border-radius: 6px;
  background: var(--paper);
  color: var(--ink);
  font-size: 0.88rem;
  list-style: none;
  cursor: pointer;
  user-select: none;
}

.routes .menu summary::-webkit-details-marker {
  display: none;
}

.routes .menu summary .v {
  color: var(--muted);
}

.routes .menu summary .v b {
  color: var(--ink);
  font-weight: 600;
}

.routes .menu summary svg {
  color: var(--muted);
}

.routes .menu[open] summary,
.routes .menu summary:hover,
.routes .menu.active summary {
  border-color: var(--ink);
}

.routes .menu-list {
  position: absolute;
  z-index: 5;
  top: calc(100% + 6px);
  left: 0;
  min-width: 210px;
  max-height: 320px;
  overflow-y: auto;
  padding: 6px;
  border: 1px solid var(--rule);
  border-radius: 8px;
  background: var(--paper);
  box-shadow: 0 8px 28px rgba(6, 9, 19, 0.18);
}

.routes .menu-list button {
  display: flex;
  width: 100%;
  align-items: center;
  gap: 9px;
  padding: 7px 10px;
  border: 0;
  border-radius: 5px;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 0.88rem;
  text-align: left;
  cursor: pointer;
}

.routes .menu-list button:hover:not(:disabled) {
  background: var(--surface);
}

.routes .menu-list button[aria-pressed='true'] {
  background: var(--ink);
  color: var(--paper);
}

.routes .menu-list .none {
  padding: 8px 10px;
  font-size: 0.82rem;
  color: var(--muted);
}

.routes .search {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 2px 2px 6px;
  padding: 5px 9px;
  border: 1px solid var(--rule);
  border-radius: 6px;
  background: var(--paper);
}

.routes .search svg {
  flex: none;
  color: var(--muted);
}

.routes .search input {
  width: 100%;
  min-width: 0;
  border: 0;
  outline: 0;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 0.88rem;
}

.routes .search input::placeholder {
  color: var(--muted);
}

.routes .search:focus-within {
  border-color: var(--ink);
}

/* What is filtering the page right now, with a way out of each of them. */
.routes .active-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  min-height: 30px;
  padding: 4px 0;
  font-size: 0.82rem;
  color: var(--muted);
}

.routes .active-row .pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 6px 2px 10px;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink);
}

.routes .active-row .pill button {
  padding: 0 3px;
  border: 0;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 0.95rem;
  line-height: 1;
  cursor: pointer;
}

.routes .active-row .pill button:hover {
  color: var(--ink);
}

.routes .active-row .clear {
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 0.82rem;
  text-decoration: underline;
  text-decoration-color: var(--rule);
  cursor: pointer;
}

/* ---- the airline's code as a tile ---- */

.routes .mono {
  display: inline-grid;
  flex: none;
  place-items: center;
  width: 22px;
  height: 22px;
  border: 1px solid var(--rule);
  border-radius: 5px;
  background: var(--surface);
  color: var(--ink);
  font-family: var(--font-mono, monospace);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.02em;
}

.routes .menu-list .mono {
  width: 24px;
  height: 24px;
}

.routes .menu-list button[aria-pressed='true'] .mono {
  border-color: rgba(255, 255, 255, 0.35);
  background: transparent;
  color: var(--paper);
}

/* ---- weeks and entries ---- */

.routes .week {
  margin: 0 0 8px;
  padding-top: 22px;
}

.routes .week h2 {
  display: flex;
  align-items: center;
  gap: 14px;
  margin: 0 0 4px;
  font-size: 1.05rem;
  color: var(--ink);
}

.routes .week h2 a {
  color: var(--ink);
  text-decoration: none;
}

.routes .week h2 a:hover {
  text-decoration: underline;
}

/* The rule runs from the end of the title to the count and no further. */
.routes .week h2::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--rule);
}

.routes .week h2 .n {
  order: 3;
  font: 400 0.82rem/1.5 var(--font-ui, system-ui, sans-serif);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.routes .entry {
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: 20px;
  padding: 20px 0;
  border-top: 1px solid var(--rule);
}

.routes .entry:first-of-type {
  border-top: 0;
}

.routes .thumb {
  display: block;
  aspect-ratio: 16 / 10;
  border-radius: 6px;
}

.routes .eyebrow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-bottom: 6px;
  font-size: 0.82rem;
  color: var(--muted);
}

.routes .eyebrow .who {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  color: var(--ink);
}

.routes .eyebrow time {
  font-variant-numeric: tabular-nums;
}

.routes .chip {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 4px;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.routes .chip.new {
  background: var(--chip-new-bg);
  color: var(--chip-new);
}

.routes .chip.back {
  background: var(--chip-back-bg);
  color: var(--chip-back);
}

.routes .chip.more {
  background: var(--chip-more-bg);
  color: var(--chip-more);
}

.routes .chip.cut {
  background: var(--chip-cut-bg);
  color: var(--chip-cut);
}

.routes .tag {
  display: inline-block;
  padding: 1px 7px;
  border: 1px solid var(--rule);
  border-radius: 4px;
  font-size: 0.75rem;
  color: var(--muted);
}

.routes .entry h3 {
  margin: 0 0 6px;
  font: 600 1.2rem/1.2 var(--font-ui, system-ui, sans-serif);
  letter-spacing: -0.005em;
  text-wrap: balance;
}

.routes .entry h3 a {
  color: var(--ink);
  text-decoration: none;
}

.routes .entry h3 a:hover {
  text-decoration: underline;
}

.routes .code {
  font-family: var(--font-mono, monospace);
  letter-spacing: 0.02em;
}

/* Route, distance, aircraft, date, frequency — separated by middots that
   belong to the layout rather than the copy. */
.routes .facts {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 0;
  margin: 0 0 8px;
  font-size: 0.88rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.routes .facts span + span::before {
  content: '·';
  margin: 0 8px;
  color: var(--rule);
}

.routes .facts b {
  color: var(--ink);
  font-weight: 600;
}

/* The one line of context an entry may carry, bulleted in the route gold. */
.routes .context {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin: 0 0 8px;
  font-size: 0.88rem;
  color: var(--muted);
}

.routes .context::before {
  content: '';
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--voyarc-gold, #ffb703);
  transform: translateY(-1px);
}

.routes .note {
  margin: 0 0 10px;
  max-width: var(--measure);
  text-wrap: pretty;
}

.routes .links {
  font-size: 0.88rem;
}

.routes .links .open {
  font-weight: 600;
}

.routes .empty {
  padding: 40px 0;
  border-top: 1px solid var(--rule);
  color: var(--muted);
}

.routes .log-foot {
  margin: 40px 0 0;
  font-size: 0.82rem;
  color: var(--muted);
}

/* ---- a week's page, and an entry's ---- */

.routes .crumb {
  margin-bottom: 20px;
  font-size: 0.88rem;
}

.routes .crumb a {
  color: var(--muted);
}

.routes .week-page .hero {
  margin-top: 20px;
}

.routes .entry-page .code {
  margin-bottom: 12px;
  font-size: 1rem;
  color: var(--muted);
}

.routes .embed {
  position: relative;
  margin: 22px 0 8px;
  border: 0;
}

.routes .embed .tagline {
  position: absolute;
  left: 12px;
  bottom: 10px;
  padding: 3px 8px;
  border-radius: 4px;
  background: rgba(6, 9, 19, 0.7);
  font-family: var(--font-mono, monospace);
  font-size: 12px;
  color: #a6abbc;
}

/* The two ways on from a page: into the app, or into a clipboard. */
.routes .actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin: 12px 0 28px;
}

.routes .actions a,
.routes .under .acts a {
  display: inline-block;
  padding: 8px 14px;
  border: 1px solid var(--rule);
  border-radius: 6px;
  color: var(--ink);
  font-size: 0.88rem;
  text-decoration: none;
}

.routes .under .acts a {
  padding: 6px 12px;
}

.routes .actions a.primary,
.routes .under .acts a.primary {
  border-color: var(--accent);
  background: var(--accent);
  color: #fff;
  font-weight: 600;
}

.routes .actions a:hover,
.routes .under .acts a:hover {
  border-color: var(--ink);
}

.routes .actions a.primary:hover,
.routes .under .acts a.primary:hover {
  border-color: var(--accent);
  filter: brightness(1.05);
}

.routes .actions .copy {
  padding: 8px 14px;
  border-radius: 6px;
  font-size: 0.88rem;
}

/* ---- the owner's post box ---- */

/* What to post when a week goes up, beside the picture to post it with.
   Hidden in the markup and unhidden by the page's own script on a dev server
   and on a Pages preview — the hosts previewAllowedOn allows — so a reader on
   voyarc.app never meets it. Dashed, because it is not part of the page. */
.routes .post {
  display: grid;
  grid-template-columns: 1fr minmax(0, 240px);
  align-items: start;
  gap: 16px;
  margin: 0 0 28px;
  padding: 14px 16px;
  border: 1px dashed var(--rule);
  border-radius: 8px;
  background: var(--surface);
}

.routes .post b {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
  font-size: 0.88rem;
}

.routes .post > div > code {
  display: block;
  margin-bottom: 10px;
  font-family: var(--font-mono, monospace);
  font-size: 0.82rem;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.routes .post img {
  width: 100%;
  height: auto;
  border-radius: 6px;
}

.routes .post .none {
  margin: 0;
  font-size: 0.82rem;
  color: var(--muted);
}

.routes .facts-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 14px 24px;
  margin-bottom: 22px;
  padding: 16px 0;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}

.routes .facts-grid div {
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.routes .facts-grid div b {
  display: block;
  margin-top: 3px;
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.routes .entry-page .context {
  font-size: 1rem;
  color: var(--ink);
}

.routes .entry-page .note {
  font-size: 1.05rem;
}

.routes .entry-page .src {
  font-size: 0.88rem;
  color: var(--muted);
}

.routes .entry-page .src a {
  color: var(--muted);
  text-decoration-color: var(--rule);
}

.routes .also {
  margin-top: 36px;
  padding-top: 18px;
  border-top: 1px solid var(--rule);
  font-size: 0.88rem;
  color: var(--muted);
}

.routes .also a {
  margin-right: 14px;
}

/* ---- the hubs: an airline's page, an airport's, and an index of each ---- */

/* The line every page of the log carries, under the crumb or under the lede:
   two hops from anywhere to any hub. */
.routes .hub-nav {
  margin: -10px 0 22px;
  font-size: 0.88rem;
  color: var(--muted);
}

/* The links threaded through a card — the airline's name, the route's codes —
   read as text until they are hovered: on these pages a colour means a change,
   and a card of gold links would say four things it does not mean. */
.routes .eyebrow .who a,
.routes .code a {
  color: inherit;
  text-decoration: none;
}

.routes .eyebrow .who a:hover,
.routes .code a:hover {
  text-decoration: underline;
}

.routes .hub-page .hero {
  margin-top: 20px;
}

/* The way across at the foot of a hub: an airline's airports, an airport's
   airlines. A row of names, wrapping, with room between the lines. */
.routes .hub-links {
  margin: 0 0 30px;
  font-size: 0.88rem;
  line-height: 1.9;
  color: var(--muted);
}

/* Every hub of one kind, each with how much of the log is in it. Columns
   rather than one long list: 59 airports is four screens in a single file. */
.routes .hub-index {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 0 28px;
  margin: 0 0 30px;
  padding: 0;
  list-style: none;
}

.routes .hub-index li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 0;
  border-bottom: 1px solid var(--rule);
}

.routes .hub-index li span {
  font-size: 0.8rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

@media (max-width: 760px) {
  /* The chips scroll rather than wrap: four of them plus All is one swipe,
     and the count moves under the row where it has space to say "3 of 8". */
  .routes .toolbar .result {
    order: 9;
    width: 100%;
    margin: 0;
  }

  .routes .seg {
    width: 100%;
    padding-right: 0;
    border-right: 0;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }

  .routes .seg::-webkit-scrollbar {
    display: none;
  }

  .routes .seg button {
    flex: none;
    white-space: nowrap;
  }

  /* A legend over a phone-sized map covers the map; below it, it does not. */
  .routes .legend {
    position: static;
    display: flex;
    flex-wrap: wrap;
    gap: 6px 14px;
    margin-top: 8px;
    padding: 8px 10px;
    background: var(--surface);
    color: var(--ink);
  }

  .routes .legend b {
    width: 100%;
    margin: 0;
  }

  .routes .map {
    aspect-ratio: 16 / 10;
  }

  .routes .entry {
    grid-template-columns: 1fr;
    gap: 14px;
  }

  .routes .thumb {
    max-width: 320px;
  }

  .routes .under .acts {
    margin-left: 0;
  }

  .routes .post {
    grid-template-columns: 1fr;
  }
}
