/* Route Refinery free beta.
   An instrument, not a travel site: restrained palette, one accent, generous
   whitespace, and type that stays readable on a phone held at arm's length. */

:root {
  --ink: #14181d;
  --ink-soft: #4c565f;
  --ink-faint: #7b858e;
  --rule: #dfe3e7;
  --paper: #ffffff;
  --wash: #f6f7f8;
  --accent: #1b5e8c;
  --accent-ink: #ffffff;
  --warn: #8a4b12;
  --measure: 38rem;
}

* { box-sizing: border-box; }

/* THE `hidden` ATTRIBUTE MEANS HIDDEN. A class rule that sets `display` beats
   the user-agent's `[hidden] { display: none }`, which is why "Copied" was on
   screen from page load: `.copied` declared `display: inline-block`, so the
   attribute the script toggles had no effect at all. One rule fixes the whole
   class of bug rather than one element of it. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 400 17px/1.55 ui-sans-serif, -apple-system, "Segoe UI", Roboto,
        "Helvetica Neue", Arial, sans-serif;
  overflow-wrap: break-word;
}

.wrap {
  width: 100%;
  max-width: var(--measure);
  margin: 0 auto;
  padding: 0 1.25rem;
}

/* ---- masthead ---- */
.masthead { border-bottom: 1px solid var(--rule); }
.masthead-inner {
  display: flex; align-items: center; gap: .75rem;
  padding-top: 1rem; padding-bottom: 1rem;
}
.wordmark { font-weight: 600; letter-spacing: .01em; }
.badge {
  font-size: .72rem; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-soft); border: 1px solid var(--rule);
  border-radius: 999px; padding: .15rem .55rem;
}

/* ---- type ---- */
h1 {
  font-size: 1.95rem; line-height: 1.2; letter-spacing: -0.015em;
  margin: 2rem 0 .75rem;
}
h2 {
  font-size: .78rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-faint); margin: 1.75rem 0 .7rem;
}
.lede { color: var(--ink-soft); margin: 0 0 1rem; }
.trust {
  font-weight: 600; margin: 0 0 2rem;
  padding-left: .7rem; border-left: 2px solid var(--accent);
}
.eyebrow {
  font-size: .75rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-faint); margin: 2rem 0 .25rem;
}
.fineprint { color: var(--ink-faint); font-size: .85rem; margin: .75rem 0 0; }
.field-note { color: var(--ink-faint); font-size: .85rem; margin: .5rem 0 0; }

/* ---- upload ---- */
.dropzone {
  display: flex; flex-direction: column; gap: .35rem;
  align-items: center; justify-content: center; text-align: center;
  min-height: 8.5rem; padding: 1.5rem 1rem;
  border: 1.5px dashed var(--rule); border-radius: 10px;
  background: var(--wash); cursor: pointer;
}
.dropzone:hover, .dropzone:focus-within { border-color: var(--accent); }
.dropzone.is-armed { border-style: solid; border-color: var(--accent); }
.dropzone-title { font-weight: 600; }
.dropzone-hint { color: var(--ink-faint); font-size: .9rem; }

.field { margin: 1.5rem 0; }
/* The narrative renders real blocks now, so `pre-line` is gone - it would add
   a second set of breaks on top of the elements. Typography and spacing are the
   page's existing values; these rules only set rhythm between blocks. */
/* SCOPED TO THE ID, and that is not cosmetic: `.narrative p { margin: 0 }`
   further down this file outranks a bare `.narrative-paragraph` class, so the
   paragraph spacing below never applied and the briefing rendered as one dense
   block. An id selector wins, and the surrounding typography is untouched. */
#result-description > *:first-child { margin-top: 0; }
#result-description > *:last-child { margin-bottom: 0; }
#result-description .narrative-paragraph { margin: 0 0 0.9em; }
#result-description .narrative-heading {
  margin: 1.6em 0 0.6em;
  font-size: 1.02rem;
  font-weight: 650;
  letter-spacing: 0.01em;
  color: var(--ink);
}
/* A sub-section label: same weight as a heading, smaller and tighter to the
   paragraph it introduces, so the two levels are distinguishable. */
#result-description .narrative-label {
  margin: 1.15em 0 0.3em;
  font-size: 0.94rem;
  font-weight: 600;
  color: var(--ink);
}
#result-description .narrative-list {
  margin: 0 0 0.9em;
  padding-left: 1.1em;
}
#result-description .narrative-list li { margin: 0 0 0.35em; }
/* A hairline, not a bar: the headings already separate the sections and a
   heavy rule between every one of them was the loudest thing on the page. */
#result-description hr {
  border: 0;
  border-top: 1px solid var(--rule);
  margin: 1.7em 0 1.4em;
}

.field-optional {
  font-weight: 400;
  opacity: 0.65;
  margin-left: 0.35rem;
  font-size: 0.85em;
}

.field-label { display: block; font-weight: 600; margin-bottom: .5rem; }

select {
  width: 100%; min-height: 3rem; padding: .6rem .75rem;
  font: inherit; color: var(--ink);
  background: var(--paper); border: 1px solid var(--rule); border-radius: 8px;
}

button {
  font: inherit; cursor: pointer; border-radius: 8px;
  min-height: 3rem; padding: .75rem 1.25rem;
}
.primary {
  width: 100%; border: none;
  background: var(--accent); color: var(--accent-ink); font-weight: 600;
}
.primary:disabled { background: var(--rule); color: var(--ink-faint); cursor: not-allowed; }

.error {
  margin: 1rem 0 0; padding: .8rem 1rem; border-radius: 8px;
  background: #fdf3ec; color: var(--warn); border: 1px solid #f0d9c6;
}

.how ol { margin: 0; padding-left: 1.15rem; color: var(--ink-soft); }
.how li { margin: .3rem 0; }

/* ---- landing: the example experience ----
   The brief below is the dominant element, so this block is short: a heading,
   a line, the action, and the label that says what the reader is looking at.
   The CTA is a link rather than a button because it navigates - and it is the
   page's one primary control, so it keeps the primary treatment. */
.cta {
  /* A BUTTON, NOT A BANNER. The landing CTA carries `primary` for its colour
     and weight, and `primary` also declares `width: 100%` - which is right for
     the uploader's submit inside a 38rem form and wrong here, where the page
     shares the brief's wide measure. So the width is stated explicitly and
     wins by cascade order: content-sized, with a floor wide enough that the
     label never crowds its own padding.

     It stays left-aligned in the content column - the same edge as the
     heading, the lede and the brief - rather than centred in a blue band. */
  display: inline-block;
  width: auto;
  min-width: 12rem;              /* ~192px: prominent, still subordinate */
  text-align: center; text-decoration: none;
  padding: .7rem 1.4rem;         /* ~48px tall: a comfortable click target */
  border-radius: 8px; font-weight: 600;
  background: var(--accent); color: var(--accent-ink);
}
/* The introduction keeps a reading measure of its own even when the brief
   below it goes wide - a 74rem line of body text is not readable. */
#view-example { max-width: 46rem; }
.cta:hover { filter: brightness(1.08); }

/* THE SAMPLE PAGE'S SECOND ACTION: see what has already been published. The
   masthead carries the same destination and is too quiet to be found from
   here, so this is a real control - but an OUTLINE one, so the primary stays
   the primary. Content-sized, never full width, and the row wraps rather than
   overflowing on a phone. */
.example-cta-row {
  display: flex; flex-wrap: wrap; gap: .6rem; align-items: center;
}
.cta-secondary {
  display: inline-block; width: auto;
  padding: .7rem 1.1rem; border-radius: 8px;
  border: 1px solid var(--rule); background: none;
  font-size: .92rem; font-weight: 600; text-decoration: none;
  color: var(--ink-soft);
}
.cta-secondary:hover { color: var(--ink); border-color: var(--ink-faint); }

/* "Example Route": a quiet flag, not a promotional banner. It sits between
   the CTA and the brief, which is where a reader's eye passes anyway. */
.example-flag {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: .5rem;
  margin: 1.5rem 0 .35rem;
}
.example-badge {
  font-size: .68rem; letter-spacing: .09em; text-transform: uppercase;
  color: var(--ink-soft); border: 1px solid var(--rule);
  border-radius: 999px; padding: .12rem .5rem; white-space: nowrap;
}
.example-note { color: var(--ink-faint); font-size: .82rem; }

/* Under the example: the action again, and a way to see a different one. Both
   are content-sized and sit side by side, wrapping rather than stretching, so
   the foot of the page does not become a second blue bar. */
.example-actions {
  display: flex; align-items: center; justify-content: flex-start;
  flex-wrap: wrap; gap: .75rem;
  margin: 1.75rem 0 0;
}

.back-link { margin: 1.25rem 0 0; font-size: .85rem; }
.back-link a { color: var(--ink-soft); text-decoration: none; }
.back-link a:hover { color: var(--ink); text-decoration: underline; }

/* BACK TO THE COLLECTION, from a published route page. A navigation link and
   not a CTA - no fill, no border, no minimum width - but the site's accent
   rather than the muted grey the upload page's back link uses, because in the
   corner at masthead size this destination was being missed. Same size and
   position on a phone; it is body-flow text, so nothing needs to move. */
.route-back { margin: 1rem 0 .25rem; font-size: .92rem; }
.route-back a { color: var(--accent); text-decoration: none; }
.route-back a:hover { text-decoration: underline; }

/* Print-only content stays off the screen at every width. */
.print-only { display: none; }

/* ---- measuring ---- */
.measuring { margin-bottom: .5rem; }
.bar {
  height: 3px; background: var(--rule); border-radius: 2px;
  overflow: hidden; margin-top: 1.5rem;
}
.bar span {
  display: block; height: 100%; width: 35%; background: var(--accent);
  animation: sweep 1.4s ease-in-out infinite;
}
@keyframes sweep {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(320%); }
}
@media (prefers-reduced-motion: reduce) {
  .bar span { animation: none; width: 100%; opacity: .45; }
}

/* ---- result ----
   THE MAP AND THE DESCRIPTION ARE THE PRODUCT. The sidebar supports them, and
   it is sized and weighted to say so.

   TWO STRUCTURAL DECISIONS, both about composition rather than arrangement:

   ONE SPANNING SIDEBAR. The second pass gave identity, measurements and the ad
   three separate grid areas beside the map, which meant the map shared a row
   with whichever of them was tallest - and the description was pushed away from
   the map by whatever slack that row had. The sidebar is now ONE grid item
   spanning the column, so the main column's rows are sized by the map and the
   description alone and they sit together with one small gap between them.

   A QUIETER RAIL. Nothing in the sidebar competes with the map: the title is
   1.3rem rather than the page's 1.95rem heading, the subline is small and
   faint, and a measurement is a 0.62rem label over a 0.95rem value. It is a
   panel to scan, not a second thing to read. */
#view-result { max-width: none; }
.result-primary {
  display: grid;
  grid-template-areas: 'head' 'map' 'side' 'narrative' 'ad';
  gap: .9rem;
}
.result-map { grid-area: map; margin: 0; min-width: 0; }
.rail-ad { grid-area: ad; }

/* Phone and portrait tablet: the wrapper steps out of the way so its three
   children are grid items of the outer grid and can be ordered individually. */
.result-side { display: contents; }
.result-head { grid-area: head; margin: 0; }
.side-body { grid-area: side; min-width: 0; }

/* Identity: quiet. A route's name is not a page banner. */
.result-head h1 {
  font-size: 1.3rem; line-height: 1.25; margin: .1rem 0 .25rem;
}
.eyebrow {
  margin: 0; font-size: .68rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-faint);
}
.subline {
  color: var(--ink-faint); margin: 0; font-size: .85rem;
  font-variant-numeric: tabular-nums;
}

/* The map is a deliberate block at every width, and roughly half the phone
   viewport - big enough to read the shape of a route on. */
.map-canvas {
  width: 100%;
  height: 48vh;
  min-height: 260px;
  border: 1px solid var(--rule);
  border-radius: 10px;
  background: var(--wash);
  overflow: hidden;
}
.map-canvas.is-empty { display: flex; align-items: center; }
.map-note { margin: .45rem 0 0; font-size: .85rem; color: var(--ink-soft); }

/* The description, immediately under the map and aligned to its edges: no
   fill, no box, no card padding. The accent edge is the one mark that says
   this is the written answer. */
.narrative {
  grid-area: narrative;
  border-left: 3px solid var(--accent);
  padding: 0 0 0 .9rem;
  /* EXACTLY THE MAP'S WIDTH. Both are items of the same grid column, so the
     box edges coincide by construction - and no `max-width` narrows this one
     out of that agreement. `min-width: 0` matches the map's, so neither can be
     widened past the column by its own content. Box-sizing is border-box
     globally, so the accent rule and the space after it are inside the width
     rather than added to it. */
  min-width: 0;
}
.narrative p { margin: 0; font-size: .94rem; line-height: 1.6; }

/* ---- the sidebar ---- */
.side-body h2 { margin: 0 0 .3rem; font-size: .68rem; }
.side-actions {
  display: flex; align-items: center; gap: .5rem;
  margin-top: .75rem; flex-wrap: wrap;
}
.copied { color: var(--accent); font-size: .8rem; }

/* Metrics: label over value over detail, tight, hairline separated. Same facts
   and the same strings - a third of the height they had as cards. */
.metrics { margin: 0; display: block; }
.metric {
  padding: .4rem 0; border-bottom: 1px solid var(--rule); min-width: 0;
}
.metric:first-child { padding-top: 0; }
.metric:last-child { border-bottom: 0; }
.metric dt {
  font-size: .62rem; letter-spacing: .09em; text-transform: uppercase;
  color: var(--ink-faint); line-height: 1.4;
}
.metric dd { margin: .05rem 0 0; min-width: 0; }
.metric-value {
  display: block; font-size: .95rem; font-weight: 600; line-height: 1.3;
  font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
}
.metric-detail {
  display: block; margin-top: .02rem;
  font-size: .76rem; line-height: 1.35; color: var(--ink-soft);
}

/* Disclosures: available, not shouting. A compact stack of utilities rather
   than four content sections - one hairline per row, a small summary, and no
   vertical rhythm of its own. */
details { margin-top: 1.25rem; }
summary {
  cursor: pointer; list-style: none;
  font-size: .72rem; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ink-faint); padding: .45rem 0;
}
summary::-webkit-details-marker { display: none; }
summary::after { content: ' +'; }
details[open] summary::after { content: ' \2013'; }
summary:hover { color: var(--ink); }

.facts { margin: .1rem 0 .5rem; }
.facts div {
  display: flex; justify-content: space-between; gap: 1rem;
  padding: .4rem 0; border-bottom: 1px solid var(--rule); font-size: .88rem;
}
.facts dt { color: var(--ink-soft); flex: 0 0 auto; }
.facts dd { margin: 0; text-align: right; font-variant-numeric: tabular-nums; }

.setting { font-size: .95rem; margin: 0; }
.setting .arrow { color: var(--ink-faint); padding: 0 .4rem; }

.geography {
  margin: 0 0 .5rem; padding-left: 1.15rem; color: var(--ink-soft);
  font-size: .88rem;
}
.geography li { margin: .2rem 0; }
/* One item is a sentence, not a bullet list of one. */
.context-line { margin: 0; color: var(--ink-soft); }

/* The interval list under a protected area that the route entered more than
   once. Quieter than the statement above it, because it is the working. */
.context-intervals {
  color: var(--ink-faint); font-size: .82rem;
  font-variant-numeric: tabular-nums;
}

.note {
  margin: 1rem 0 0; padding: .8rem 1rem; border-radius: 8px;
  background: var(--wash); border: 1px solid var(--rule);
  color: var(--ink-soft); font-size: .9rem;
}

/* The bordered `.cta` PANEL that used to live here belonged to the promotional
   block this page no longer has - no markup has matched it since that block
   was removed. It stayed dead in the stylesheet, and because it sat below the
   landing button's own rule its 1.15rem padding, 10px radius and grey border
   were what the button actually got. Removed, so one rule governs the CTA. */

.method p { color: var(--ink-soft); margin: .25rem 0 0; }

.foot {
  border-top: 1px solid var(--rule); padding-top: 1.25rem; padding-bottom: 2rem;
  color: var(--ink-faint); font-size: .85rem;
  display: flex; flex-wrap: wrap; gap: .35rem 1rem; justify-content: space-between;
}
.foot a, .wordmark { color: inherit; text-decoration: none; }
.foot a:hover { color: var(--ink); text-decoration: underline; }

/* The landing ad unit: separation from the upload controls and from the
   informational content below it. No width of its own - the unit is
   responsive and must stay so on a phone. */
.ad-slot { margin: 2.25rem 0 1.5rem; }
/* In the sidebar the unit needs separation, not a margin sized for a page
   break - and when AdSense does not fill it, the <ins> collapses to nothing
   and this label is all that remains, so it must not reserve space either. */
.rail-ad { margin: 1.1rem 0 0; }
/* Says where advertising belongs, including before Google fills the slot. No
   box, no reserved rectangle, no fixed height - an unfilled unit stays
   collapsed and the page does not pretend an ad is there. */
.ad-label {
  font-size: .66rem; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-faint); margin-bottom: .3rem;
}

/* The title and its Edit control on one line. `baseline` keeps the button on
   the title's last line rather than centred against a two-line heading, and
   `wrap` lets the button drop below on a narrow phone instead of squeezing the
   title. The input takes the heading's own type so the line does not move. */
.title-row {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: .5rem .75rem;
}
.title-row #result-title, .title-input { flex: 1 1 12rem; min-width: 0; }
/* The heading's own type, restated rather than inherited: `inherit` would take
   the row div's body type and the line would jump on every edit. The two
   responsive overrides for `.result-head h1` further down carry the input with
   them for the same reason. */
.title-input {
  font-family: inherit; font-size: 1.3rem; font-weight: bold;
  line-height: 1.25;
  color: var(--ink); background: var(--paper);
  border: 1px solid var(--rule); border-radius: 4px;
  padding: .15em .35em; width: 100%;
}
.title-input:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Small, quiet, and never the reason the title wraps. 44px tall for a thumb. */
.title-edit { flex: 0 0 auto; min-height: 44px; padding: .3rem .7rem; }

/* THE ROUTES INDEX LAYOUT. Browse column left, cards right on a desktop; one
   column on a phone, where the browse list is a <details> the reader can
   collapse rather than scroll past. */
/* THE ATLAS WIDTH. The analyzer's 38rem reading measure is right for one
   brief and wrong for a catalogue: at 38rem the card column resolved to 324px,
   the card's content box to 292px, and a 280px profile plus its gap needs
   300px - so the prose column computed to MINUS 8px, floored to zero by
   `minmax(0, ...)`, and `overflow-wrap: break-word` then broke the description
   one character per line. The index gets its own container width instead. */
body.has-routes .wrap { max-width: 76rem; }

.routes-head {
  display: flex; flex-wrap: wrap; gap: .75rem 1rem;
  align-items: baseline; justify-content: space-between;
}
.routes-head h1 { margin: 0; }

/* THE INVITATION. Secondary to the heading by type and colour, not by being
   tucked away: a short prompt, the sentence that says what happens, and a
   content-sized button. Its own reading measure, so the prose does not run the
   full width of the atlas page - and no width of its own beyond that, so the
   page is unchanged. */
.routes-cta { max-width: 34rem; margin: .75rem 0 1.5rem; }
.routes-cta-lead {
  margin: 0; font-size: .95rem; font-weight: 600; color: var(--ink);
}
.routes-cta .field-note { margin: .1rem 0 .6rem; }
/* NOT `.primary`, which declares `width: 100%` - that is the rule that made
   this a banner once the flex row blockified it. Compact, content-sized, and
   the same accent the rest of the site uses for an action. */
.routes-cta-button, .page-cta {
  display: inline-block; width: auto;
  padding: .45rem .95rem; border-radius: 8px;
  font-size: .9rem; font-weight: 600; text-decoration: none;
  background: var(--accent); color: var(--accent-ink);
}
.routes-cta-button:hover, .page-cta:hover { filter: brightness(1.08); }
/* WHY THIS EXISTS. `.page a` below sets the accent colour for prose links on
   the disclosure pages, and one class plus one element selector outranks the
   single class above - so the contact page's button was painted accent text on
   its own accent fill and rendered BLANK. One selector of equal weight puts
   the label back. */
.page a.page-cta { color: var(--accent-ink); }
/* Quieter than the button above it: a plain link in the muted ink, so the
   collection and the primary action both stay ahead of it. */
.routes-cta-aside { margin: .55rem 0 0; font-size: .85rem; }
.routes-cta-aside a { color: var(--ink-soft); }
.routes-cta-aside a:hover { color: var(--ink); }
.routes-layout { display: grid; gap: 1.25rem; }
/* 60rem, NOT 52rem. The rail costs the cards 13rem plus the gap, and between
   52 and 60rem that left the card too narrow for a side-by-side profile - so
   widening the window used to STACK the card that had been side by side. Above
   60rem the rail is affordable and the card still clears 38rem. */
@media (min-width: 60rem) {
  /* A FIXED 13rem rail. `1fr` for the cards takes everything left over, so
     the extra page width goes to the routes and not to the browse list. */
  .routes-layout {
    grid-template-columns: 13rem minmax(0, 1fr); align-items: start;
  }
  .routes-browse { position: sticky; top: 1rem; }
  /* On a wide screen the list is always open and the summary is a heading. */
  .routes-browse > summary { list-style: none; cursor: default; }
  .routes-browse > summary::-webkit-details-marker { display: none; }
}
.routes-browse > summary {
  font-size: .72rem; text-transform: uppercase; letter-spacing: .06em;
  color: var(--ink-faint); padding: .3rem 0; cursor: pointer;
}
/* PHONE ONLY, below the rail's breakpoint. The summary stops being a quiet
   label and becomes the one full-width control that opens the list: a text
   label and not an icon, at a size that is easy to hit. Above 60rem the rules
   in the media query above win and this never applies. */
@media (max-width: 59.9375rem) {
  .routes-browse > summary {
    font-size: .8rem; color: var(--ink-soft); text-align: center;
    padding: .8rem 1rem; border: 1px solid var(--rule); border-radius: .3rem;
    font-weight: 600; list-style: none;
  }
  .routes-browse > summary::-webkit-details-marker { display: none; }
  .routes-browse > summary:hover { color: var(--ink); }
  .routes-browse[open] > summary { margin-bottom: .5rem; }
}
#routes-geography { font-size: .88rem; line-height: 1.5; }
#routes-geography button {
  display: block; width: 100%; text-align: left; background: none;
  border: 0; padding: .16rem 0; font: inherit; color: var(--ink-soft);
  cursor: pointer;
}
#routes-geography button:hover { color: var(--accent); }
#routes-geography button[aria-current="true"] {
  color: var(--ink); font-weight: 600;
}
/* Indentation IS the hierarchy: country, state/province, region, subregion. */
#routes-geography .geo-1 { padding-left: .7rem; }
#routes-geography .geo-2 { padding-left: 1.4rem; }
#routes-geography .geo-3 { padding-left: 2.1rem; }
#routes-geography .geo-count { color: var(--ink-faint); font-weight: 400; }
/* The one mark that says a state/province has regions inside it. Right-aligned
   so the labels stay in a column, and decorative: `aria-expanded` on the
   button carries the state. */
#routes-geography .geo-caret { float: right; color: var(--ink-faint); }
#routes-geography button[aria-expanded="true"] { color: var(--ink); }

/* PUBLISHED ROUTES. One column on a phone, two when there is room. Built from
   the page's own rule colour and type; no card library and no new chart. */
.masthead-nav { margin-left: auto; font-size: .85rem; }
.masthead-nav a { color: var(--ink-soft); text-decoration: none; }
.masthead-nav a:hover { color: var(--ink); text-decoration: underline; }
/* ONE CARD PER ROW. Two narrow columns squeezed the thumbnail, and a
   comparison instrument that changes width between layouts is not one. */
.route-cards { display: grid; gap: 1rem; margin: .5rem 0 2rem; }
.route-card {
  border: 1px solid var(--rule); border-radius: 6px; padding: .9rem 1rem;
  background: var(--paper);
}
.route-card h2 { margin: 0 0 .2rem; font-size: 1.02rem; line-height: 1.3; }
.route-card h2 a { color: var(--ink); text-decoration: none; }
.route-card h2 a:hover { color: var(--accent); text-decoration: underline; }
/* TWO LINES: what the route is, then where it is. They read as one block, so
   they sit tight together and only the pair carries the space below. */
.route-card-meta {
  margin: 0 0 .15rem; font-size: .78rem; color: var(--ink-faint);
  text-transform: uppercase; letter-spacing: .05em;
}
.route-card-meta:last-of-type { margin-bottom: .6rem; }
.route-card-excerpt {
  margin: .5rem 0 0; font-size: .88rem; line-height: 1.55;
  color: var(--ink-soft);
}
/* THE CARD BODY. Prose left, profile right - but ONLY when the card itself is
   wide enough for both.

   WHAT WAS WRONG. This was a `@media (min-width: 44rem)` viewport query, and a
   viewport says nothing about the width of a card sitting in the second column
   of a grid inside a narrow container. On any desktop the query was true while
   the card had 292px to give, so the prose column collapsed to nothing.

   THE FIX. A CONTAINER query on the card, so the condition is measured where
   the constraint actually is. 38rem of card content is 608px: 180px of profile
   plus a 20px gap leaves 408px - around 66 characters at this type size, which
   is readable prose rather than a strip. Below that, or in a browser without
   container queries, the declaration never applies and the body stays a single
   column: description, then profile. Stacking is the safe direction. */
.route-card { container-type: inline-size; }
.route-card-body { display: grid; gap: .75rem 1.25rem; }
@container (min-width: 38rem) {
  .route-card-body {
    grid-template-columns: minmax(0, 1fr) 180px; align-items: start;
  }
}

/* THE PROFILE BOX, and it does NOT stretch. `width: 100%` used to hand the SVG
   the whole card, so one route drew at a different horizontal scale in every
   window size and read as a flat smear. A fixed 180x72 at the viewBox's own
   2.5:1 ratio replaces it: identical dimensions for every route, which is what
   makes the shapes comparable, and compact enough to read as an instrument
   beside the prose rather than a long sparkline across it. `max-width: 100%`
   only ever lets it SHRINK on a narrow phone, and the ratio holds when it does.
   The thumbnail is absent, not empty, when a route has no profile. */
.route-card-spark {
  display: block; width: 180px; max-width: 100%;
  height: auto; aspect-ratio: 180 / 72;
}

/* PUBLISHING METADATA. A label/field grid in the rail, the page's own type
   and rule colour. One column on a phone. */
.admin-meta { margin: 1.25rem 0 0; }
.admin-meta h2 { margin: 0 0 .4rem; }
.admin-meta-grid {
  display: grid; grid-template-columns: auto 1fr;
  gap: .35rem .6rem; align-items: center;
}
.admin-meta-grid label { font-size: .78rem; color: var(--ink-faint); }
.admin-meta-grid input, .admin-meta-grid select {
  font: inherit; font-size: .88rem; width: 100%;
  color: var(--ink); background: var(--paper);
  border: 1px solid var(--rule); border-radius: 4px; padding: .25rem .4rem;
}
@media (max-width: 30rem) {
  .admin-meta-grid { grid-template-columns: 1fr; gap: .15rem .6rem; }
}

/* THE SUGGESTION MENU. The page's own, because Safari answered a <datalist>
   on a field labelled "Country" with its own address AutoFill instead. Plain
   type and rule colour, absolutely positioned so it overlays the rail rather
   than pushing the fields around, and bounded so a long list scrolls. */
.meta-field { position: relative; }
.meta-menu {
  position: absolute; z-index: 20; left: 0; right: 0; top: 100%;
  max-height: 12rem; overflow-y: auto;
  background: var(--paper); border: 1px solid var(--rule);
  border-radius: 4px; box-shadow: 0 4px 12px rgba(20, 24, 29, .12);
}
.meta-option {
  padding: .3rem .45rem; font-size: .88rem; color: var(--ink-soft);
  cursor: pointer;
}
.meta-option:hover, .meta-option.is-active {
  background: var(--wash); color: var(--ink);
}

/* ADMIN AND THE NARRATIVE EDITOR. Plain, and built from what the page already
   has - the site's own type, rule colour and quiet button. Nothing here is
   loaded for a visitor: the controls are hidden and the table is empty. */
.brief-input {
  width: 100%; font: inherit; font-size: .94rem; line-height: 1.6;
  color: var(--ink); background: var(--paper);
  border: 1px solid var(--rule); border-radius: 4px; padding: .6rem;
}
.admin-filters {
  display: flex; flex-wrap: wrap; gap: .5rem; margin: .75rem 0 1rem;
  align-items: center;
}
.button-link { text-decoration: none; }
/* THE SAVED-ROUTES TABLE, at the reading measure, wrapped every column one
   word per line: eight short values in 568px. It is a spreadsheet view, so it
   gets its own width and its columns get room. */
body.has-admin .wrap { max-width: 88rem; }

/* Sideways on a phone rather than crushed - the columns keep their widths. */
.admin-table-scroll { overflow-x: auto; }
.admin-table { width: 100%; border-collapse: collapse; font-size: .9rem; }
.admin-table th, .admin-table td {
  text-align: left; padding: .4rem .5rem;
  border-bottom: 1px solid var(--rule); vertical-align: top;
  /* `body` sets `overflow-wrap: break-word`, which is what broke "New
     Hampshire" mid-word in a narrow cell. A table cell wraps between words. */
  overflow-wrap: normal;
  /* One row per record: every column stays on one line, and the scroller
     below carries the overflow sideways rather than growing rows. */
  white-space: nowrap;
}
/* Room where the values are. Title 20rem, Country 8, State/Prov 9, Region 11 -
   so the four columns the editor scans can show a value at the page's own
   width. Nowrap is NOT set here: the shared cell rule above covers every
   column, so columns 2, 3, 7 and 8 no longer need their own. */
.admin-table th:nth-child(1), .admin-table td:nth-child(1) { min-width: 20rem; }
.admin-table th:nth-child(4), .admin-table td:nth-child(4) { min-width: 8rem; }
.admin-table th:nth-child(5), .admin-table td:nth-child(5) { min-width: 9rem; }
.admin-table th:nth-child(6), .admin-table td:nth-child(6) { min-width: 11rem; }
.admin-table th:nth-child(8), .admin-table td:nth-child(8) { min-width: 7rem; }
.admin-table th { font-size: .72rem; text-transform: uppercase;
  letter-spacing: .06em; color: var(--ink-faint); }
.admin-table a { color: var(--accent); }
.admin-status-cell { text-transform: capitalize; }

/* The disclosure pages: the site's own type, nothing new. */
.page { padding-bottom: 2.5rem; }
.page h2 { margin-top: 1.75rem; }
.page p { color: var(--ink-soft); margin: .5rem 0 0; }
.page a { color: var(--accent); }

/* Compact secondary blocks: collapsed detail should not reintroduce the
   vertical cost the primary layout just removed. */
/* The secondary stack: quiet, and separated from the product by one rule
   rather than by a gap large enough to read as a new page. */
.secondary {
  margin-top: 1.5rem; padding-bottom: 2rem;
  border-top: 1px solid var(--rule);
}
.secondary details { margin-top: 0; border-bottom: 1px solid var(--rule); }
.secondary details:last-child { border-bottom: 0; }
.secondary details > *:not(summary) { margin-bottom: .6rem; }
.secondary .field-note { font-size: .82rem; }

/* Two sidebar actions. The copy control is findable and small; the second is
   quieter still, because starting over is not what the page is for. */
button.compact, button.quiet {
  border-radius: 8px; font: inherit; font-size: .85rem;
  padding: .4rem .8rem; cursor: pointer; width: auto; min-height: 0;
}
button.compact {
  background: var(--accent); color: var(--accent-ink); border: 0;
}
button.compact:hover { filter: brightness(1.08); }
button.quiet {
  background: none; border: 1px solid var(--rule); color: var(--ink-soft);
}
button.quiet:hover { color: var(--ink); border-color: var(--ink-faint); }

/* ---------------------------------------------------------------
   PHONE - the sidebar wrapper is `display: contents`, so the order is
   identity, map, measurements + actions, description, ad. Nothing is
   clamped: the description sits below the map and hiding it would conceal
   the product to save space nobody is waiting for.
---------------------------------------------------------------- */
@media (max-width: 46rem) {
  .map-canvas { height: 50vh; min-height: 300px; }
  .narrative p { font-size: 1rem; line-height: 1.6; }
  /* PHONE: a slightly larger target, still content-sized. Edge-to-edge is
     reserved for a genuinely narrow viewport below - on a normal phone a
     13rem button in a ~21rem column is easy to hit and does not read as a
     banner. The two controls keep sharing a row and wrap if they must. */
  .cta { min-width: 13rem; padding: .8rem 1.5rem; }
  .example-actions { gap: .6rem .75rem; }
}

/* GENUINELY NARROW - a small phone, where a content-sized control and the
   column are within a few millimetres of each other anyway. Only here does the
   primary take the full width, and only here do the two controls stack. */
@media (max-width: 22rem) {
  .cta { display: block; width: 100%; min-width: 0; }
  .example-actions { flex-direction: column; align-items: stretch; }
  .example-actions .quiet { width: 100%; }
}

@media (max-width: 26rem) {
  .result-head h1, .title-input { font-size: 1.2rem; }
  .facts div { flex-direction: column; gap: .15rem; }
  .facts dd { text-align: left; font-weight: 600; }
}

/* ---------------------------------------------------------------
   TABLET PORTRAIT - the same stacked order, a larger map, and the
   measurements paired up because the stack is full width here.
---------------------------------------------------------------- */
@media (min-width: 46.001rem) and (max-width: 60rem) {
  body.has-result .wrap { max-width: 46rem; }
  .map-canvas { height: 52vh; min-height: 380px; }
  .metrics {
    display: grid; grid-template-columns: 1fr 1fr; column-gap: 1.5rem;
  }
}

/* ---------------------------------------------------------------
   TABLET LANDSCAPE and DESKTOP - one main column of map + description, one
   spanning sidebar beside it.

   `.result-side` becomes a real block again, which is what removes the shared
   row: the main column's two rows are sized by the map and the description,
   so they sit together with one .9rem gap and the description's edges line up
   with the map's.
---------------------------------------------------------------- */
@media (min-width: 60.001rem) {
  .result-side {
    display: block; grid-area: side; min-width: 0; align-self: start;
  }
  .result-primary {
    grid-template-areas: 'map side' 'narrative side';
    grid-template-rows: auto auto;
    align-items: start;
  }
  /* Inside a block wrapper the children are no longer grid items, so their
     areas are inert; only the spacing under the identity is wanted. */
  .side-body { margin-top: 1rem; }
}

@media (min-width: 60.001rem) and (max-width: 78rem) {
  body.has-result .wrap { max-width: 74rem; }
  .result-primary {
    grid-template-columns: minmax(0, 64fr) minmax(17rem, 36fr);
    column-gap: 1.4rem;
  }
  .map-canvas { height: 60vh; min-height: 400px; }
}

@media (min-width: 78.001rem) {
  body.has-result .wrap { max-width: 82rem; }
  .result-primary {
    grid-template-columns: minmax(0, 72fr) minmax(18rem, 28fr);
    column-gap: 1.6rem;
  }
  .map-canvas { height: 70vh; min-height: 520px; max-height: 780px; }
  .narrative p { font-size: .94rem; line-height: 1.62; }
  .secondary { max-width: 52rem; }
}

/* ---------------------------------------------------------------
   PRINT / SAVE PDF — the completed brief, on paper.

   THE EXPORT IS THIS STYLESHEET. No second renderer, no server-side PDF
   subsystem, no headless browser: the page already holds exactly the data the
   reader is looking at, and the only thing a document needs is for the
   interface around it to step aside. So this block removes the chrome and
   sets type for paper, and the browser's own Save as PDF does the rest.

   WHAT IS DELIBERATELY REMOVED: advertising, every button, the uploader, the
   analysis state, the landing example experience, and the browser-driven map
   canvas. WHAT STAYS: the mark, the title and purpose, the key measurements,
   the description, Route Details, Along the Way, Setting, and Method &
   limitations including the safety disclaimer. app.js opens the collapsed
   disclosures before printing so none of that is lost to a closed <details>.

   THE MAP IS NOT PRINTED. It is a live Google Maps canvas of tiles fetched by
   the browser; whether it rasterises into a print job varies by browser and
   by whether background graphics are enabled, and the alternatives - a static
   map URL carrying the API key, or a headless browser to screenshot it - are
   respectively a key disclosure and an infrastructure dependency. A brief
   that is right every time beats a picture that is there sometimes.
---------------------------------------------------------------- */
@media print {
  /* Paper is white and ink is black; the screen's greys read as mud. */
  body {
    background: #fff; color: #000;
    font-size: 10.5pt; line-height: 1.45;
  }
  .wrap, body.has-result .wrap { max-width: none; padding: 0; }

  /* The interface. */
  .masthead, .foot, .ad-slot, .side-actions, .example-actions, .back-link,
  .no-print, .map-note, button, .result-map, .copied {
    display: none !important;
  }

  /* The brief's own mark, in place of the masthead. */
  .print-only { display: block; }
  .print-brand {
    border-bottom: 1pt solid #000; padding-bottom: .35rem; margin-bottom: .6rem;
  }
  .print-wordmark { font-weight: 700; letter-spacing: .01em; }
  .print-tagline { color: #444; font-size: 8.5pt; margin-left: .6rem; }

  /* One column: the sidebar and the narrative are a single reading order. */
  .result-primary { display: block !important; }
  .result-side { display: block !important; }
  .result-head h1 { font-size: 15pt; margin: .2rem 0 .15rem; }
  /* Print shows the title, never the editor or its button. */
  .title-input, .title-edit { display: none !important; }
  .eyebrow { font-size: 8pt; }
  .subline { font-size: 9pt; color: #333; }
  .side-body { margin-top: .5rem; }
  .side-body h2, .facts dt, .metric dt { color: #333; }

  .metrics { display: block; }
  .metric { padding: .18rem 0; border-bottom: .5pt solid #999; }
  .metric-value { font-size: 10.5pt; }
  .metric-detail { color: #333; }

  .narrative {
    border-left: 2pt solid #000; padding-left: .5rem; margin-top: .6rem;
  }
  .narrative p { font-size: 10.5pt; line-height: 1.5; }

  /* Disclosures print as sections. app.js opens them; this makes them read
     like headings rather than like controls. */
  .secondary { margin-top: .8rem; border-top: .5pt solid #999; }
  details { margin-top: .5rem; break-inside: avoid; }
  summary {
    font-size: 8.5pt; color: #000; letter-spacing: .08em;
    padding: .3rem 0 .1rem;
  }
  summary::after, details[open] summary::after { content: ''; }
  .field-note, .note { color: #333; font-size: 8.5pt; }
  .note { border: .5pt solid #999; background: none; }

  a { color: #000; text-decoration: none; }
}

/* ---------------------------------------------------------------
   ELEVATION PROFILE

   A thumbnail in the rail, and the readable chart in a <dialog> over the page.
   The chart is deliberately NOT in the rail: at minmax(17rem, 36fr) the column
   is ~272-340px, and a mile scale and an elevation scale cannot both be legible
   in that width. */

/* THUMBNAIL: A SPARKLINE, NOT A CHART.
   The ratio is pinned rather than inherited. It was `height: 3.5rem` against a
   fluid rail, so the rendered shape drifted between about 4.3:1 and 5.4:1 with
   the viewport and was never a decision anyone made. 4:1 is fixed, inside the
   3.5:1-4:1 target, and identical at every width. */
.profile-block { margin-top: 1.1rem; border-top: 1px solid var(--rule);
                 padding-top: .85rem; }
.profile-card {
  display: block; width: 100%; cursor: pointer; padding: 0; margin: 0;
  background: none; border: 0; text-align: left; color: inherit;
  font: inherit; border-radius: 4px;
}
.profile-card-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: .5rem;
}
.profile-card-label {
  font-size: .62rem; letter-spacing: .09em; text-transform: uppercase;
  color: var(--ink-faint); line-height: 1.4;
}
/* The affordance in words, so it survives a screen reader, a high-contrast
   mode and the print stylesheet. */
.profile-card-hint { font-size: .68rem; color: var(--accent); font-weight: 600; }
.profile-spark {
  display: block; width: 100%; aspect-ratio: 4 / 1; height: auto;
  margin: .35rem 0 .2rem;
}
.profile-card:hover .profile-spark-line,
.profile-card:focus-visible .profile-spark-line { stroke: var(--accent); }
.profile-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.profile-card-ends {
  display: flex; justify-content: space-between;
  font-size: .68rem; color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}

/* THE DIALOG. Sized from the viewport, capped so it never fills a huge screen
   edge to edge, and never wider than the viewport on a phone. */
.profile-dialog {
  width: min(60rem, calc(100vw - 2rem));
  max-width: calc(100vw - 2rem);
  max-height: calc(100vh - 3rem);
  padding: 1.1rem 1.25rem 1.25rem;
  border: 1px solid var(--rule); border-radius: 8px;
  background: #ffffff; color: var(--ink);
  box-shadow: 0 18px 48px rgba(20, 24, 29, .22);
  overflow: auto;
}
.profile-dialog::backdrop { background: rgba(20, 24, 29, .45); }
.profile-dialog-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; margin-bottom: .5rem;
}
.profile-dialog-head h2 {
  margin: 0; font-size: .78rem; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-faint); font-weight: 600;
}
/* 44px minimum, so it is comfortably tappable on a phone. */
.profile-dialog-close {
  min-height: 44px; min-width: 5rem; padding: .35rem .9rem;
  font-size: .8rem; cursor: pointer;
  border: 1px solid var(--rule); border-radius: 4px;
  background: #ffffff; color: var(--ink);
}
.profile-dialog-close:hover { border-color: var(--accent); color: var(--accent); }
/* THE CHART SCALES INSIDE ITS BOX AND ASSERTS NO PIXEL WIDTH, which is what
   keeps an expanded chart from pushing the page sideways. ~400px tall at the
   60rem cap, inside the 350-450px target. */
.profile-chart {
  display: block; width: 100%; max-width: 100%; height: auto;
  aspect-ratio: 900 / 400;
}
.profile-readout {
  margin: .5rem 0 0; min-height: 1.3em; font-size: .82rem;
  color: var(--ink-soft); font-variant-numeric: tabular-nums;
}
.profile-source { margin: .3rem 0 0; font-size: .68rem; color: var(--ink-faint); }
.profile-axis-label { font-size: 12px; fill: var(--ink-faint); }
.profile-marker-label { font-size: 12px; fill: var(--ink-soft); font-weight: 600; }
.profile-grid { stroke: var(--rule); stroke-width: 1; }

/* MOBILE: nearly the full width, and a taller chart box so the scales stay
   legible when the figure is narrow. */
@media (max-width: 40rem) {
  .profile-dialog {
    width: calc(100vw - 1rem); max-width: calc(100vw - 1rem);
    padding: .9rem .75rem 1rem;
  }
  .profile-chart { aspect-ratio: 4 / 3; }
  .profile-axis-label { font-size: 15px; }
  .profile-marker-label { font-size: 15px; }
}

@media print {
  .profile-card-hint { display: none; }
  .profile-dialog { display: none; }
}

/* ---------------------------------------------------------------
   ROUTE PROGRESSION
   Mile, then the change, in route order. A numbered list because the order IS
   the information - this is a sequence, not a set of facts. */
/* ---------------------------------------------------------------
   ROUTE PROGRESSION
   Mile, then the change, in route order. A numbered list because the order IS
   the information - this is a sequence, not a set of facts. */
.progression { margin-top: 0; }
.progression-list {
  margin: .2rem 0 .5rem; padding: 0; list-style: none;
  border-top: 1px solid var(--rule);
}

/* TWO COLUMNS, WITH A GAP THAT IS A DECISION.
   The mile column has a FLOOR wide enough for the longest value the projection
   can produce - "Mile 105.4" - and grows for a longer one rather than clipping,
   so the event text starts on the same x for every row.

   `minmax(0, 1fr)` on the text column is what keeps a long unbroken name from
   widening the grid past its container, which is how a list like this pushes a
   phone page sideways. */
.progression-item {
  display: grid;
  grid-template-columns: minmax(6.5rem, max-content) minmax(0, 1fr);
  column-gap: 1.25rem;
  row-gap: .15rem;
  padding: .6rem 0;
  border-bottom: 1px solid var(--rule);
}
.progression-item:last-child { border-bottom: 0; }
.progression-mile {
  font-size: .68rem; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ink-faint); font-variant-numeric: tabular-nums;
  padding-top: .18rem; white-space: nowrap;
}
.progression-text {
  font-size: .92rem; line-height: 1.5;
  min-width: 0; overflow-wrap: anywhere;
}

/* GENUINELY NARROW ONLY. Two columns are kept wherever they fit, because the
   aligned mile is the point. Below this the mile sits above its text with a
   REAL row gap - the previous rule stacked them at .05rem, which is what made
   them read as one run-together line. */
@media (max-width: 26rem) {
  .progression-item {
    grid-template-columns: minmax(0, 1fr);
    row-gap: .3rem;
    padding: .7rem 0;
  }
}

@media print {
  .progression { break-inside: avoid; }
  .progression-item { padding: .25rem 0; border-bottom: .5pt solid #999; }
}
