/* ============================================================================
   Wanderlight — one stylesheet, shared by site/index.html and every page that
   pipeline/build-site.mjs generates.

   This file replaced the old arrangement, where the tokens lived twice: once
   inline in index.html and once in pipeline/site-assets/page.css, "kept in step
   by hand". They drifted. Now there is one copy and the drift is impossible.

   Design intent: unhurried. Motion here is physical rather than decorative —
   things settle into place at the speed a heavy object would. Nothing bounces,
   nothing spins, nothing slides in from off-screen to get attention. Every
   animated rule is inside a `prefers-reduced-motion: no-preference` query, so
   the reduced-motion experience is the static one by construction rather than
   by remembering to switch things off.
   ========================================================================= */

/* ---------------------------------------------------------------- fonts --
   Self-hosted, deliberately. The site promises no tracking; a Google Fonts
   request would log every visitor's IP with a third party and quietly make
   that promise less true. Both files are variable, so one download covers
   every weight we use. */

@font-face {
  font-family: 'Fraunces';
  src: url('/fonts/fraunces-var-latin.woff2') format('woff2');
  font-weight: 100 900;
  font-display: swap;
  font-style: normal;
}
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-var-latin.woff2') format('woff2');
  font-weight: 100 900;
  font-display: swap;
  font-style: normal;
}

/* --------------------------------------------------------------- tokens --
   Day and evening. The values come from growth/brand.md: terracotta is the
   one personality colour and carries "free"; gold is reserved for paid and
   unlock moments, which is why it appears here only as a hairline, a rule,
   and a link — never as a button. */

:root {
  color-scheme: light dark;

  --paper:      #efe7d6;
  --paper-warm: #f7f1e4;
  --paper-deep: #e6dcc7;
  --ink:        #1a1611;
  --ink-soft:   #5c5346;
  --ink-faint:  #6e6354;   /* was #8a7f6d — 3.20:1, failed AA */
  --terracotta: #b0563a;
  --terracotta-lift: #c2624272;
  --gold:       #9a6f0a;
  /* The solid fill behind cream text. Deliberately NOT --terracotta, which is
     lightened in evening mode for legibility *as text on dark paper* — that
     lighter value drops cream-on-terracotta to 3.44:1 and fails AA. This one
     stays put in both modes: 4.52:1 against the cream, and 3.63:1 against dark
     paper, which clears the 3:1 bar for a large block. */
  --cta-fill:   #b0563a;
  --cta-ink:    #faf4e8;
  /* Terracotta as TEXT on paper is 4.03:1 — under the 4.5 needed for small
     text. The brand value stays for fills, rules and the arch, where only 3:1
     applies; anything set in words uses this darker cut instead. Checked
     against the darkest ground it ever sits on, --paper-deep behind the hub
     header, where it is 4.90:1; on plain paper it is 5.43:1. */
  --terracotta-text: #94452c;
  /* The solid terracotta header behind the landmark titles. Darker than the
     brand terracotta for the same reason --cta-fill is: cream on #b0563a is
     4.25:1 even at full opacity, so no amount of tuning the text rescues it.
     Cream on this is 4.78:1. Fixed across both themes. */
  --head-fill:  #a54f34;
  --head-ink:   #f6ece0;
  --rule:       rgba(26, 22, 17, .14);
  --rule-soft:  rgba(26, 22, 17, .07);
  --shadow-sm:  0 1px 2px rgba(26, 22, 17, .06);
  --shadow-md:  0 1px 2px rgba(26, 22, 17, .06), 0 8px 24px rgba(26, 22, 17, .07);
  --shadow-lg:  0 2px 4px rgba(26, 22, 17, .05), 0 18px 50px rgba(26, 22, 17, .12);

  /* Motion. One easing curve for almost everything: slow to start, long tail.
     It reads as weight. --ease-out-soft is the "settling" curve. */
  --ease-out-soft: cubic-bezier(.16, .84, .24, 1);
  --ease-in-out-soft: cubic-bezier(.62, .01, .21, 1);
  --dur-fast: 180ms;
  --dur-mid: 420ms;
  --dur-slow: 900ms;

  --measure: 38rem;      /* prose line length */
  --gutter: 1.25rem;
  --radius: 14px;
  --radius-lg: 22px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --paper:      #1a1611;
    --paper-warm: #221d17;
    --paper-deep: #12100c;
    --ink:        #efe7d6;
    --ink-soft:   #a99e8c;
    --ink-faint:  #8d8374;   /* was #7d7365 — 3.87:1, failed AA */
    --terracotta: #c56b4a;
    --terracotta-lift: #d17d5c72;
    --gold:       #c9a45e;
    --cta-fill:   #b0563a;   /* see note above: not lightened, on purpose */
    --cta-ink:    #faf4e8;
    --terracotta-text: #cc7454;   /* 4.93:1 on --paper-warm, the .listen card */
    --rule:       rgba(239, 231, 214, .16);
    --rule-soft:  rgba(239, 231, 214, .08);
    --shadow-sm:  0 1px 2px rgba(0, 0, 0, .3);
    --shadow-md:  0 1px 2px rgba(0, 0, 0, .3), 0 8px 24px rgba(0, 0, 0, .35);
    --shadow-lg:  0 2px 6px rgba(0, 0, 0, .35), 0 18px 50px rgba(0, 0, 0, .5);
  }
}

/* ----------------------------------------------------------------- base -- */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  font-size: 1.02rem;
  line-height: 1.68;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
  font-feature-settings: 'kern' 1, 'liga' 1;
}

img { max-width: 100%; display: block; }

a { color: var(--gold); text-underline-offset: .18em; }

::selection { background: var(--terracotta); color: #faf4e8; }

:focus-visible {
  outline: 2px solid var(--terracotta);
  outline-offset: 3px;
  border-radius: 3px;
}

.wrap {
  width: 100%;
  max-width: 68rem;
  margin: 0 auto;
  padding: 0 var(--gutter);
}
@media (min-width: 40rem) { :root { --gutter: 2rem; } }
@media (min-width: 64rem) { :root { --gutter: 2.5rem; } }

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--cta-fill); color: var(--cta-ink);
  padding: .7rem 1.1rem; border-radius: 0 0 var(--radius) 0;
  text-decoration: none; font-weight: 500;
}
.skip-link:focus { left: 0; }

/* ------------------------------------------------------------ typography --
   Fraunces for display, with optical sizing wound up: at large sizes its
   high-contrast cut is the point. Inter for everything read in quantity. */

h1, h2, h3, .display {
  font-family: 'Fraunces', Georgia, 'Times New Roman', serif;
  font-variation-settings: 'opsz' 96, 'SOFT' 0, 'WONK' 0;
  font-weight: 600;
  letter-spacing: -.021em;
  line-height: 1.08;
  text-wrap: balance;
}

h1 {
  font-size: clamp(2.5rem, 8.5vw, 5.2rem);
  margin: 0 0 1.1rem;
}
h2 {
  font-size: clamp(1.75rem, 4.6vw, 2.9rem);
  margin: 0 0 .75rem;
  line-height: 1.12;
}
h3 {
  font-size: clamp(1.12rem, 2.2vw, 1.35rem);
  font-variation-settings: 'opsz' 40;
  letter-spacing: -.012em;
  margin: 0 0 .4rem;
  line-height: 1.24;
}

p { margin: 0 0 1.15rem; }
p:last-child { margin-bottom: 0; }

.lede {
  font-size: clamp(1.1rem, 2.6vw, 1.38rem);
  line-height: 1.55;
  color: var(--ink-soft);
  max-width: 34rem;
  margin: 0 0 2.25rem;
  text-wrap: pretty;
}

.sub {
  color: var(--ink-soft);
  max-width: 36rem;
  margin: 0 0 2.75rem;
  font-size: 1.04rem;
  text-wrap: pretty;
}

/* The small caps label that sits above a heading. Letterspaced, never shouty. */
.eyebrow {
  display: block;
  font-family: 'Inter', sans-serif;
  font-size: .76rem;
  font-weight: 600;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--terracotta-text);
  margin: 0 0 1rem;
}

/* --------------------------------------------------------------- header -- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: color-mix(in srgb, var(--paper) 82%, transparent);
  backdrop-filter: saturate(1.5) blur(14px);
  -webkit-backdrop-filter: saturate(1.5) blur(14px);
  border-bottom: 1px solid transparent;
  transition: border-color var(--dur-mid) var(--ease-out-soft),
              background var(--dur-mid) var(--ease-out-soft);
}
/* .is-stuck is set by JS once the page has scrolled past the hero lip. The
   header starts borderless over the image and earns its rule on scroll. */
.site-header.is-stuck {
  border-bottom-color: var(--rule);
  background: color-mix(in srgb, var(--paper) 93%, transparent);
}

/* NB: .bar and .hero-inner sit on the same element as .wrap, so a `padding`
   shorthand here silently overrides .wrap's horizontal gutter. Both must
   restate var(--gutter) rather than 0, or the page goes edge-to-edge on a
   phone while looking fine on a desktop, where the max-width hides it. */
.bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: .95rem var(--gutter);
}

.wordmark {
  font-family: 'Fraunces', Georgia, serif;
  font-variation-settings: 'opsz' 40;
  font-weight: 600;
  font-size: 1.3rem;
  letter-spacing: -.01em;
  line-height: 1;
  white-space: nowrap;
}
.wordmark a { color: inherit; text-decoration: none; }
.wordmark span { color: var(--terracotta-text); }

.site-nav {
  display: flex;
  align-items: center;
  gap: clamp(.9rem, 2.4vw, 1.75rem);
  font-size: .93rem;
}
.site-nav a {
  color: var(--ink-soft);
  text-decoration: none;
  position: relative;
  padding: .25rem 0;
  white-space: nowrap;
}
/* The underline grows from the left rather than fading in. Cheap, and it
   reads as deliberate. */
.site-nav a::after {
  content: '';
  position: absolute;
  left: 0; bottom: 0;
  height: 1px; width: 100%;
  background: var(--terracotta);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur-mid) var(--ease-out-soft);
}
.site-nav a:hover { color: var(--ink); }
.site-nav a:hover::after,
.site-nav a:focus-visible::after { transform: scaleX(1); }

/* On a narrow screen the two text links drop out and only the app link, which
   is the one that converts, survives. No hamburger: four links do not earn a
   drawer, and a drawer is one more thing to get wrong on a phone. */
.site-nav .nav-optional { display: none; }
@media (min-width: 46rem) {
  .site-nav .nav-optional { display: inline-block; }
}

.nav-cta {
  color: var(--terracotta-text) !important;
  font-weight: 500;
}

/* Scroll progress: a terracotta hairline across the very top. On an article
   this is genuinely useful — these pages are long. */
.progress {
  position: fixed;
  top: 0; left: 0;
  height: 2px;
  width: 100%;
  transform: scaleX(0);
  transform-origin: left;
  background: linear-gradient(90deg, var(--terracotta), var(--gold));
  z-index: 60;
  pointer-events: none;
}

/* ----------------------------------------------------------------- hero --
   The homepage hero. The painting is full-bleed and the type sits on it, so
   everything here is about keeping the text legible over a busy image without
   dimming the painting into mud. Two scrims: a soft vertical one for the type,
   and a warm vignette to stop the edges glowing. */

.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  isolation: isolate;
}

.hero-media {
  position: absolute;
  inset: -8% 0 0 0;      /* bleed, so parallax never exposes an edge */
  z-index: -2;
}

.hero-frame {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 1.1s var(--ease-in-out-soft);
}
.hero-frame.is-active { opacity: 1; }

.hero-frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 42%;
}

/* Legibility over a busy painting is the whole problem with a hero like this.
   Dark ink on a bright sunset fails contrast badly in the middle of the image,
   so the lower half of the frame is brought almost all the way back to paper
   and the painting is left to read in the top half, where no text sits. The
   second gradient is horizontal: it keeps the text column grounded on the left
   while the right side of the painting stays open. */
.hero-scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(to top,
      color-mix(in srgb, var(--paper) 99%, transparent) 0%,
      color-mix(in srgb, var(--paper) 97%, transparent) 26%,
      color-mix(in srgb, var(--paper) 86%, transparent) 44%,
      color-mix(in srgb, var(--paper) 46%, transparent) 66%,
      color-mix(in srgb, var(--paper) 8%, transparent) 88%,
      transparent 100%),
    linear-gradient(to right,
      color-mix(in srgb, var(--paper) 72%, transparent) 0%,
      color-mix(in srgb, var(--paper) 30%, transparent) 38%,
      transparent 68%),
    radial-gradient(130% 85% at 50% 22%, transparent 48%, rgba(40, 26, 16, .26) 100%);
}

.hero-inner {
  position: relative;
  padding: 7rem var(--gutter) 3rem;
  width: 100%;
}

.hero h1 { max-width: 15ch; }

.hero .lede { max-width: 32rem; }

/* The city switcher under the hero. Tabs cross-fade the painting behind. */
.hero-cities {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 4vw, 2.25rem);
  margin-top: 2.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--rule);
  flex-wrap: wrap;
}
.hero-city {
  appearance: none;
  background: none;
  border: 0;
  padding: .3rem 0;
  cursor: pointer;
  font-family: 'Fraunces', Georgia, serif;
  font-variation-settings: 'opsz' 40;
  font-size: clamp(1rem, 2.6vw, 1.22rem);
  font-weight: 600;
  color: var(--ink-faint);
  letter-spacing: -.01em;
  position: relative;
  transition: color var(--dur-mid) var(--ease-out-soft);
}
.hero-city::after {
  content: '';
  position: absolute;
  left: 0; bottom: 0;
  height: 2px; width: 100%;
  background: var(--terracotta);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur-slow) var(--ease-out-soft);
}
.hero-city[aria-selected='true'] { color: var(--ink); }
.hero-city[aria-selected='true']::after { transform: scaleX(1); }
.hero-city:hover { color: var(--ink-soft); }

.hero-city .n {
  display: block;
  font-family: 'Inter', sans-serif;
  font-size: .72rem;
  font-weight: 500;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-top: .2rem;
}

/* The scroll hint. Fades away the moment you scroll — it has done its job. */
.scroll-hint {
  position: absolute;
  left: 50%;
  bottom: 1.4rem;
  transform: translateX(-50%);
  font-size: .72rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-faint);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .5rem;
  transition: opacity var(--dur-mid) var(--ease-out-soft);
  pointer-events: none;
}
.scroll-hint .line {
  width: 1px; height: 2rem;
  background: linear-gradient(to bottom, var(--ink-faint), transparent);
}
body.has-scrolled .scroll-hint { opacity: 0; }

@media (min-width: 46rem) {
  .hero-inner { padding: 9rem var(--gutter) 4.5rem; }
}

/* ------------------------------------------------------------------ cta -- */

.cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .6rem;
  background: var(--cta-fill);
  color: var(--cta-ink);
  text-decoration: none;
  border-radius: 100px;
  padding: .95rem 1.75rem;
  font-size: 1.02rem;
  font-weight: 500;
  letter-spacing: -.005em;
  box-shadow: var(--shadow-md);
  position: relative;
  overflow: hidden;
  transition: transform var(--dur-mid) var(--ease-out-soft),
              box-shadow var(--dur-mid) var(--ease-out-soft);
}
.cta svg { width: 1.15rem; height: 1.15rem; fill: currentColor; flex: none; }
/* A slow sheen that crosses the button on hover. One pass, no loop — a looping
   shimmer on a static page is a distraction, not a delight. */
.cta::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(105deg, transparent 35%, rgba(255, 255, 255, .22) 50%, transparent 65%);
  transform: translateX(-120%);
  transition: transform 0s;
}
.cta:hover { transform: translateY(-2px); box-shadow: var(--shadow-lg); }
.cta:hover::before { transform: translateX(120%); transition: transform 780ms var(--ease-in-out-soft); }
.cta:active { transform: translateY(0); }

.cta-note {
  display: block;
  margin-top: .95rem;
  color: var(--ink-soft);
  font-size: .9rem;
}
.hero .cta-note { color: var(--ink-soft); }

/* -------------------------------------------------------------- sections -- */

.band {
  padding: clamp(4rem, 11vw, 8rem) 0;
  border-top: 1px solid var(--rule-soft);
  position: relative;
}
.band:first-of-type { border-top: 0; }
.band--warm { background: var(--paper-warm); }

/* ---------------------------------------------------------------- features --
   A numbered list rather than a card grid. Cards imply equivalence and
   invite decoration; a numbered list reads as an argument being made. */

.features {
  display: grid;
  gap: clamp(2rem, 4vw, 3rem);
  grid-template-columns: 1fr;
  counter-reset: feat;
}
@media (min-width: 38rem) { .features { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 64rem) { .features--4 { grid-template-columns: repeat(4, 1fr); } }
@media (min-width: 64rem) { .features--3 { grid-template-columns: repeat(3, 1fr); } }

.feature { counter-increment: feat; position: relative; padding-top: 1.5rem; }
.feature::before {
  content: '';
  position: absolute;
  top: 0; left: 0;
  width: 2.25rem; height: 2px;
  background: var(--terracotta);
  transform-origin: left;
  transform: scaleX(0);
  transition: transform var(--dur-slow) var(--ease-out-soft);
  transition-delay: calc(var(--i, 0) * 90ms + 120ms);
}
.is-revealed .feature::before { transform: scaleX(1); }
.feature p { color: var(--ink-soft); font-size: .98rem; margin: 0; }
.feature h3 a { color: inherit; text-decoration: none; }
.feature h3 a:hover { color: var(--terracotta-text); }

/* The generated city → landmark index on the homepage. Link-per-line, so it
   reads as a contents page rather than a wall of comma-separated blue. */
.linklist { line-height: 1.95; }
.linklist a {
  color: var(--ink-soft);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: color var(--dur-fast) var(--ease-out-soft),
              border-color var(--dur-fast) var(--ease-out-soft);
}
.linklist a:hover { color: var(--terracotta-text); border-bottom-color: var(--terracotta-lift); }

/* ---------------------------------------------------------------- cities -- */

.cities {
  display: grid;
  gap: clamp(1.25rem, 3vw, 2rem);
  grid-template-columns: 1fr;
}
@media (min-width: 46rem) { .cities { grid-template-columns: repeat(3, 1fr); } }

.city {
  display: block;
  text-decoration: none;
  color: inherit;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--paper-warm);
  border: 1px solid var(--rule);
  box-shadow: var(--shadow-sm);
  transition: transform var(--dur-mid) var(--ease-out-soft),
              box-shadow var(--dur-mid) var(--ease-out-soft),
              border-color var(--dur-mid) var(--ease-out-soft);
}
/* These two are <span>s, because the card is an <a> wrapping the whole thing.
   They must be made block-level explicitly: aspect-ratio is ignored on an
   inline box, which silently gives the figure its intrinsic height instead of
   the 4:3 crop and makes every card about twice as tall as intended. */
.city-figure { display: block; position: relative; overflow: hidden; aspect-ratio: 4 / 3; }
.city-body { display: block; }
.city-count { display: block; }
.city-figure img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform 1.4s var(--ease-out-soft);
}
.city:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); border-color: var(--terracotta-lift); }
.city:hover .city-figure img { transform: scale(1.055); }

.city-body { padding: 1.3rem 1.4rem 1.5rem; }
.city-count {
  font-size: .74rem;
  letter-spacing: .11em;
  text-transform: uppercase;
  color: var(--terracotta-text);
  font-weight: 600;
}
.city h3 { margin: .45rem 0 .5rem; font-size: 1.5rem; font-variation-settings: 'opsz' 60; }
.city p { color: var(--ink-soft); font-size: .95rem; margin: 0; }
.city-go {
  display: inline-flex; align-items: center; gap: .4rem;
  margin-top: .95rem; color: var(--terracotta-text);
  font-size: .9rem; font-weight: 500;
}
.city-go svg { width: .85rem; height: .85rem; fill: currentColor; transition: transform var(--dur-mid) var(--ease-out-soft); }
.city:hover .city-go svg { transform: translateX(4px); }

/* ------------------------------------------------------------- the arch --
   The brand mark, used as a quiet piece of furniture rather than a logo. It
   sits behind the landmark page header and drifts a little on scroll. */

.arch {
  position: absolute;
  right: clamp(-2rem, -2vw, 0rem);
  bottom: -1px;
  width: clamp(7rem, 20vw, 13rem);
  aspect-ratio: 2 / 3;
  border-radius: 999px 999px 0 0;
  background: currentColor;
  opacity: .13;
  pointer-events: none;
}

/* ------------------------------------------------------- landmark header --
   Terracotta ground, Fraunces name, the arch. This is what stands in for
   photography we do not have and will not fake. */

.page-head {
  position: relative;
  overflow: hidden;
  background: var(--head-fill);
  color: var(--head-ink);
  isolation: isolate;
}
.page-head .wrap { position: relative; z-index: 1; }
.page-head-inner { padding: clamp(3.5rem, 9vw, 6.5rem) 0 clamp(2.5rem, 6vw, 4rem); max-width: 46rem; }
.page-head .eyebrow { color: var(--head-ink); }
.page-head h1 { color: #fbf4ea; margin-bottom: .9rem; }
.page-head .page-standfirst {
  color: var(--head-ink);
  font-size: clamp(1.02rem, 2.3vw, 1.2rem);
  max-width: 34rem;
  margin: 0;
  text-wrap: pretty;
}
.page-head .arch { color: #fbf4ea; opacity: .11; }

.crumbs {
  font-size: .84rem;
  color: var(--head-ink);
  padding: 1.25rem 0 0;
  margin: 0;
}
.crumbs a { color: inherit; text-decoration: none; }
.crumbs a:hover { color: #fff; text-decoration: underline; }
.crumbs .sep { opacity: .45; margin: 0 .4rem; }

/* ------------------------------------------------------------- article -- */

.article {
  max-width: var(--measure);
  padding: clamp(2.75rem, 7vw, 4.5rem) 0 1rem;
  font-size: 1.07rem;
}
.article > h2 {
  font-size: clamp(1.5rem, 3.4vw, 2rem);
  margin: 3.25rem 0 .9rem;
  font-variation-settings: 'opsz' 60;
}
.article > h2:first-child { margin-top: 0; }
.article > h3 {
  font-size: 1.12rem;
  margin: 2.25rem 0 .55rem;
  font-variation-settings: 'opsz' 30;
}
.article p { color: var(--ink); }
.article > ul, .article > ol { color: var(--ink); padding-left: 1.3rem; margin: 0 0 1.15rem; }
.article li { margin-bottom: .45rem; }
/* :not(.cta) matters. The install button sits inside .article (it is in the
   .listen block at the foot of every landmark and hub page), and `.article a`
   outranks `.cta` on specificity — so without this the button rendered its
   label in gold on terracotta at 1.10:1, which is effectively invisible. */
.article a:not(.cta) { color: var(--gold); }

/* A pulled-out first paragraph. Sets the tone before the reader commits. */
.article > p.standfirst {
  font-size: 1.22rem;
  line-height: 1.55;
  color: var(--ink-soft);
  border-left: 2px solid var(--terracotta);
  padding-left: 1.15rem;
  margin-bottom: 2rem;
}

.article blockquote {
  margin: 2rem 0;
  padding: .25rem 0 .25rem 1.3rem;
  border-left: 2px solid var(--gold);
  color: var(--ink-soft);
  font-style: italic;
}

.article table {
  width: 100%;
  border-collapse: collapse;
  margin: 1.75rem 0;
  font-size: .95rem;
}
.article th, .article td {
  text-align: left;
  padding: .7rem .8rem;
  border-bottom: 1px solid var(--rule);
  vertical-align: top;
}
.article th { font-weight: 600; color: var(--ink); }
.article td { color: var(--ink-soft); }
/* Tables are the thing most likely to break a phone layout. Let them scroll
   rather than squeezing the page. */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* ------------------------------------------------------------- stop list --
   The city hub's table of landmarks. A list of rows, not a grid of cards:
   the runtime is the thing being compared, so it gets a column of its own. */

.stops { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--rule); }
.stop a {
  display: flex;
  align-items: baseline;
  gap: 1rem;
  padding: 1.15rem .35rem;
  border-bottom: 1px solid var(--rule);
  text-decoration: none;
  color: inherit;
  transition: background var(--dur-fast) var(--ease-out-soft),
              padding-left var(--dur-mid) var(--ease-out-soft);
}
.stop a:hover { background: var(--paper-warm); padding-left: .9rem; }
.stop .idx {
  font-size: .78rem;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
  flex: none;
  width: 1.75rem;
}
.stop .name {
  font-family: 'Fraunces', Georgia, serif;
  font-variation-settings: 'opsz' 40;
  font-weight: 600;
  font-size: 1.1rem;
  flex: 1 1 auto;
  letter-spacing: -.01em;
}
.stop .hook { display: block; font-family: 'Inter', sans-serif; font-weight: 400; font-size: .92rem; color: var(--ink-soft); margin-top: .2rem; letter-spacing: 0; }
.article .stops a,
.stops a { color: var(--ink); }
.article .stops .hook,
.stops .hook { color: var(--ink-soft); }
.article .stops .idx,
.stops .idx { color: var(--ink-faint); }
/* Third time this has bitten: `.article a` outranks a component's own colour
   for any link nested inside the prose column — it hit the install button and
   these landmark names. Components placed inside .article must state their
   colour, or inherit gold by accident. */

.stop .dur {
  font-size: .85rem;
  color: var(--terracotta-text);
  font-variant-numeric: tabular-nums;
  flex: none;
  white-space: nowrap;
}

/* ------------------------------------------------------------- siblings -- */

.siblings { padding: clamp(3rem, 7vw, 5rem) 0; border-top: 1px solid var(--rule); }
.sibling-grid { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; grid-template-columns: 1fr; }
@media (min-width: 46rem) { .sibling-grid { grid-template-columns: repeat(2, 1fr); } }
.sibling-grid a {
  display: block;
  padding: .95rem 1.1rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  text-decoration: none;
  color: inherit;
  transition: border-color var(--dur-mid) var(--ease-out-soft),
              transform var(--dur-mid) var(--ease-out-soft);
}
.sibling-grid a:hover { border-color: var(--terracotta-lift); transform: translateY(-2px); }
.sibling-grid .name { font-family: 'Fraunces', Georgia, serif; font-variation-settings: 'opsz' 30; font-weight: 600; display: block; }
.sibling-grid .meta { font-size: .85rem; color: var(--ink-soft); }

/* Rows for landmarks that have no page written yet. Same shape, no affordance:
   nothing here should look clickable when it is not. */
.stop .row { display: flex; align-items: baseline; gap: 1rem; padding: 1.15rem .35rem; border-bottom: 1px solid var(--rule); color: var(--ink-soft); }
.stop .row .name { color: var(--ink-soft); }

/* ----------------------------------------------- hub header with artwork --
   The city painting behind the hub title. Same scrim logic as the homepage
   hero, tuned darker because the type here sits higher up the image. */

.page-head--image { background: var(--paper-deep); color: var(--ink); }
.page-head-media { position: absolute; inset: 0; z-index: 0; }
.page-head-media img { width: 100%; height: 100%; object-fit: cover; object-position: center 45%; }
/* The hub title sits higher up its image than the homepage headline does, so
   this needs to be heavier than the hero scrim, not lighter. The eyebrow is
   the binding constraint: small, letterspaced, terracotta, and therefore the
   first thing to become unreadable over a bright sky. */
.page-head-scrim {
  position: absolute; inset: 0; z-index: 0;
  background:
    linear-gradient(to top,
      color-mix(in srgb, var(--paper) 99%, transparent) 0%,
      color-mix(in srgb, var(--paper) 96%, transparent) 42%,
      color-mix(in srgb, var(--paper) 78%, transparent) 66%,
      color-mix(in srgb, var(--paper) 38%, transparent) 86%,
      color-mix(in srgb, var(--paper) 10%, transparent) 100%),
    linear-gradient(to right,
      color-mix(in srgb, var(--paper) 66%, transparent) 0%,
      color-mix(in srgb, var(--paper) 24%, transparent) 42%,
      transparent 72%),
    radial-gradient(130% 90% at 50% 20%, transparent 45%, rgba(40, 26, 16, .24) 100%);
}
.page-head--image .page-head-inner { padding-top: clamp(5rem, 14vw, 9rem); }
.page-head--image h1 { color: var(--ink); }
.page-head--image .eyebrow { color: var(--terracotta-text); }
.page-head--image .crumbs { color: var(--ink-soft); }
.page-head--image .crumbs a:hover { color: var(--terracotta-text); }
.page-head--image .page-standfirst { color: var(--ink-soft); }

/* ---------------------------------------------------------- listen block --
   The in-page conversion moment at the foot of every landmark and hub page.
   Terracotta because it carries "free"; gold would mean the opposite here. */

.listen {
  margin: clamp(3rem, 7vw, 4.5rem) 0 0;
  padding: clamp(1.75rem, 5vw, 2.75rem);
  background: var(--paper-warm);
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  text-align: left;
}
.listen .runtime {
  display: block;
  font-size: .74rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--terracotta-text);
  font-weight: 600;
  margin-bottom: .85rem;
}
.listen h2 { font-size: clamp(1.4rem, 3.2vw, 1.85rem); margin: 0 0 .7rem; }
.listen p { color: var(--ink-soft); margin: 0 0 1.5rem; }

/* ---------------------------------------------------------- sources note -- */

.sources {
  margin: 2.5rem 0 0;
  padding: 1.25rem 1.4rem;
  border-left: 2px solid var(--gold);
  background: var(--paper-warm);
  border-radius: 0 var(--radius) var(--radius) 0;
  font-size: .93rem;
  color: var(--ink-soft);
}
.sources h2, .sources h3 { font-size: 1rem; margin: 0 0 .4rem; font-variation-settings: 'opsz' 20; }
.sources p { margin: 0; }

.note { color: var(--ink-soft); font-size: .93rem; }

/* Markdown tables from the dossiers. Same scroll guard as .table-scroll. */
.tablewrap { overflow-x: auto; -webkit-overflow-scrolling: touch; margin: 1.75rem 0; }
.tablewrap table { width: 100%; border-collapse: collapse; font-size: .95rem; min-width: 30rem; }
.tablewrap th, .tablewrap td { text-align: left; padding: .7rem .8rem; border-bottom: 1px solid var(--rule); vertical-align: top; }
.tablewrap th { font-weight: 600; }
.tablewrap td { color: var(--ink-soft); }

/* --------------------------------------------------------- privacy note -- */

.privacy-note {
  background: var(--paper-warm);
  border: 1px solid var(--rule);
  border-left: 3px solid var(--gold);
  border-radius: var(--radius);
  padding: clamp(1.4rem, 4vw, 2rem);
  max-width: 44rem;
}
.privacy-note p { color: var(--ink-soft); }
.privacy-note p:first-child { color: var(--ink); }

/* ------------------------------------------------------------- stat row -- */

.stats { display: flex; flex-wrap: wrap; gap: clamp(1.75rem, 6vw, 4rem); margin: 0; padding: 0; }
.stat { }
.stat dt { font-size: .74rem; letter-spacing: .12em; text-transform: uppercase; color: var(--ink-faint); font-weight: 600; }
.stat dd {
  margin: .3rem 0 0;
  font-family: 'Fraunces', Georgia, serif;
  font-variation-settings: 'opsz' 72;
  font-weight: 600;
  font-size: clamp(2rem, 5.5vw, 3rem);
  line-height: 1;
  letter-spacing: -.025em;
  font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------------- footer -- */

.site-footer {
  border-top: 1px solid var(--rule);
  padding: clamp(2.5rem, 6vw, 4rem) 0 3rem;
  color: var(--ink-soft);
  font-size: .9rem;
}
.footer-in { display: flex; justify-content: space-between; gap: 1.25rem; flex-wrap: wrap; }
.site-footer a { color: var(--ink-soft); text-decoration: none; }
.site-footer a:hover { color: var(--terracotta-text); text-decoration: underline; }

/* ========================================================================
   MOTION
   Everything below only applies when the visitor has not asked for less of
   it. The base state above is the finished state, so reduced motion needs no
   overrides — it simply never opts in.
   ===================================================================== */

@media (prefers-reduced-motion: no-preference) {

  /* Scroll reveal. Elements start slightly low and slightly transparent and
     settle. Staggered by --i where a group wants it. */
  .reveal {
    opacity: 0;
    transform: translate3d(0, 22px, 0);
    transition: opacity var(--dur-slow) var(--ease-out-soft),
                transform var(--dur-slow) var(--ease-out-soft);
    transition-delay: calc(var(--i, 0) * 80ms);
    will-change: opacity, transform;
  }
  .reveal.is-revealed { opacity: 1; transform: none; will-change: auto; }

  /* The hero headline arrives a beat before everything else, by word. */
  .hero h1 .w {
    display: inline-block;
    opacity: 0;
    transform: translate3d(0, .5em, 0);
    animation: word-rise 1s var(--ease-out-soft) forwards;
    animation-delay: calc(var(--w, 0) * 65ms + 120ms);
  }
  @keyframes word-rise { to { opacity: 1; transform: none; } }

  /* Ken Burns on the hero painting. 40s, 1.06 scale — slow enough that you
     notice it only if you look for it, which is the intent. */
  .hero-frame.is-active img { animation: drift 40s var(--ease-in-out-soft) infinite alternate; }
  @keyframes drift {
    from { transform: scale(1.02) translate3d(0, 0, 0); }
    to   { transform: scale(1.085) translate3d(-1.5%, -1.5%, 0); }
  }

  /* Parallax. --py is written by JS on scroll. */
  .hero-media { transform: translate3d(0, var(--py, 0px), 0); }

  .arch { transform: translate3d(0, var(--ay, 0px), 0); }
}

/* Anything JS might reveal must be visible if JS never runs. */
.no-js .reveal { opacity: 1; transform: none; }
.no-js .hero-frame { opacity: 1; }
.no-js .hero-frame:not(:first-child) { display: none; }

/* -------------------------------------------------------------- printing -- */

@media print {
  .site-header, .progress, .scroll-hint, .cta, .siblings, .hero-cities { display: none !important; }
  body { background: #fff; color: #000; }
  .page-head { background: none; color: #000; }
  .page-head h1, .page-head .page-standfirst { color: #000; }
  .article { max-width: none; }
}
