/* Golden Rift Adventures — site.css
   Palette: the locked seven (01-brand/palette.md). No eighth color.

   Type, reset 7 August 2026 at Laurence's direction (Framer research):
     MedievalSharp   the brand name, in capitals, and nowhere else
     Metrophobic     headings and body — most of the page
     Merienda        the two places the page speaks in its own voice
     Spline Sans Mono  record lines, unchanged since the first build

   MedievalSharp and Metrophobic ship one weight each (400). There is no bold
   to reach for, so hierarchy is carried by size, colour, and letter-spacing
   instead. Do not set font-weight above 400 on those two: the browser will
   synthesise a smeared fake bold. Archivo and Abril Fatface were retired in
   this pass. */

/* ---------- Fonts ---------- */

@font-face {
  font-family: "MedievalSharp";
  src: url("../fonts/medievalsharp-latin-400-normal.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Metrophobic";
  src: url("../fonts/metrophobic-latin-400-normal.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Merienda";
  src: url("../fonts/merienda-latin-400-normal.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Spline Sans Mono";
  src: url("../fonts/spline-sans-mono-latin-wght-normal.woff2") format("woff2");
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;
}

/* ---------- Tokens ---------- */

:root {
  --deep-slate: #1C2A23;
  --jungle-green: #4A5C3A;
  --golden-savannah: #D2A24C;
  --sandstone: #E8C98B;
  --terracotta: #8B5A2B;
  --deep-earth: #3B2716;
  --ivory: #F6F3EC;

  /* alpha variants of the seven, used for scrims, rules, and borders only */
  --slate-scrim-heavy: rgba(28, 42, 35, 0.60);
  --slate-scrim-light: rgba(28, 42, 35, 0.16);
  --ivory-60: rgba(246, 243, 236, 0.60);
  --ivory-18: rgba(246, 243, 236, 0.18);
  --jungle-25: rgba(74, 92, 58, 0.25);
  --jungle-45: rgba(74, 92, 58, 0.45);

  --font-brand: "MedievalSharp", "Iowan Old Style", Georgia, serif;
  --font-display: "Metrophobic", "Helvetica Neue", Arial, sans-serif;
  --font-body: "Metrophobic", "Helvetica Neue", Arial, sans-serif;
  --font-quote: "Merienda", "Iowan Old Style", Georgia, serif;
  --font-mono: "Spline Sans Mono", "Courier New", monospace;

  /* Corner radius scale. Softened 7 August 2026 at Laurence's request: the
     build opened at a documentary 2px, which read sharp next to the
     photography. These are the Apple-ish middle — rounded enough to feel
     handled, not so round it turns into a pill. */
  --r-sm: 6px;    /* inputs, small controls */
  --r-md: 10px;   /* buttons */
  --r-lg: 14px;   /* cards, panels, images */
  --r-xl: 20px;   /* the largest surfaces */

  --measure: 34rem;
  --section-pad: clamp(4.5rem, 10vw, 8rem);
  --gutter: clamp(1.25rem, 4vw, 3.5rem);
  --header-h: 4.25rem;
}

/* ---------- Reset-ish ---------- */

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

html {
  scroll-padding-top: calc(var(--header-h) + 1rem);
}

@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

body {
  margin: 0;
  background: var(--ivory);
  color: var(--deep-earth);
  font-family: var(--font-body);
  font-size: 1.0625rem;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

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

h1, h2, h3, p, ul, dl, figure {
  margin: 0;
}

/* Motion gating: hidden states exist only when JS has confirmed motion is welcome. */
html.has-motion [data-reveal],
html.has-motion [data-reveal-item],
html.has-motion [data-hero-seq] {
  visibility: hidden;
}

/* ---------- Shared atoms ---------- */

.section-inner {
  max-width: 76rem;
  margin: 0 auto;
  padding-left: var(--gutter);
  padding-right: var(--gutter);
}

.section-label {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--terracotta);
  margin-bottom: 1.1rem;
}

.section-label-dark { color: var(--sandstone); }

.section-heading {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(2rem, 4.1vw, 3rem);
  line-height: 1.08;
  letter-spacing: -0.02em;
  color: var(--deep-earth);
  max-width: 24ch;
}

.section-heading-dark { color: var(--ivory); }

.section-sub {
  max-width: var(--measure);
  margin-top: 1.4rem;
  color: var(--deep-earth);
}

.section-sub-dark { color: var(--ivory); opacity: 0.85; }

/* The signature: field-record caption lines */
.record-line {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  line-height: 1.5;
  color: var(--terracotta);
}

.record-line-dark { color: var(--sandstone); }

/* ---------- Photo captions ----------
   Rule of thumb, set 7 August 2026 at Laurence's direction: anything that
   describes a photograph speaks in Merienda. The mono record line stays for
   what is data rather than description — section labels, pending stamps, trip
   facts, form hints, footer meta. So a caption under a picture is Merienda; a
   status stamp beside it is still Spline Sans Mono.

   Merienda is a warmer, wider face than the mono it replaces here, so these
   drop the uppercasing and the wide tracking that suited a monospaced stamp. */

figure figcaption .record-line,
.hero-caption,
.record-band-caption {
  font-family: var(--font-quote);
  font-size: 0.95rem;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  line-height: 1.5;
}

/* The prose under a photograph, same voice as the caption above it. */
.record-note,
.plate-note {
  font-family: var(--font-quote);
}


/* Buttons */
.btn {
  display: inline-block;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 0.95rem;
  letter-spacing: 0.02em;
  line-height: 1;
  padding: 1em 1.7em;
  border-radius: var(--r-md);
  border: 1.5px solid transparent;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.25s ease, color 0.25s ease, border-color 0.25s ease;
}

.btn-gold {
  background: var(--golden-savannah);
  color: var(--deep-earth);
}

.btn-gold:hover { background: var(--sandstone); }

.btn-ghost-light {
  border-color: var(--ivory-60);
  color: var(--ivory);
  background: transparent;
}

.btn-ghost-light:hover {
  background: var(--ivory);
  color: var(--deep-earth);
  border-color: var(--ivory);
}

.btn-ghost-dark {
  border-color: var(--jungle-green);
  color: var(--jungle-green);
  background: transparent;
}

.btn-block {
  display: block;
  text-align: center;
  width: 100%;
}

.btn-disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

:focus-visible {
  outline: 2.5px solid var(--golden-savannah);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}

.skip-link {
  position: absolute;
  left: 1rem;
  top: -4rem;
  z-index: 100;
  background: var(--deep-slate);
  color: var(--ivory);
  padding: 0.8em 1.2em;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  text-decoration: none;
  transition: top 0.2s ease;
}

.skip-link:focus { top: 1rem; }

/* Film grain: static SVG noise. Masks WhatsApp compression, adds tooth. */
.grain {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.05;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='240' height='240' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 240px 240px;
}

/* Softened corners, 7 August 2026. Kept in one place so the radius scale can
   be retuned without hunting through the file. The full-bleed bands (hero,
   record band) stay square: they run edge to edge, so a radius there would
   read as a mistake rather than a choice. */

.park-media img,
.park-panel,
.record-item img,
.guide-media-main img,
.guide-media-secondary img {
  border-radius: var(--r-lg);
}

.park-panel { overflow: hidden; }

.skip-link { border-radius: var(--r-md); }

/* ---------- Header ---------- */

/* The existing header, kept, with the resizable behaviour folded in: at the
   top of the page it sits full width and transparent over the hero; once the
   reader scrolls it draws in to a narrower rounded bar with a blurred ground
   under it. Same markup, same links — it just changes shape. */

.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 50;
  padding: 0 var(--gutter);
  transition: padding 0.45s cubic-bezier(0.22, 0.61, 0.36, 1);
}

.site-header.is-scrolled { padding-top: 0.6rem; }

.header-inner {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  max-width: 82rem;
  margin: 0 auto;
  padding: 0.8rem var(--gutter);
  min-height: var(--header-h);
  border: 1px solid transparent;
  border-radius: var(--r-xl);
  transition: max-width 0.45s cubic-bezier(0.22, 0.61, 0.36, 1),
              min-height 0.45s cubic-bezier(0.22, 0.61, 0.36, 1),
              background-color 0.35s ease,
              border-color 0.35s ease,
              box-shadow 0.35s ease;
}

.site-header.is-scrolled .header-inner {
  max-width: 64rem;
  min-height: 3.5rem;
  background: rgba(28, 42, 35, 0.82);
  border-color: var(--ivory-18);
  box-shadow: 0 10px 30px rgba(28, 42, 35, 0.28);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
}

/* No backdrop-filter (older WebKit): fall back to near-opaque, because a
   translucent bar over photographs is unreadable without the blur. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .site-header.is-scrolled .header-inner { background: rgba(28, 42, 35, 0.96); }
}

.site-header.is-scrolled .wordmark-sub {
  opacity: 0;
  transition: opacity 0.3s ease;
}

.wordmark-sub { transition: opacity 0.3s ease; }

/* ---------- Mobile menu ---------- */

.nav-toggle {
  display: none;
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid var(--ivory-18);
  border-radius: var(--r-md);
  cursor: pointer;
  transition: border-color 0.2s ease;
}

.nav-toggle:hover { border-color: var(--golden-savannah); }

.nav-toggle-box {
  display: block;
  position: relative;
  width: 1.1rem;
  height: 0.6rem;
}

.nav-toggle-bar {
  position: absolute;
  left: 0;
  width: 100%;
  height: 1.5px;
  background: var(--ivory);
  transition: transform 0.3s cubic-bezier(0.22, 0.61, 0.36, 1), opacity 0.2s ease;
}

.nav-toggle-bar:first-child { top: 0; }
.nav-toggle-bar:last-child { bottom: 0; }

.nav-toggle[aria-expanded="true"] .nav-toggle-bar:first-child {
  transform: translateY(0.29rem) rotate(45deg);
}
.nav-toggle[aria-expanded="true"] .nav-toggle-bar:last-child {
  transform: translateY(-0.29rem) rotate(-45deg);
}

.mobile-nav {
  max-width: 82rem;
  margin: 0.5rem auto 0;
  padding: 0.6rem;
  border: 1px solid var(--ivory-18);
  border-radius: var(--r-lg);
  background: rgba(28, 42, 35, 0.96);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  box-shadow: 0 14px 40px rgba(28, 42, 35, 0.35);
}

.mobile-nav[hidden] { display: none; }

.mobile-nav-list {
  display: flex;
  flex-direction: column;
}

.mobile-nav-list a {
  padding: 0.85rem 1rem;
  color: var(--ivory);
  text-decoration: none;
  font-size: 1rem;
  border-radius: var(--r-md);
  transition: background-color 0.2s ease;
}

.mobile-nav-list a:hover { background: rgba(246, 243, 236, 0.07); }

/* Opening animation, skipped entirely under reduced motion. */
@media (prefers-reduced-motion: no-preference) {
  .mobile-nav { animation: nav-drop 0.32s cubic-bezier(0.22, 0.61, 0.36, 1); }
  @keyframes nav-drop {
    from { opacity: 0; transform: translateY(-0.5rem); }
    to   { opacity: 1; transform: translateY(0); }
  }
}

/* The lion mark sits beside the name rather than replacing it: the designer's
   full lockup already carries "Golden Rift" in its own serif, and stacking
   that under the site's wordmark would say the name twice. Only the mark is
   used here, and it takes its colour from whatever it sits on. */

.sprite {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

.wordmark {
  text-decoration: none;
  margin-right: auto;
  display: flex;
  align-items: center;
  gap: 0.65rem;
  color: var(--ivory);
}

.wordmark-mark {
  width: 2.35rem;
  height: 2.35rem;
  flex: 0 0 auto;
  color: var(--golden-savannah);
  transition: width 0.45s cubic-bezier(0.22, 0.61, 0.36, 1),
              height 0.45s cubic-bezier(0.22, 0.61, 0.36, 1);
}

.site-header.is-scrolled .wordmark-mark {
  width: 1.95rem;
  height: 1.95rem;
}

.wordmark-text { display: block; }

.footer-mark {
  width: 3.25rem;
  height: 3.25rem;
  color: var(--golden-savannah);
  margin-bottom: 0.9rem;
}

.wordmark-main {
  display: block;
  font-family: var(--font-brand);
  font-weight: 400;
  font-size: 1.5rem;
  line-height: 1;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ivory);
}

.wordmark-sub {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.58rem;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--sandstone);
  margin-top: 0.3rem;
}

.site-nav {
  display: flex;
  gap: 1.6rem;
}

.site-nav a {
  color: var(--ivory);
  text-decoration: none;
  font-size: 0.95rem;
  font-weight: 400;
  letter-spacing: 0.02em;
  padding: 0.4em 0;
  border-bottom: 1.5px solid transparent;
  transition: border-color 0.2s ease;
}

.site-nav a:hover { border-bottom-color: var(--golden-savannah); }

.btn-header { padding: 0.85em 1.4em; }

@media (max-width: 860px) {
  .site-nav { display: none; }
  /* The mark takes horizontal room the name used to have. */
  .wordmark-main { white-space: nowrap; font-size: 1.25rem; }
  .wordmark-sub { white-space: nowrap; letter-spacing: 0.16em; font-size: 0.54rem; }
  .wordmark-mark { width: 2rem; height: 2rem; }
  .site-header.is-scrolled .wordmark-mark { width: 1.75rem; height: 1.75rem; }
  .nav-toggle[data-nav-ready] { display: inline-flex; }
  .header-inner { gap: 0.75rem; }
  .btn-header { padding: 0.75em 1.05em; font-size: 0.88rem; }
  .site-header { padding-left: 0.9rem; padding-right: 0.9rem; }
  .header-inner { padding-left: 1rem; padding-right: 1rem; }
}

@media (min-width: 861px) {
  /* The panel is markup, not JS-built, so it must be held shut on desktop. */
  .mobile-nav { display: none; }
}

/* ---------- Hero ---------- */

.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  overflow: hidden;
}

.hero-media,
.hero-media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.hero-media img {
  object-fit: cover;
  object-position: 46% 58%;
}

.hero-scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    var(--slate-scrim-heavy) 0%,
    var(--slate-scrim-light) 38%,
    rgba(28, 42, 35, 0.30) 68%,
    var(--slate-scrim-heavy) 100%
  );
}

.hero-content {
  position: relative;
  z-index: 2;
  max-width: 76rem;
  width: 100%;
  margin: 0 auto;
  padding: 0 var(--gutter) clamp(5rem, 12vh, 8rem);
}

.eyebrow {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--sandstone);
  margin-bottom: 1.3rem;
}

.hero-title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(2.4rem, 7.1vw, 5.2rem);
  line-height: 1.02;
  letter-spacing: -0.03em;
  color: var(--ivory);
  /* Wide enough that the second written line stays one line. Metrophobic sets
     far wider than the Abril it replaced, and 12ch broke "be." onto its own. */
  max-width: 21ch;
}

.hero-line { display: block; }

.hero-sub {
  max-width: var(--measure);
  margin-top: 1.6rem;
  font-size: clamp(1.02rem, 1.4vw, 1.15rem);
  line-height: 1.65;
  color: var(--ivory);
}

.hero-ctas {
  display: flex;
  flex-wrap: wrap;
  gap: 0.9rem;
  margin-top: 2.1rem;
}

.hero-caption {
  position: absolute;
  right: var(--gutter);
  bottom: 1.4rem;
  z-index: 2;
  color: var(--ivory-60);
  text-align: right;
}

@media (max-width: 640px) {
  .hero-caption {
    position: relative;
    right: auto;
    bottom: auto;
    text-align: left;
    padding: 0 var(--gutter) 1.2rem;
  }
}

/* ---------- Provenance ---------- */

.provenance {
  position: relative;
  background: var(--deep-slate);
  padding: var(--section-pad) 0;
}

/* The one place the page speaks in its own voice rather than reporting, so
   it is the one place Merienda appears. Spending it twice would spend it. */
.provenance-text {
  font-family: var(--font-quote);
  font-size: clamp(1.1rem, 2.3vw, 1.6rem);
  font-weight: 400;
  line-height: 1.7;
  letter-spacing: 0;
  color: var(--ivory);
  max-width: 44rem;
}

.provenance-accent { color: var(--sandstone); }

/* ---------- Reach ---------- */

.reach {
  position: relative;
  background: var(--deep-slate);
  padding: var(--section-pad) 0;
  overflow: hidden;
}

.reach-layout {
  display: grid;
  grid-template-columns: minmax(0, 0.8fr) minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: center;
}

.reach-globe {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.reach-globe-container {
  position: relative;
  width: 100%;
  /* Sized up 7 August 2026 at Laurence's request. */
  max-width: 32rem;
  aspect-ratio: 1 / 1;
  margin: 0 auto;
  cursor: grab;
  /* Vertical drag on the globe must not scroll the page out from under it. */
  touch-action: none;
}

.reach-globe-container[data-dragging] { cursor: grabbing; }

/* Atmosphere: a soft halo sitting behind the sphere, not over it. */
.reach-globe-container::before {
  content: "";
  position: absolute;
  inset: -12%;
  border-radius: 50%;
  background: radial-gradient(
    circle at 50% 50%,
    rgba(74, 92, 58, 0.34) 42%,
    rgba(74, 92, 58, 0.14) 58%,
    rgba(74, 92, 58, 0) 70%
  );
  pointer-events: none;
}

.reach-globe-canvas,
.reach-globe-overlay {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/* The marker/arc layer is painted over the sphere, so it must never take
   the pointer: the drag handler lives on the WebGL canvas beneath it. */
.reach-globe-overlay { pointer-events: none; }

/* No WebGL, or the texture failed: drop the stage rather than leave a
   blank square, and let the text column carry the section on its own. */
.reach-globe-container[data-globe-failed] { display: none; }
.reach-globe-container[data-globe-failed] ~ .reach-globe-caption { display: none; }

.reach-globe-caption {
  margin-top: 1.2rem;
  text-align: center;
}

@media (max-width: 860px) {
  .reach-layout { grid-template-columns: 1fr; }
  .reach-globe { margin-top: 0.5rem; }
  .reach-globe-container { max-width: 23rem; }
}

/* ---------- Parks ---------- */

.parks {
  padding: var(--section-pad) 0;
}

.park-featured {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
  gap: clamp(1.5rem, 4vw, 4rem);
  align-items: end;
  margin-top: clamp(2.5rem, 6vw, 4.5rem);
}

.park-media .record-line { margin-top: 0.8rem; }

.park-name {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(2.1rem, 4.8vw, 3.9rem);
  line-height: 1.04;
  letter-spacing: -0.03em;
  color: var(--deep-earth);
}

.park-desc {
  margin-top: 1.2rem;
  max-width: 30rem;
}

.park-status {
  margin-top: 1.4rem;
  padding-top: 1rem;
  border-top: 1px solid var(--jungle-25);
}

.parks-pending {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(1rem, 2.5vw, 2rem);
  margin-top: clamp(2rem, 5vw, 3.5rem);
}

.park-panel {
  position: relative;
  background: var(--deep-slate);
  padding: clamp(1.8rem, 4vw, 3rem);
  border-top: 3px solid var(--golden-savannah);
  overflow: hidden;
}

.park-panel .park-name { color: var(--ivory); }

.park-panel .park-desc { color: var(--ivory); opacity: 0.85; }

.park-panel .park-status {
  color: var(--sandstone);
  border-top-color: var(--ivory-18);
}

.parks-note {
  margin-top: clamp(2rem, 5vw, 3rem);
  max-width: var(--measure);
  color: var(--jungle-green);
}

@media (max-width: 860px) {
  .park-featured { grid-template-columns: 1fr; align-items: start; }
  .parks-pending { grid-template-columns: 1fr; }
}

/* ---------- Parks: sticky scroll reveal ----------
   The plate column holds still while the three park steps scroll past it, and
   the plates cross-fade as each step takes over. Deliberately slow: the
   transition is 0.9s on a gentle curve, because the point of this pattern is
   grace, not responsiveness. A fast version of this reads as a glitch. */

.parks-sticky {
  display: grid;
  /* The plate carries the section, so it takes the larger share. The text
     column stays wide enough for the longest park name to break in two. */
  grid-template-columns: minmax(0, 0.68fr) minmax(0, 1.32fr);
  gap: clamp(1.5rem, 3.5vw, 3rem);
  align-items: start;
  margin-top: clamp(2.5rem, 6vw, 4.5rem);
}

/* Visual first in the source order would fight the reading order, so the
   column is placed rather than reordered. */
/* The column must span the full row height, otherwise it shrink-wraps the
   plate and position:sticky has no distance to travel — the frame scrolls
   away with the first step instead of holding. */
.parks-visual {
  grid-column: 2;
  grid-row: 1;
  align-self: stretch;
  height: 100%;
}

.parks-steps { grid-column: 1; grid-row: 1; }

/* The sticky box is a full-height column that holds the plate in the middle
   of the viewport. Sticking the plate itself to a fixed top offset pinned it
   to the top of the screen while the text it belonged to sat centred, which
   read as two unrelated columns. */
.parks-visual-frame {
  position: sticky;
  top: var(--header-h);
  height: calc(100svh - var(--header-h));
  display: flex;
  align-items: center;
  /* The steps are later siblings and would otherwise paint over the plate. */
  z-index: 2;
}

.parks-visual-stage {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 3;
  max-height: calc(100svh - var(--header-h) - 4rem);
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--deep-slate);
}

.park-plate {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
  transform: scale(1.02);
  transition: opacity 0.9s cubic-bezier(0.33, 0, 0.2, 1),
              transform 1.2s cubic-bezier(0.33, 0, 0.2, 1);
}

.park-plate[data-active] { opacity: 1; transform: scale(1); }

.park-plate picture,
.park-plate img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.park-plate figcaption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 2.5rem 1rem 0.9rem;
  color: var(--ivory);
  background: linear-gradient(to top, rgba(28, 42, 35, 0.88), rgba(28, 42, 35, 0));
}

.park-plate figcaption .record-line { color: var(--ivory); }

/* Stock plates say so on the plate. The provenance strip promises this label
   exists, so it must never be quietly dropped when a plate is swapped. */
.plate-stock {
  margin-top: 0.3rem;
  color: var(--sandstone) !important;
  opacity: 0.85;
}

.park-plate-panel {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.75rem;
  padding: clamp(1.5rem, 4vw, 2.5rem);
  border-top: 3px solid var(--golden-savannah);
}

.park-plate-note {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.7rem, 3.1vw, 2.5rem);
  line-height: 1.06;
  letter-spacing: -0.02em;
  color: var(--ivory);
}

/* Steps: the inactive ones stay legible but recede, so the active park is
   obvious without anything being hidden from a reader who is skimming. */
.parks-steps {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Roughly two scroll gestures per park. The pitch between steps, not the
   0.9s cross-fade, is what makes this feel unhurried: at the original
   10vh the next park arrived the moment you nudged the wheel. */
.park-step {
  padding: clamp(6rem, 30vh, 18rem) 0;
  opacity: 0.4;
  transition: opacity 0.9s cubic-bezier(0.33, 0, 0.2, 1);
}

.park-step[data-active] { opacity: 1; }

/* The first park keeps its full top padding. With padding-top:0 its heading
   sat at the very top of the section, which put it behind the fixed header at
   exactly the moment the plate finished centring — so the plate landed and
   "Maasai Mara" was already half gone. The padding also gives the section
   intro room to breathe before the first park starts. */

@media (prefers-reduced-motion: reduce) {
  .park-plate, .park-step { transition: none; }
}

/* Under 861px the sticky frame is capped so the plate and the text it belongs
   to are on screen together; an uncapped 4:3 plate eats a phone viewport. */
@media (max-width: 860px) {
  /* Block flow, not grid. In a grid the plate column is its own row, so its
     containing block ends where the row does and the sticky plate scrolls
     away with the first park. In normal flow both children share
     .parks-sticky, which is tall enough for the plate to hold all the way
     down the section. */
  .parks-sticky { display: block; }
  /* display:contents removes this wrapper from the box tree, so the sticky
     frame becomes a direct child of .parks-sticky and inherits the whole
     section as its travel range. Left as a box, it constrains the sticky
     element to its own height and nothing sticks. */
  .parks-visual { display: contents; }

  .parks-visual-frame {
    position: sticky;
    top: var(--header-h);
    height: auto;
    display: block;
    /* Opaque, so the steps scrolling underneath do not show through the strip
       between the header and the plate. */
    background: var(--ivory);
    padding: 0.6rem 0 0.9rem;
    margin-bottom: 0.5rem;
  }

  .parks-visual-stage {
    aspect-ratio: 4 / 3;
    max-height: 34vh;
    margin: 0 auto;
  }

  .park-step { padding: clamp(3.5rem, 18vh, 9rem) 0; }
  .park-step:first-child { padding-top: 0.5rem; }
}

/* No JS: no step is marked active, so show everything at full strength and
   let the first plate stand. */
.no-js .park-step { opacity: 1; }
.no-js .park-plate:first-child { opacity: 1; transform: none; }

/* ---------- Reading the land ---------- */

.reading {
  background: var(--deep-slate);
}

.reading-pin {
  position: relative;
  min-height: 100svh;
  display: flex;
  align-items: center;
  overflow: hidden;
  padding: calc(var(--header-h) + 1rem) 0 2rem;
}

.reading-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(1.5rem, 4vw, 4.5rem);
  align-items: center;
  max-width: 76rem;
  width: 100%;
  margin: 0 auto;
  padding: 0 var(--gutter);
}

.reading-text .section-heading {
  display: grid;
}

.reading-caption {
  grid-area: 1 / 1;
}

/* Statically (no JS, reduced motion) both captions stack and read as one heading. */
html:not(.has-motion) .reading-caption-1 { position: relative; }
html:not(.has-motion) .reading-text .section-heading { display: block; }
html:not(.has-motion) .reading-caption { display: block; }
html:not(.has-motion) .reading-caption-2 { margin-top: 0.6em; }

.reading-sub {
  margin-top: 1.5rem;
  max-width: 26rem;
  color: var(--ivory);
}

.reading-text .record-line-dark {
  margin-top: 2rem;
}

.reading-media {
  display: flex;
  justify-content: center;
}

.reading-frame {
  overflow: hidden;
  border-radius: var(--r-lg);
  max-height: min(78vh, 40rem);
  aspect-ratio: 1204 / 1600;
}

.reading-frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform-origin: 42% 68%;
  will-change: transform;
}

@media (max-width: 860px) {
  .reading-layout { grid-template-columns: 1fr; gap: 1.2rem; }
  /* The whole pinned scene must fit inside one small viewport, or the frame's
     bottom edge stays hidden for the entire pin. */
  .reading-frame { max-height: 40vh; }
  .reading-pin { padding-top: calc(var(--header-h) + 0.5rem); }
  .reading-text .section-heading { font-size: clamp(1.5rem, 5.6vw, 2.05rem); }
  .reading-sub { margin-top: 1rem; }
  .reading-text .record-line-dark { margin-top: 1.2rem; }
}

/* ---------- The record ---------- */

.record {
  padding: var(--section-pad) 0 0;
}

.record-grid {
  margin-top: clamp(2.5rem, 6vw, 4rem);
  columns: 3;
  column-gap: clamp(1.2rem, 2.5vw, 2rem);
}

.record-item {
  break-inside: avoid;
  margin-bottom: clamp(1.8rem, 3.5vw, 2.6rem);
}

/* The Oloololo Gate frame used to span all three columns and ate most of a
   screen on its own. Laurence asked for it back down to gallery size, 8 Aug
   2026. It keeps its note, which is the one caption on the page that says
   something the picture cannot. */
.record-item-lead {
  column-span: none;
}

.record-item figcaption {
  padding-top: 0.8rem;
  border-top: 1px solid var(--jungle-25);
  margin-top: 0.9rem;
}

.record-note {
  margin-top: 0.5rem;
  font-size: 1rem;
  line-height: 1.6;
  max-width: 38rem;
  color: var(--jungle-green);
}

@media (max-width: 980px) { .record-grid { columns: 2; } }
@media (max-width: 620px) { .record-grid { columns: 1; } }

.record-band {
  position: relative;
  margin-top: clamp(3rem, 7vw, 5rem);
  overflow: hidden;
  max-height: 70vh;
}

.record-band img {
  width: 100%;
  height: 70vh;
  min-height: 20rem;
  object-fit: cover;
  will-change: transform;
}

.record-band-caption {
  position: absolute;
  left: var(--gutter);
  bottom: 1.2rem;
  color: var(--ivory);
  z-index: 2;
  text-shadow: 0 1px 8px rgba(28, 42, 35, 0.8);
}

/* ---------- The guide ---------- */

.guide {
  padding: var(--section-pad) 0;
}

.guide-layout {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
  gap: clamp(1.8rem, 4.5vw, 4.5rem);
  margin-top: clamp(2.5rem, 6vw, 4rem);
  align-items: start;
}

.guide-media-main .record-line,
.guide-media-secondary .record-line {
  margin-top: 0.8rem;
}

.guide-body {
  max-width: var(--measure);
}

.guide-body + .guide-body { margin-top: 1.2rem; }

.pending-card {
  margin-top: 2rem;
  border: 1.5px dashed var(--terracotta);
  border-radius: var(--r-lg);
  padding: 1.5rem 1.6rem;
}

.pending-list {
  list-style: none;
  padding: 0;
  margin-top: 0.9rem;
  font-weight: 400;
  font-size: 1.05rem;
  line-height: 2;
}

.pending-note {
  margin-top: 0.8rem;
  font-size: 0.92rem;
  color: var(--jungle-green);
}

.guide-media-secondary {
  margin-top: 2.2rem;
}

@media (max-width: 860px) {
  .guide-layout { grid-template-columns: 1fr; }
}

/* ---------- Trips (removed) ----------
   The .trips / .trip-card rules lived here until 8 August 2026, when the
   "Two ways to travel" section was replaced by .experiences. Deleted rather
   than left dangling: nothing on the page carried those classes any more. */

/* ---------- Tilt (Comet Card treatment) ----------
   Applied to the six experience cards. The transform itself is written by
   tilt.js;
   everything here is the resting state and the sheen it moves around. */

[data-tilt] {
  position: relative;
  transform-style: preserve-3d;
  /* Returning to rest is eased; following the pointer is not, so the card
     tracks instantly on the way in and settles on the way out. */
  transition: transform 0.5s cubic-bezier(0.22, 0.61, 0.36, 1),
              border-color 0.3s ease;
}

[data-tilt][data-tilting] {
  transition: border-color 0.3s ease;
  border-color: var(--ivory-60);
}

/* The sheen only exists while the card is being tracked, so a resting card
   is exactly the flat card it was before. */
[data-tilt]::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  opacity: 0;
  pointer-events: none;
  background: radial-gradient(
    22rem circle at var(--sheen-x, 50%) var(--sheen-y, 50%),
    rgba(232, 201, 139, 0.16),
    rgba(232, 201, 139, 0) 60%
  );
  transition: opacity 0.35s ease;
}

[data-tilt][data-tilting]::after { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  [data-tilt] { transition: none; transform: none; }
  [data-tilt]::after { display: none; }
}

/* ---------- Ask ---------- */

.ask {
  padding: var(--section-pad) 0;
}

.ask-layout {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  margin-top: clamp(2.5rem, 6vw, 4rem);
  align-items: start;
}

.field {
  margin-bottom: 1.3rem;
}

.field label {
  display: block;
  font-weight: 400;
  font-size: 0.95rem;
  margin-bottom: 0.45rem;
}

.field-hint {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--terracotta);
  margin-left: 0.5rem;
}

.field input,
.field textarea {
  width: 100%;
  background: var(--ivory);
  border: 1.5px solid var(--jungle-45);
  border-radius: var(--r-lg);
  padding: 0.85em 1em;
  font-family: var(--font-body);
  font-size: 1rem;
  color: var(--deep-earth);
  transition: border-color 0.2s ease;
}

.field input:focus,
.field textarea:focus {
  border-color: var(--jungle-green);
}

.field textarea { resize: vertical; }

.btn-submit { margin-top: 0.4rem; }

.form-status {
  margin-top: 1rem;
  min-height: 1.2em;
}

.ask-direct-heading {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 1.3rem;
  line-height: 1.1;
  letter-spacing: -0.02em;
  margin-bottom: 1.4rem;
}

.ask-direct .btn-block + .pending-stamp {
  margin-top: 0.6rem;
  margin-bottom: 1.6rem;
}

.ask-direct-note {
  margin-top: 1.6rem;
  font-size: 0.95rem;
  color: var(--jungle-green);
  max-width: 24rem;
}

@media (max-width: 860px) {
  .ask-layout { grid-template-columns: 1fr; }
}

/* ---------- Field plates ----------
   A stack of portrait photographs advanced one at a time, centred, with the
   neighbours held back. The active plate is the only one at full strength, so
   the eye is never asked to choose. */

.plates {
  position: relative;
  background: var(--deep-slate);
  padding: var(--section-pad) 0;
  overflow: hidden;
}

.plates-stage {
  margin-top: clamp(2.5rem, 6vw, 4rem);
}

.plates-viewport {
  overflow: hidden;
  padding: 0.5rem 0;
}

.plates-track {
  display: flex;
  align-items: flex-start;
  gap: clamp(1rem, 2.5vw, 2rem);
  margin: 0;
  padding: 0;
  list-style: none;
  will-change: transform;
  transition: transform 0.75s cubic-bezier(0.22, 0.61, 0.36, 1);
}

.plate {
  flex: 0 0 auto;
  width: min(26rem, 78vw);
  opacity: 0.32;
  transform: scale(0.9);
  transform-origin: center bottom;
  transition: opacity 0.6s ease, transform 0.6s cubic-bezier(0.22, 0.61, 0.36, 1);
}

.plate[data-active] {
  opacity: 1;
  transform: scale(1);
}

.plate figure { margin: 0; }

.plate img {
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  border-radius: var(--r-lg);
  background: var(--jungle-25);
}

.plate figcaption {
  margin-top: 0.9rem;
  padding-top: 0.8rem;
  border-top: 1px solid var(--ivory-18);
}

.plate-note {
  margin-top: 0.45rem;
  font-size: 1rem;
  line-height: 1.6;
  color: var(--ivory);
  opacity: 0.82;
}

/* Only the active plate's caption is read out; the rest are decoration. */
.plate:not([data-active]) figcaption { opacity: 0.7; }

.plates-controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.4rem;
  margin-top: clamp(1.6rem, 3vw, 2.4rem);
}

.plate-btn {
  width: 2.75rem;
  height: 2.75rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  color: var(--ivory);
  border: 1.5px solid var(--ivory-18);
  border-radius: 50%;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  transition: border-color 0.2s ease, background-color 0.2s ease, color 0.2s ease;
}

.plate-btn:hover {
  border-color: var(--golden-savannah);
  background: var(--golden-savannah);
  color: var(--deep-earth);
}

.plates-dots {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.plates-dot {
  width: 0.5rem;
  height: 0.5rem;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: var(--ivory-18);
  cursor: pointer;
  transition: background-color 0.25s ease, width 0.25s ease;
}

.plates-dot[aria-selected="true"] {
  background: var(--golden-savannah);
  width: 1.5rem;
  border-radius: 0.25rem;
}

/* The live region announces the change; it does not need to be seen. */
.plates-status {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.plates-pending {
  margin-top: clamp(1.8rem, 4vw, 2.6rem);
  opacity: 0.75;
}

/* Without JS the track cannot be driven, so let it scroll by hand instead of
   stranding five of the six photographs off-screen. */
.no-js .plates-viewport {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.no-js .plate { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .plates-track { transition: none; }
  .plate { transition: none; }
}

@media (max-width: 640px) {
  .plate { width: 78vw; }
  .plates-controls { gap: 1rem; }
}

/* ---------- Pointer highlight ----------
   A box drawn around a phrase as it comes into view, with a small pointer
   arriving at the corner. Used once, on the provenance claim, because that
   sentence is the one the whole page is asking to be believed. Using it twice
   would spend the effect. */

.pointer-hl {
  position: relative;
  display: inline-block;
  padding: 0.1em 0.25em;
  margin: 0 -0.05em;
}

.pointer-hl-text { position: relative; z-index: 1; }

.pointer-hl::before {
  content: "";
  position: absolute;
  inset: 0;
  border: 1.5px solid var(--golden-savannah);
  border-radius: var(--r-sm);
  opacity: 0;
  transform: scale(0.94);
  transform-origin: left center;
  transition: opacity 0.5s ease, transform 0.7s cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* The pointer itself: a small arrow that arrives just after the box lands. */
.pointer-hl::after {
  content: "";
  position: absolute;
  right: -0.55em;
  bottom: -0.75em;
  width: 0.7em;
  height: 0.7em;
  background: var(--golden-savannah);
  clip-path: polygon(0 0, 0 100%, 30% 72%, 52% 100%, 68% 88%, 46% 62%, 100% 44%);
  opacity: 0;
  transform: translate(-0.3em, -0.3em);
  transition: opacity 0.35s ease 0.35s, transform 0.55s cubic-bezier(0.22, 0.61, 0.36, 1) 0.35s;
}

.pointer-hl[data-lit]::before { opacity: 1; transform: scale(1); }
.pointer-hl[data-lit]::after  { opacity: 1; transform: translate(0, 0); }

@media (prefers-reduced-motion: reduce) {
  .pointer-hl::before,
  .pointer-hl::after { transition: none; }
}

/* ---------- Animal tooltip cards ----------
   The six names in the plates intro. Hover shows the frame; activating moves
   the viewer to it. Styled to sit in the sentence, not on top of it. */

.animal {
  font: inherit;
  color: var(--sandstone);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  border-bottom: 1px dashed rgba(232, 201, 139, 0.5);
  transition: color 0.2s ease, border-color 0.2s ease;
}

.animal:hover,
.animal[data-open] {
  color: var(--golden-savannah);
  border-bottom-color: var(--golden-savannah);
}

.tooltip-card {
  position: fixed;
  z-index: 60;
  width: 13rem;
  padding: 0.5rem;
  border-radius: var(--r-lg);
  background: rgba(28, 42, 35, 0.94);
  border: 1px solid var(--ivory-18);
  box-shadow: 0 18px 40px rgba(28, 42, 35, 0.45);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  pointer-events: none;
  opacity: 0;
  transform: translateY(6px) scale(0.97);
  transition: opacity 0.28s ease, transform 0.38s cubic-bezier(0.22, 0.61, 0.36, 1);
}

.tooltip-card[data-open] { opacity: 1; transform: translateY(0) scale(1); }

.tooltip-card img {
  width: 100%;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  border-radius: var(--r-md);
}

.tooltip-card p {
  margin-top: 0.5rem;
  padding: 0 0.25rem 0.15rem;
}

@media (prefers-reduced-motion: reduce) {
  .tooltip-card { transition: opacity 0.01s linear; transform: none; }
  .tooltip-card[data-open] { transform: none; }
}

/* ---------- Stateful submit ---------- */

.btn-submit {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  min-width: 12rem;
}

.btn-spinner {
  width: 0.95em;
  height: 0.95em;
  border-radius: 50%;
  border: 2px solid rgba(59, 39, 22, 0.35);
  border-top-color: var(--deep-earth);
  display: none;
}

.btn-submit[data-state="working"] { cursor: progress; }
.btn-submit[data-state="working"] .btn-spinner { display: block; }

@media (prefers-reduced-motion: no-preference) {
  .btn-submit[data-state="working"] .btn-spinner { animation: btn-spin 0.7s linear infinite; }
  @keyframes btn-spin { to { transform: rotate(360deg); } }
}

.btn-submit[disabled] { opacity: 0.85; }

/* ---------- Footer ---------- */

.site-footer {
  position: relative;
  background: var(--deep-slate);
  padding: clamp(3rem, 7vw, 5rem) 0 2.5rem;
  overflow: hidden;
}

.footer-inner {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 4rem);
  align-items: start;
}

.footer-brand .wordmark-main { font-size: 1.6rem; }

.footer-line {
  margin-top: 1rem;
  color: var(--ivory);
  opacity: 0.8;
  font-size: 0.95rem;
  max-width: 24rem;
}

.footer-nav {
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
}

.footer-nav a {
  color: var(--golden-savannah);
  text-decoration: none;
  font-size: 0.95rem;
  width: fit-content;
  border-bottom: 1.5px solid transparent;
  transition: border-color 0.2s ease;
}

.footer-nav a:hover { border-bottom-color: var(--golden-savannah); }

.footer-meta .record-line {
  color: var(--ivory-60);
}

.footer-meta .record-line + .record-line { margin-top: 0.8rem; }

@media (max-width: 780px) {
  .footer-inner { grid-template-columns: 1fr; }
}

/* ================================================================
   The Magical Sticky Scroll Down Text Reveal
   Added 8 August 2026. Named by Laurence, after morningside.ai.

   A conveyor, not a list. One block of text holds the centre at full
   strength; scrolling lifts it up and dims it to a ghost while the next
   block rises from below to take its place. Never more than two legible at
   once. A gold rule along the foot of the stage says how far through the
   passage the reader is.

   The overlapping absolute layout below is gated behind BOTH html.has-motion
   (set by site.js once motion is welcome) AND [data-msr-ready] (set by
   textreveal.js once it has decided to drive this particular section). If
   either is missing the blocks stay in normal flow, stacked and fully
   legible, and the section does not pin. That is the whole no-JS story: the
   passage reads as ordinary paragraphs.
   ================================================================ */

.msr {
  position: relative;
  background: var(--deep-slate);
  overflow: hidden;
}

.msr-pin {
  position: relative;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: calc(var(--header-h) + 2.5rem) 0 3.5rem;
}

/* A jungle-green wash over the slate. Laurence asked for "that jungle green
   background"; flat Jungle Green would drop gold accent type to 3.1:1, which
   fails. Washed over Deep Slate it reads green and keeps the accent legible. */
.msr-pin::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(
    130% 78% at 50% 46%,
    rgba(74, 92, 58, 0.55) 0%,
    rgba(74, 92, 58, 0.20) 46%,
    rgba(74, 92, 58, 0) 74%
  );
}

.msr-stage {
  position: relative;
  z-index: 2;
  width: 100%;
  text-align: center;
}

/* Bottom left, above the progress rule, not centred at the top.

   Three positions were tried. In the centred flow above the blocks it pushed
   the whole composition high. Anchored to the top of the pin it was in the
   exact path of every departing block: the blocks travel upward by half their
   own height plus half the next one's, and against a four-line block that
   puts the ghost's top edge at 81px, straight through a label at 96px.
   Down here nothing legible ever reaches it, because an arriving block is
   still at zero opacity while it is this low. Left-aligned in the gutter for
   the same reason: the blocks are centred and capped at 30ch, so they never
   come near the gutter on a wide screen. */
.msr-label {
  position: absolute;
  bottom: 2.1rem;
  left: var(--gutter);
  z-index: 4;
  margin: 0;
  text-align: left;
}

html:not(.has-motion) .msr-label {
  position: static;
  margin-bottom: clamp(2rem, 5vh, 3.4rem);
}

.msr-blocks {
  position: relative;
}

.msr-block {
  font-family: var(--font-display);
  font-weight: 400;
  /* Reduced 8 Aug 2026: at 3.2vw this capped near 43px and Laurence read it
     as too large at 100% zoom. ~34px on a desktop sits closer to the
     reference and stops single sentences breaking into four lines. */
  font-size: clamp(1.15rem, 2.4vw, 2.1rem);
  line-height: 1.3;
  letter-spacing: -0.02em;
  color: var(--ivory);
}

/* Widened from 26ch. Not full-bleed, just enough that a sentence is not
   forced into more lines than it has clauses. */
.msr-block-inner {
  max-width: 42ch;
  margin: 0 auto;
}

.msr-block-inner span { display: block; }

/* The one line the passage turns on. Colour only: it keeps the same face as
   every block around it, because Laurence found the face change one signal too
   many on top of the colour.

   Sandstone rather than Golden Savannah. The palette designates Sandstone as
   accent type on dark, and it measures 7.28:1 over the washed ground where the
   signature gold sits near 4:1. */
.msr-block-accent .msr-block-inner {
  color: var(--sandstone);
}

/* Static fallback: no JS, no GSAP, or reduced motion. Ordinary paragraphs. */
.msr-block + .msr-block { margin-top: clamp(1.6rem, 4vh, 2.6rem); }

html.has-motion [data-msr-ready] .msr-blocks {
  min-height: 54vh;
}

html.has-motion [data-msr-ready] .msr-block {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  will-change: transform, opacity;
}

/* Progress along the passage. A fill, never type, so gold is safe here. */
.msr-bar-track {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: var(--ivory-18);
  z-index: 3;
}

.msr-bar {
  height: 100%;
  background: var(--golden-savannah);
  transform: scaleX(0);
  transform-origin: left center;
}

html:not(.has-motion) .msr-bar-track { display: none; }

/* The .msr-tail CTA band lived here until the Choices conveyor it belonged to
   was cut on 8 August 2026. Removed rather than left dangling. */

@media (max-width: 640px) {
  .msr-block { font-size: clamp(1.15rem, 5.2vw, 1.55rem); }
  html.has-motion [data-msr-ready] .msr-blocks { min-height: 60vh; }
}

/* ---------- Hero trust strip ---------- */

/* Three claims, not four. "Designed around you" was cut because it says the
   same thing as "Personally planned"; the three that remain are each a
   different promise (vehicle, itinerary, knowledge) and they scan as a set. */

.trust-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 1.5rem;
  margin-top: 2.2rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--ivory-18);
  max-width: var(--measure);
}

.trust-item {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sandstone);
}

/* ---------- Philosophy ---------- */

/* Darkened 8 August 2026 to run as one dark stretch with .reach above it
   (see .reach for the same position/background/overflow trio). Every colour
   below this point in the section was chosen for the dark ground, not
   inherited from the Ivory version this used to be. */
.philosophy {
  position: relative;
  background: var(--deep-slate);
  padding: var(--section-pad) 0;
  overflow: hidden;
}

.philosophy-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  margin-top: clamp(2.5rem, 6vw, 4rem);
  align-items: start;
}

/* Stacks the lead and the belief in one grid cell so the belief sits
   directly under the lead instead of waiting for a second grid row. */
.philosophy-column {
  display: flex;
  flex-direction: column;
}

.philosophy-lead {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.25rem, 2.2vw, 1.6rem);
  line-height: 1.4;
  letter-spacing: -0.01em;
  color: var(--ivory);
  max-width: 24ch;
}

/* The unseen work, set as a register rather than a sentence. Each line is a
   thing the traveller never sees, so they are listed the way a checklist is,
   not the way prose is. Hairlines use the ivory-alpha token the header and
   mobile nav already use for borders on this same dark ground. */
.philosophy-list {
  list-style: none;
  padding: 0;
  margin: 0;
  border-top: 1px solid var(--ivory-18);
}

.philosophy-list li {
  font-family: var(--font-mono);
  font-size: 0.82rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--sandstone);
  padding: 0.85rem 0;
  border-bottom: 1px solid var(--ivory-18);
}

/* Quote-weight belief line, styled like the footer tagline: sandstone is the
   accent colour for type on a dark ground, never gold direct. Sits close
   under the lead now that philosophy-turn (cut in the same pass) no longer
   separates them. */
.philosophy-belief {
  margin-top: clamp(1.6rem, 4vw, 2.4rem);
  padding-top: clamp(1.8rem, 4vw, 2.4rem);
  border-top: 1px solid var(--ivory-18);
  font-family: var(--font-quote);
  /* Brought down 8 Aug 2026: at a 1.65rem cap it outsized the lead it sits
     under and read as the section's headline rather than its coda. Now just
     under .philosophy-lead, so the lead stays primary. */
  font-size: clamp(1.05rem, 1.8vw, 1.35rem);
  line-height: 1.6;
  color: var(--sandstone);
  max-width: 40ch;
}

@media (max-width: 860px) {
  .philosophy-layout { grid-template-columns: 1fr; }
}

/* ---------- Experiences ----------
   Replaces the two "Two ways to travel" cards, which asked the reader to
   choose between two options while showing [To confirm] against every fact
   on both. Six named ways to travel, each one a link into the inquiry form
   with its type already chosen, needs no price to be useful. */

.experiences {
  padding: var(--section-pad) 0;
}

.experiences-intro {
  margin-top: clamp(2rem, 5vw, 3rem);
  max-width: 46rem;
}

.experiences-intro .msr-line {
  font-family: var(--font-display);
  font-size: clamp(1.1rem, 1.9vw, 1.35rem);
  line-height: 1.55;
  color: var(--deep-earth);
}

.experiences-intro .msr-line + .msr-line { margin-top: 0.5rem; }

/* Gated on data-msr-flow-ready, which textreveal.js sets only after it has
   found lines it will actually animate. This is not belt-and-braces: on
   8 August 2026 the dim state was keyed on the container alone while the
   script matched on [data-msr-line], an attribute the markup did not carry.
   Nothing errored. Five consecutive lines of copy simply sat at 0.22 forever,
   about 1.5:1 against Ivory, invisible but present in the DOM and in every
   automated text check. Keying the dim state to the script's own success
   makes silent-invisible-text impossible: no script, no dimming. */
html.has-motion [data-msr-flow][data-msr-flow-ready] .msr-line { opacity: 0.22; }

.experiences-turn {
  margin-top: clamp(1.6rem, 4vw, 2.2rem);
  font-family: var(--font-quote);
  font-size: clamp(1.1rem, 2vw, 1.4rem);
  line-height: 1.5;
  color: var(--terracotta);
  max-width: 30ch;
}

.experience-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(1rem, 2vw, 1.4rem);
  margin-top: clamp(2.5rem, 6vw, 3.5rem);
}

/* Resting state carries a whisper of jungle-green so the card reads as a
   card against Ivory rather than a flat outline. Half the hover alpha, so
   hover (0.05) stays a clearly distinct step up, not a repeat. */
.experience-card {
  display: block;
  border: 1.5px solid var(--jungle-25);
  border-radius: var(--r-lg);
  padding: clamp(1.4rem, 2.6vw, 1.9rem);
  text-decoration: none;
  color: inherit;
  /* The header's own green, at Laurence's direction after two lighter passes
     failed to read as cards at all. Tints of jungle green over Ivory topped
     out around a nine-value-per-channel shift, which is not differentiation.
     This is the same Deep Slate the navigation pill uses, so the cards belong
     to the same family as the bar above them.

     Going dark means every colour inside the card has to invert with it. That
     is handled below; do not change this line without changing those. */
  background: rgba(28, 42, 35, 0.94);
  border-color: transparent;
  transition: transform 0.3s ease, background-color 0.3s ease, box-shadow 0.3s ease;
  box-shadow: 0 1px 2px rgba(28, 42, 35, 0.10);
}

.experience-card:hover {
  background: var(--deep-slate);
  border-color: transparent;
  box-shadow: 0 14px 30px rgba(28, 42, 35, 0.22);
}

/* Inverted with the ground above. Ivory heading, softened Ivory body,
   Sandstone for the action: the same three roles the dark sections use. */
.experience-card .experience-name { color: var(--ivory); }
.experience-card .experience-note { color: var(--ivory); opacity: 0.82; }
.experience-card .experience-go { color: var(--sandstone); }

.experience-name {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.15rem, 1.9vw, 1.4rem);
  line-height: 1.15;
  letter-spacing: -0.02em;
  color: var(--deep-earth);
}

.experience-note {
  margin-top: 0.7rem;
  font-size: 0.95rem;
  line-height: 1.55;
  color: var(--jungle-green);
}

.experience-go {
  display: inline-block;
  margin-top: 1.1rem;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--terracotta);
}

.experiences-note {
  margin-top: clamp(2rem, 5vw, 3rem);
  max-width: 44rem;
  color: var(--jungle-green);
}

@media (max-width: 900px) {
  .experience-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 560px) {
  .experience-grid { grid-template-columns: 1fr; }
}

/* ---------- The camera-roll gallery ----------
   Turned up 8 August 2026. The tilt alone read as "a volume turned down": on a
   photograph, six degrees of rotation is invisible because there is no card
   edge to see it against. What makes a picture feel liftable is everything
   around the rotation, so this adds the three things the rotation cannot do
   on its own: a real cast shadow, a lift above its neighbours, and a small
   push in saturation and contrast so the frame gains presence as it comes up.

   The shadow sits on the img, not the figure, because the figure also holds
   the caption and a shadow around that would read as a card rather than a
   photograph. */

.record-item[data-tilt] img {
  transition: box-shadow 0.35s ease, filter 0.35s ease;
  box-shadow: 0 1px 3px rgba(28, 42, 35, 0.10);
}

.record-item[data-tilt][data-tilting] {
  z-index: 4;
}

.record-item[data-tilt][data-tilting] img {
  box-shadow: 0 30px 55px rgba(28, 42, 35, 0.34), 0 6px 14px rgba(28, 42, 35, 0.18);
  filter: saturate(1.10) contrast(1.05) brightness(1.03);
}

/* A specular highlight, not a wash. Terracotta at a tenth was invisible over
   photographs; near-white at a fifth reads as light moving across glass. */
.record-item[data-tilt]::after {
  background: radial-gradient(
    20rem circle at var(--sheen-x, 50%) var(--sheen-y, 50%),
    rgba(246, 243, 236, 0.20),
    rgba(246, 243, 236, 0) 58%
  );
}

/* The cards went dark on 8 Aug 2026, so the terracotta sheen that suited them
   on Ivory went back to sandstone, which is what reads on Deep Slate. */
.experience-card[data-tilt]::after {
  background: radial-gradient(
    18rem circle at var(--sheen-x, 50%) var(--sheen-y, 50%),
    rgba(232, 201, 139, 0.16),
    rgba(232, 201, 139, 0) 62%
  );
}

.experience-card[data-tilt][data-tilting] { border-color: transparent; }

/* Camera-roll photographs are on the same Ivory ground as the experience
   cards, so they take the same terracotta sheen rather than the base
   sandstone one built for dark cards. */
.record-item[data-tilt]::after {
  background: radial-gradient(
    18rem circle at var(--sheen-x, 50%) var(--sheen-y, 50%),
    rgba(139, 90, 43, 0.10),
    rgba(139, 90, 43, 0) 62%
  );
}

/* ---------- The founder ---------- */

.founder-lead {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.2rem, 2.1vw, 1.55rem);
  line-height: 1.45;
  letter-spacing: -0.01em;
  color: var(--deep-earth);
  max-width: 38rem;
}

/* The lead runs straight into the first body paragraph without this, and the
   two read as one run-on block at two different sizes. */
.founder-lead + .guide-body { margin-top: 1.4rem; }

.founder-link {
  display: inline-block;
  margin-top: 1.8rem;
  font-family: var(--font-display);
  font-size: 1.05rem;
  color: var(--terracotta);
  text-decoration: none;
  border-bottom: 1.5px solid var(--terracotta);
  padding-bottom: 0.25rem;
  transition: color 0.25s ease, border-color 0.25s ease;
}

.founder-link:hover {
  color: var(--deep-earth);
  border-color: var(--deep-earth);
}

/* ---------- Footer tagline ---------- */

.footer-tagline {
  font-family: var(--font-quote);
  font-size: clamp(1.05rem, 1.9vw, 1.3rem);
  line-height: 1.45;
  color: var(--sandstone);
  margin-bottom: 1rem;
}


/* ---------- Section rules ----------
   Rebuilt 9 August 2026. The first version was a bare hairline and Laurence
   was right about both of its failures: on the dark ground it read as a hard
   bright wire (his word was "forced"), and on Ivory it was so faint it did
   not divide anything.

   What his reference actually does, and what the hairline was missing, is the
   BLOOM. A soft warm haze rises out of the line into the empty space above it.
   The line alone is a border; the line plus the bloom is an atmosphere, and
   that is the whole difference between a rule that feels drawn on and one that
   feels like the page breathing.

   The bloom is an absolutely positioned pseudo-element, so it costs zero
   layout height — it floats up over the section padding above, which is empty
   anyway. Three of these on the page add no length at all.

   Two grounds, two colour stories:
     dark  gold, dialled well back. The old line peaked at 0.45 alpha; over
           Deep Slate that is a wire. 0.26 with much longer tails reads as a
           seam.
     light terracotta, not gold. Gold on Ivory measures 2.1:1 and is the single
           most common way this palette gets ruined. Laurence asked for
           "something that leans more on dark brown, like a gradient of the
           gold" — that is exactly Terracotta Brown, already in the palette. */

.rule-gold {
  position: relative;
  height: 1px;
  border: 0;
  margin: 0;
  overflow: visible;
  background: linear-gradient(
    90deg,
    rgba(210, 162, 76, 0) 0%,
    rgba(210, 162, 76, 0.10) 18%,
    rgba(210, 162, 76, 0.26) 50%,
    rgba(210, 162, 76, 0.10) 82%,
    rgba(210, 162, 76, 0) 100%
  );
}

/* The bloom. Widest and softest at the line, gone by the top. */
.rule-gold::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 170px;
  pointer-events: none;
  background: radial-gradient(
    120% 100% at 50% 100%,
    rgba(210, 162, 76, 0.10) 0%,
    rgba(210, 162, 76, 0.035) 38%,
    rgba(210, 162, 76, 0) 72%
  );
}

/* On Ivory. Terracotta carries where gold cannot, and the bloom is halved
   again because a warm haze reads much louder on a light ground. */
.rule-gold-light {
  background: linear-gradient(
    90deg,
    rgba(139, 90, 43, 0) 0%,
    rgba(139, 90, 43, 0.16) 16%,
    rgba(139, 90, 43, 0.42) 50%,
    rgba(139, 90, 43, 0.16) 84%,
    rgba(139, 90, 43, 0) 100%
  );
}

.rule-gold-light::before {
  height: 150px;
  background: radial-gradient(
    120% 100% at 50% 100%,
    rgba(139, 90, 43, 0.075) 0%,
    rgba(139, 90, 43, 0.026) 40%,
    rgba(139, 90, 43, 0) 74%
  );
}

/* Between the parks, the same idea at a different scale. Not full width:
   Laurence asked for it inset from the left and running a little past the end
   of the description, so it measures the column rather than the page. The
   description caps at 30rem, so 34rem clears it by about four characters. */

.park-step { position: relative; }

.park-step + .park-step::before {
  content: "";
  position: absolute;
  top: 0;
  left: clamp(1.25rem, 3vw, 2.5rem);
  width: min(34rem, calc(100% - clamp(1.25rem, 3vw, 2.5rem)));
  height: 1px;
  /* Terracotta, not gold. This was gold at 0.55 and Laurence could not see it
     at all, which is the predictable result of putting gold on Ivory: 2.1:1.
     Same hue family, dark enough to actually divide two parks. */
  background: linear-gradient(
    90deg,
    rgba(139, 90, 43, 0.50) 0%,
    rgba(139, 90, 43, 0.26) 62%,
    rgba(139, 90, 43, 0) 100%
  );
}
