/* ==========================================================================
   impossible.je — styles
   Tokens from brand/impossible.je-brand-pack.md (single source of truth).
   Ivory on ink, one point of jade. Editorial, quiet, still by default.
   ========================================================================== */

/* ---------- self-hosted fonts (latin subsets, no third-party requests) --- */

@font-face {
  font-family: "Cormorant Garamond";
  src: url("assets/fonts/cormorant-garamond.woff2") format("woff2");
  font-weight: 500 600;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Cormorant Garamond";
  src: url("assets/fonts/cormorant-garamond-italic.woff2") format("woff2");
  font-weight: 500;
  font-style: italic;
  font-display: swap;
}

@font-face {
  font-family: "Inter";
  src: url("assets/fonts/inter-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "JetBrains Mono";
  src: url("assets/fonts/jetbrains-mono-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* ---------- tokens ---------- */

:root {
  --ink:     #1F1F1D;
  --surface: #262624;
  --line:    #33332F;
  /* two lifted line tones for clarity: --rule for structural dividers and the field-book
     ruling (legible over the map relief), --line-strong for interactive affordances —
     link underlines, form fields, the consent box — so they read clearly at rest, not
     only on hover. Both ivory-based so they hold over dark ground and bright relief alike. */
  --rule:        rgba(241, 239, 232, 0.14);
  --line-strong: rgba(241, 239, 232, 0.34);
  --ivory:   #F1EFE8;
  --white:   #F5F5F5;
  --body:    #CBC9C1;
  --muted:   #8C8B86;
  --accent:  #5DCAA5;
  --accent-hover: #78DBBA;

  /* the survey chart's land, on the charcoal ladder above (the sea is the ink canvas
     itself, unpainted). Three tiers of recede: Jersey the subject sits warmest; the
     other Channel Islands step down to --land-far (surface lifted ~20% so the smaller
     ones stay legible against the ink); the French mainland recedes furthest to
     --land-france, kept dark so the large mass reads as background and never competes
     with Jersey for the eye. */
  --land:        #3B3A36;
  --land-far:    #33332F;
  --land-france: #262624;

  --font-display: "Cormorant Garamond", Georgia, "Times New Roman", serif;
  --font-sans: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, "SFMono-Regular", Consolas, "Liberation Mono", monospace;

  --measure: 34rem;      /* prose measure */
  --gutter: clamp(1.5rem, 5vw, 4rem);
  --section-gap: clamp(5rem, 12vh, 9rem);
  --footer-lead: clamp(4rem, 13vh, 7.5rem);  /* uniform air between a sheet's last block and the footer rule */
  --header-h: 6.5rem;    /* approximate header height, used by the hero */
  --ease: cubic-bezier(0.25, 0.1, 0.25, 1);
}

/* ---------- reset, base ---------- */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  overflow-x: clip;   /* full-bleed instruments (header band, footer feather) never mint a horizontal scrollbar */
  background: #1F1F1D;   /* the canvas itself is ink — no white frame between pages */
  color-scheme: dark;
}

/* no cross-document dissolve: the crossfade rides on browser snapshots, and
   when Chrome loses the outgoing page's snapshot it composites a blank white
   plate in its place — a layer no CSS background can paint over. Pages cut
   directly instead: the fixed chrome is identical on every sheet and the ink
   canvas holds until the new page has painted, so the cut only swaps the
   reading column and the chart's crop. */

body {
  margin: 0;
  background: var(--ink);
  color: var(--ivory);
  font-family: var(--font-sans);
  font-size: 1.0625rem;
  font-weight: 400;
  line-height: 1.75;
  font-feature-settings: "kern" 1, "liga" 1;
  text-rendering: optimizeLegibility;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  position: relative;
}

main { flex: 1; }

main, .site-header, .site-footer { position: relative; z-index: 1; }

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 600;
  color: var(--ivory);
  line-height: 1.1;
  margin: 0;
}

p { margin: 0; }
p + p { margin-top: 1.2em; }

img { max-width: 100%; height: auto; display: block; }

::selection { background: var(--accent); color: var(--ink); }

/* ---------- links, focus ---------- */

a {
  color: var(--ivory);
  text-decoration: underline;
  text-decoration-color: var(--line-strong);   /* links read as links at rest, not only on hover */
  text-decoration-thickness: 1px;
  text-underline-offset: 0.25em;
  transition: text-decoration-color 300ms var(--ease), color 300ms var(--ease);
}

a:hover { text-decoration-color: var(--accent-hover); }

:focus-visible {
  outline: 1px solid var(--accent);
  outline-offset: 4px;
  border-radius: 1px;
}

.skip-link {
  position: absolute;
  left: var(--gutter);
  top: -4rem;
  z-index: 10;
  background: var(--ink);
  padding: 0.5rem 1rem;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  transition: top 200ms var(--ease);
}

.skip-link:focus { top: 1rem; }

/* ---------- shared vocabulary ---------- */

.wordmark {
  font-family: var(--font-display);
  font-weight: 500;
  color: var(--ivory);
  text-decoration: none;
  font-size: 1.75rem;   /* Cormorant's small x-height reads under its set size — the masthead needs the extra step */
  letter-spacing: 0.005em;
}

.wordmark:hover { text-decoration: none; color: var(--ivory); }

.accent { color: var(--accent); }

.kicker {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.22em;
  color: var(--muted);
  margin: 0;
}

.mono {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  letter-spacing: 0.02em;
}

/* inline addressables — the pack sets email, phone and URLs in mono even mid-
   prose. 0.9em seats the mono's larger eye at the body copy's apparent size so
   the switch reads as a shift in register, not in scale */
.prose-addr {
  font-family: var(--font-mono);
  font-size: 0.9em;
  letter-spacing: 0.01em;
}

.hairline { border: 0; border-top: 1px solid var(--rule); margin: 0; }

/* ---------- header, nav ---------- */

.site-header {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: clamp(1.5rem, 4vh, 2.5rem) var(--gutter);
  max-width: 74rem;
  margin: 0 auto;
  width: 100%;
}

.site-nav { display: flex; gap: clamp(1.25rem, 3vw, 2.5rem); }

.site-nav a {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--muted);
  text-decoration: none;
  position: relative;
  transition: color 300ms var(--ease);
}

.site-nav a:hover { color: var(--ivory); }

.site-nav a[aria-current="page"] { color: var(--ivory); }

.site-nav a[aria-current="page"]::before {
  content: "";
  position: absolute;
  left: 50%;
  top: -0.7rem;
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: var(--accent);
  transform: translateX(-50%);
}

/* ---------- the menu trigger (phones) ------------------------------------
   A minimal two-bar mark. Hidden by default; the small-screen block near the
   foot of this file reveals it (only with JS) and folds the three links into
   a quiet dropdown, so a phone's band stays a clean wordmark-and-mark row.
   Without JS the inline nav stays, so the menu still works with no script. */

.nav-toggle {
  display: none;
  align-self: center;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  /* the hit box is a full 44px (2.75rem) touch target — Apple's HIG / WCAG 2.5.8
     floor — while the two bars stay their small 1.35rem selves; only the tappable
     area grows, so the thumb has room the eye needn't see */
  width: 2.75rem;
  height: 2.75rem;
  margin-right: -0.65rem;   /* the bars sit on the gutter line, not the tap box's edge */
  padding: 0;
  border: 0;
  background: none;
  color: var(--ivory);
  cursor: pointer;
  pointer-events: auto;   /* the journey band is pointer-events:none; the trigger opts back in */
}

.nav-toggle-bar {
  display: block;
  width: 1.35rem;
  height: 1px;
  background: currentColor;
  transition: transform 260ms var(--ease), opacity 260ms var(--ease);
}

/* open: the two bars cross into a close mark, warmed to the accent */
.site-header.nav-open .nav-toggle { color: var(--accent); }
.site-header.nav-open .nav-toggle-bar:first-child { transform: translateY(0.23rem) rotate(45deg); }
.site-header.nav-open .nav-toggle-bar:last-child  { transform: translateY(-0.23rem) rotate(-45deg); }

/* ---------- layout ---------- */

.container {
  max-width: 74rem;
  margin: 0 auto;
  padding-left: var(--gutter);
  padding-right: var(--gutter);
  width: 100%;
}

.section { margin-top: var(--section-gap); }
.section:first-child { margin-top: clamp(3rem, 8vh, 6rem); }

/* ---------- editorial blocks ---------- */

.block {
  border-top: 1px solid var(--rule);
  padding-top: clamp(1.75rem, 4vh, 2.75rem);
  padding-bottom: clamp(1.75rem, 4vh, 2.75rem);
}

.block:last-child { padding-bottom: 0; }

.block h2, .block h3 {
  font-size: clamp(1.9rem, 3.8vw, 2.7rem);
  margin-top: 1.1rem;
}

.block p:not(.kicker) {
  color: var(--body);
  max-width: var(--measure);
  margin-top: 1.2rem;
}

@media (min-width: 56rem) {
  .block {
    display: grid;
    grid-template-columns: 15rem 1fr;
    column-gap: 3rem;
  }
  .block .kicker { padding-top: 0.9rem; }
  .block h2, .block h3 { margin-top: 0; }
}


/* ---------- diagrams (the brand's second voice) --------------------------
   Minimal line drawings in the LinkedIn artwork style: 1px ivory strokes,
   one jade element, mono micro-labels. Strokes draw in on reveal. */

.diagram {
  width: 100%;
  max-width: 15rem;
  height: auto;
  margin-top: 1.75rem;
  overflow: visible;
}

.diagram .stroke,
.diagram .stroke-jade,
.diagram .stroke-line {
  fill: none;
  stroke-width: 1;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.diagram .stroke      { stroke: var(--ivory); opacity: 0.85; }
.diagram .stroke-jade { stroke: var(--accent); }
.diagram .stroke-line { stroke: var(--line); }

.diagram .diag-label {
  font-family: var(--font-mono);
  font-size: 8px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  fill: var(--muted);
}

/* draw-in on reveal (JS present, motion allowed) */
.js .reveal .diagram .draw {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  transition: stroke-dashoffset 900ms var(--ease) 250ms;
}

.js .reveal.is-visible .diagram .draw { stroke-dashoffset: 0; }

.js .reveal .diagram .diag-label {
  opacity: 0;
  transition: opacity 500ms var(--ease) 900ms;
}

.js .reveal.is-visible .diagram .diag-label { opacity: 1; }

/* ---------- the stage (about): the word broken open by the reader --------
   A sticky viewport across ~220vh of scroll. Scrub progress (set by JS as
   custom properties) drives the apostrophe in, opens the gap, blooms the
   jade, and raises the line. Defaults are the finished state, so the page
   is complete without JS and under reduced motion. */

.stage {
  height: 180vh;
  /* the display composition sits on the gutter line at every width,
     sharing the masthead's left edge — the reading column resumes below */
  padding-left: var(--gutter);
  padding-right: var(--gutter);
  --pa: 1;   /* apostrophe + gap */
  --pj: 1;   /* jade bloom */
  --pl: 1;   /* the line beneath */
}

/* rewound starting state, only when the scrub is active (html.scrub) */
.scrub .stage { --pa: 0; --pj: 0; --pl: 0; }

@supports (height: 100svh) {
  .stage { height: 180svh; }
}

.stage-sticky {
  position: sticky;
  top: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* seat the word against the island: the middle of the l rides the middle
     of the west bayline. The padding lowers the centred composition by half
     its own value, and 5vh lands the letter on the line at any height */
  padding-top: clamp(2rem, 5vh, 4rem);
}

@supports (height: 100svh) {
  .stage-sticky { min-height: 100svh; }
}

.stage-word {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(3rem, 12vw, 10rem);
  letter-spacing: -0.01em;
  line-height: 1;
  color: var(--ivory);
  margin-top: 1.6rem;
}

.sw-im { color: var(--accent); }

@supports (color: color-mix(in srgb, red 50%, blue)) {
  .sw-im {
    color: color-mix(in srgb, var(--ivory) calc((1 - var(--pj)) * 100%),
                              var(--accent) calc(var(--pj) * 100%));
  }
}

.sw-apos, .sw-gap {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
}

.sw-apos { max-width: calc(var(--pa) * 0.35em); opacity: var(--pa); }
.sw-gap  { max-width: calc(var(--pa) * 0.22em); }
.sw-gap::before { content: "\00a0"; }

.stage-line {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 500;
  font-size: clamp(1.3rem, 2.4vw, 1.75rem);
  color: var(--muted);
  margin-top: 2.2rem;
  opacity: var(--pl);
  transform: translateY(calc((1 - var(--pl)) * 14px));
}

.stage-hint {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--muted);
  margin-top: 2.4rem;   /* the survey hint's rhythm — no dead band at rest */
  opacity: calc(1 - var(--pa));
}

@media (max-width: 40rem) {
  .stage { height: 150vh; }
}

/* ---------- page headline (about, contact) ---------- */

.page-head h1 {
  font-size: clamp(2.6rem, 6.5vw, 4.5rem);
  max-width: 12em;
}

/* under the journey's fixed band the header no longer holds space in the
   flow: a page that opens with prose (contact, 404) seats its headline the
   same distance below the menu the static header used to give it */
body.journey .page-head,
body.journey .lost { margin-top: calc(var(--header-h) + clamp(3rem, 8vh, 6rem)); }

.page-head .kicker { margin-bottom: 1.4rem; }

/* the privacy notice opens with more light than the contact sheet: legal
   prose reads as calm only when the kicker, headline and lede each get a
   clear step of air, and the lede itself carries a taller line */
.notice-head .kicker { margin-bottom: 1.9rem; }

.notice-lede {
  color: var(--body);
  max-width: var(--measure);
  margin-top: 2.4rem;
  line-height: 1.75;
}

.notice-lede + .notice-lede { margin-top: 1.1rem; }

/* ---------- the record book (station 05) ---------------------------------
   Actual entries, ruled like a surveyor's field book — not prose. */

.record-book {
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: 34rem;
  font-family: var(--font-mono);
}

.record-book li {
  display: grid;
  grid-template-columns: 9.5rem 1fr;
  gap: 1.5rem;
  align-items: baseline;
  padding: 0.85rem 0;
  /* ruled like a surveyor's field book — a quiet, warm hairline. The jade is kept off
     the ruling and spent on the entries that are the proof (the accented outcomes),
     so the colour marks meaning, not scaffolding. */
  border-bottom: 1px solid var(--rule);
}

.record-book li:first-child { border-top: 1px solid var(--rule); }

.rb-item {
  font-size: 0.6875rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
}

.rb-entry {
  font-size: 0.875rem;
  line-height: 1.6;
  color: var(--body);
}

.rb-entry b { font-weight: 400; }

@media (max-width: 720px) {
  /* Phone: the field book stacks to one column. Keep it a ruled ledger, not a mono
     stream — the entries tighten, the labels lift enough to hold over the bright
     relief, and the hairline is brought up so each row reads as a discrete entry. */
  .record-book li {
    grid-template-columns: 1fr;
    gap: 0.35rem;
    padding: 0.95rem 0;
    border-bottom-color: rgba(241, 239, 232, 0.22);
  }
  .record-book li:first-child { border-top-color: rgba(241, 239, 232, 0.22); }
  .rb-item { color: #A5A49C; }
  .rb-entry { font-size: 0.8125rem; line-height: 1.45; letter-spacing: -0.01em; }
  /* phone only: drop the meta "This chart · built by impossible.je" entry so the
     field book breathes — the desktop ledger keeps all six. Scoped to Station 06
     so The legend's own two-row ledger is left intact */
  .station[aria-label="The record"] .record-book li:last-child { display: none; }
}

/* ---------- about ---------- */

.about-grid {
  display: grid;
  gap: clamp(3rem, 7vw, 5rem);
}

@media (min-width: 56rem) {
  .about-grid { grid-template-columns: minmax(0, 1fr) 18rem; align-items: start; }
}

/* stacked on mobile, the mount centres under the prose rather than hugging the left edge */
@media (max-width: 55.99rem) {
  .portrait { margin-left: auto; margin-right: auto; }
}

.portrait {
  border: 1px solid var(--rule);
  padding: 0.75rem;
  max-width: 18rem;
  margin: 0;
  position: sticky;
  top: 8.5rem;   /* clear of the fixed header's ink band */
  /* the mount sits a hair above the sheet's ink — still darker than the island so it
     quiets the linework beneath, but distinct from the sea so the panel keeps its own
     edge on any ground, held by an ivory hairline rather than dissolving into the sheet */
  background: #242422;
  /* the print lies proud of the sheet: a tight contact edge and a soft fall,
     true black so it reads against the ink by quieting the linework beneath */
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.32), 0 0.75rem 1.75rem rgba(0, 0, 0, 0.2);
}

/* fully monochrome — the print belongs to the ink-and-ivory sheet, and jade
   stays the page's only colour. The source file keeps its colour for use
   off the chart. */
.portrait img { filter: grayscale(1); }

.portrait figcaption {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
  margin-top: 0.75rem;
}

/* the sitter's name takes the display hand, as the pack rules and the
   survey's identity strip already practise; the role stays archival mono */
.portrait-name {
  display: block;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.4rem;
  line-height: 1.2;
  letter-spacing: 0.01em;
  text-transform: none;
  color: var(--ivory);
  margin-bottom: 0.4rem;
}

/* the record's instruments under the print: quiet ivory marks in the
   monochrome column — jade is saved for the reach itself */
.portrait-contact {
  display: flex;
  align-items: center;
  flex-wrap: wrap;   /* the revealed number takes its own line if the row runs short */
  gap: 0.75rem;
  margin-top: 0.9rem;
}

/* each instrument is a drawn disc, its glyph a quiet ivory mark in the monochrome
   print; jade is saved for the reach — the disc warms to the accent only when the
   hand goes for it, so the colour means contact, not decoration */
.portrait-link {
  display: grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: transparent;
  color: var(--ivory);
  text-decoration: none;
  transition: color 300ms var(--ease), border-color 300ms var(--ease);
}

.portrait-link:hover,
.portrait-link:focus-visible { color: var(--accent); border-color: var(--accent); }

/* the third instrument is a button, not a link: same disc, no chrome of its own */
button.portrait-link { padding: 0; cursor: pointer; }

/* the direct line stays off the sheet until asked for, then reads as every
   addressable thing does: mono, beside the instruments that revealed it */
.portrait-number {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  color: var(--ivory);
  text-decoration-color: var(--line);
}

.portrait-link svg { width: 0.9rem; height: 0.9rem; display: block; fill: currentColor; }

/* the one other italic moment: the turn itself, set as a pulled line */
.pull-line {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 500;
  font-size: clamp(1.4rem, 2.4vw, 1.75rem);
  line-height: 1.35;
  color: var(--ivory);
  max-width: var(--measure);
  margin-top: 1.4rem;
}

/* ---------- closing invitation ---------- */

.closing {
  /* the invitation holds its own water at the sheet's end: clear of the prose
     above and the footer below, one moment alone in the frame. No hairline of
     its own — the footer's rule is the only line that closes the sheet.
     (The journey footer carries no margin, so the closing holds the gap.) */
  padding-top: clamp(4rem, 18vh, 11rem);
  padding-bottom: var(--footer-lead);   /* shared lead-in, so every sheet closes at one depth */
}

.closing p {
  font-family: var(--font-display);
  font-size: clamp(1.6rem, 3.2vw, 2.3rem);
  font-weight: 500;
  line-height: 1.25;
  max-width: 22em;
}

/* the invitation carries the jade at rest — the one warm mark closing the sheet,
   ruled with a full ivory underline — then swaps on hover: the text cools to ivory
   as the accent moves down into the underline */
.closing a { color: var(--accent); text-decoration-color: var(--ivory); }
.closing a:hover { color: var(--ivory); }

/* ---------- contact form: hairlines, not boxes ---------- */

.contact-lede {
  color: var(--body);
  max-width: var(--measure);
  margin-top: 1.6rem;
}

.contact-form {
  margin-top: clamp(2.5rem, 6vh, 4rem);
  max-width: 38rem;
  display: grid;
  gap: 2.25rem;
}

.field { display: grid; gap: 0.35rem; }

.field label {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--muted);
}

.field input,
.field textarea {
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--line-strong);   /* the input baseline reads clearly as a field at rest */
  border-radius: 0;
  color: var(--ivory);
  font-family: var(--font-sans);
  font-size: 1.0625rem;
  line-height: 1.6;
  padding: 0.65rem 0;   /* lifts the field box past the 48px touch-target floor without a visible change */
  width: 100%;
  transition: border-color 300ms var(--ease);
}

.field textarea { resize: vertical; min-height: 8rem; }

.field input:focus-visible,
.field textarea:focus-visible {
  outline: none;
  border-bottom-color: var(--accent);
}

.field input::placeholder,
.field textarea::placeholder { color: var(--muted); opacity: 0.7; }

/* keep autofill from painting the fields white */
.field input:-webkit-autofill {
  -webkit-box-shadow: 0 0 0 1000px var(--ink) inset;
  -webkit-text-fill-color: var(--ivory);
}

/* consent — a square drawn in the sheet's hairline; the mark inside is jade
   because, like the portrait instruments, it only appears when the hand acts */
.field-consent {
  grid-template-columns: auto 1fr;
  column-gap: 0.75rem;
  align-items: start;
}

.field-consent input {
  appearance: none;
  -webkit-appearance: none;
  width: 1.05rem;
  height: 1.05rem;
  margin-top: 0.15rem;
  border: 1px solid var(--line-strong);
  border-radius: 0;
  background: transparent;
  cursor: pointer;
  display: grid;
  place-content: center;
  transition: border-color 300ms var(--ease);
}

.field-consent input:hover,
.field-consent input:focus-visible { outline: none; border-color: var(--accent); }

.field-consent input::before {
  content: "";
  width: 0.55rem;
  height: 0.55rem;
  background: var(--accent);
  transform: scale(0);
  transition: transform 200ms var(--ease);
}

.field-consent input:checked::before { transform: scale(1); }

.field-consent label {
  font-family: var(--font-sans);
  font-size: 0.875rem;
  line-height: 1.6;
  text-transform: none;
  letter-spacing: normal;
  color: var(--muted);
  cursor: pointer;
  max-width: var(--measure);
}

.field-consent label a { color: var(--ivory); text-decoration-color: var(--line-strong); }
.field-consent label a:hover { color: var(--accent-hover); }

.submit {
  justify-self: start;
  background: none;
  /* the sheet's one action carries the accent, exactly as the survey's arrival CTA does */
  border: 1px solid var(--accent);
  border-radius: 0;
  color: var(--ivory);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  padding: 0.9rem 2.2rem;
  cursor: pointer;
  transition: border-color 300ms var(--ease), color 300ms var(--ease);
}

.submit:hover { border-color: var(--accent-hover); }
.submit:focus-visible { outline: 1px solid var(--accent); outline-offset: 4px; }
.submit:disabled { color: var(--muted); border-color: var(--line); cursor: default; }

/* honeypot — off-canvas, not display:none, so bots still fill it */
.hp-field {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form-note {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  color: var(--accent);
}

.mailto-fallback {
  margin-top: 2.5rem;
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  color: var(--muted);
}

.mailto-fallback a { color: var(--ivory); text-decoration-color: var(--line); }

/* at full scroll the sign-off line stands clear of the footer's feather */
body.journey .mailto-fallback { margin-bottom: var(--footer-lead); }   /* shared lead-in, so every sheet closes at one depth */

/* ==========================================================================
   THE SURVEY — the Channel Islands chart
   chart.js draws the sheet (real OSM / EMODnet / SRTM geometry) into the
   fixed .chart-stage behind the page. On the home page (body.journey) the
   stations bind camera keyframes to native scroll; other pages carry a
   still plate crop. All chart furniture waits for html.charted, so every
   page reads complete without JS.
   ========================================================================== */

.chart-stage { position: fixed; inset: 0; overflow: hidden; z-index: 0; }
/* every charted sheet's ground, given depth without a shift in hue: a soft warm vignette
   lifts the centre a touch and sinks the edges, so the water reads with volume rather than a
   dead-flat fill. Applied to all .chart-stage — the survey and the plate pages alike — so the
   sheets share one ground; the fixed menu and footer sit above this at their own flat #141413,
   so the bands stay clean chrome framing the sheet. */
.chart-stage {
  background:
    radial-gradient(125% 95% at 50% 43%,
      #1A1A18 0%,
      #141413 45%,
      #0E0E0C 100%);
}
/* a fine grain over the sheet: chart-paper tactility, and it breaks up any banding on the
   dark ground. Sits over the chart's own line-work but under the menu, footer and reading
   column (all at z-index ≥ 1), so only the map surface is textured. */
.chart-stage::after {
  content: "";
  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='150' height='150'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  /* the grain sits on top of the camera group (#cam is composited via will-change: transform).
     A non-composited overlay over a composited, transforming layer forces the browser to
     de-composite and repaint that layer every frame — and at high zoom the map is enormous, so
     that per-frame repaint was the scroll flicker. Give the grain its own compositor layer so
     the map stays composited and the transform stays cheap. */
  transform: translateZ(0);
}
.chart-stage svg { position: absolute; inset: 0; width: 100%; height: 100%; }
#cam { will-change: transform; }

/* chart lettering: serif for the natural, mono for the survey apparatus */
.chart-stage text.island { font-family: var(--font-display); fill: var(--ivory); }
/* names take their presence from tracking, not bulk — the sheet, not the label,
   is the subject. Held below the body copy's weight: the names are context,
   and the coastline outranks them */
.chart-stage text.big  { font-size: 20px; opacity: 0.28; letter-spacing: 14px; }
.chart-stage text.mid  { font-size: 13.5px; opacity: 0.25; letter-spacing: 9px; }
.chart-stage text.sml  { font-size: 11px; opacity: 0.23; letter-spacing: 5.5px; }
.chart-stage text.tiny { font-size: 9px;  opacity: 0.21; letter-spacing: 3.5px; }
.chart-stage text.reef { font-family: var(--font-display); font-style: italic; font-size: 11px; fill: var(--muted); letter-spacing: 1.2px; }
.chart-stage text.note { font-family: var(--font-mono); font-size: 6.5px; letter-spacing: 1.6px; fill: var(--muted); opacity: 0.85; }
.chart-stage text.water { font-family: var(--font-display); font-style: italic; font-size: 12px; fill: var(--muted); opacity: 0.9; letter-spacing: 1.2px; }
.chart-stage text.water.big { font-size: 13px; letter-spacing: 4px; opacity: 0.7; }
.chart-stage text.water.sm { font-size: 9px; letter-spacing: 1px; }
.chart-stage text.town { font-family: var(--font-sans); font-size: 7.5px; letter-spacing: 1.6px; fill: var(--muted); }
.chart-stage text.spot { font-family: var(--font-mono); font-size: 8.5px; fill: var(--ivory); opacity: 0.6; }
.chart-stage text.gridlbl { font-family: var(--font-mono); font-size: 9px; fill: var(--muted); opacity: 0.5; }
.chart-stage .lbl { transition: opacity 500ms ease; }

/* the Les Platons beacon (station 07): the route's destination, lit and pulsing —
   a soft jade glow breathing while a ping ring radiates out and fades. The summit
   signalling back as the survey arrives. Scales with the camera on its own group. */
.beacon-glow, .beacon-ping { transform-box: fill-box; transform-origin: center; }
.beacon-glow { animation: beaconBreathe 3s ease-in-out infinite; }
.beacon-ping { animation: beaconPing 3s ease-out infinite; }
@keyframes beaconBreathe {
  0%, 100% { opacity: 0.35; }
  50%      { opacity: 0.9; }
}
@keyframes beaconPing {
  0%   { transform: scale(1);   opacity: 0.65; }
  70%  { opacity: 0; }
  100% { transform: scale(3.6); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .beacon-glow { animation: none; opacity: 0.55; }
  .beacon-ping { animation: none; opacity: 0; }
}

/* the graticule dives under the header like the labels do — a ruler-straight
   northing is the one mark that still ghosts through the band's 0.94 ink hold */
#gGrid {
  -webkit-mask-image: linear-gradient(to bottom, transparent 102px, #000 165px);
  mask-image: linear-gradient(to bottom, transparent 102px, #000 165px);
}

/* sheet furniture: the frame, corner coordinates, instruments */
.chart-frame {
  position: fixed;
  inset: 14px;
  border: 1px solid rgba(93, 202, 165, 0.11);   /* the map edge is the water contour lines' shade: jade at the water's 0.11 effective (plate override below tops it back up under the frame's 0.55 fade) */
  pointer-events: none;
}

.chart-corner {
  position: fixed;
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.08em;
  color: var(--muted);
  pointer-events: none;
}

.c-tl { top: 20px; left: 22px; }
.c-tr { top: 20px; right: 22px; text-align: right; }
.c-bl { bottom: 20px; left: 22px; }
.c-br { bottom: 20px; right: 22px; text-align: right; }

/* the traverse: a hairline on the frame's east edge that plots the reader's
   own progress down the sheet — the instrument answering "how far remains" */
.chart-progress {
  position: fixed;
  top: 14px;
  bottom: 14px;
  right: 14px;
  width: 1px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 450ms var(--ease);
}

.charted .chart-progress { opacity: 1; }

.chart-progress b {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: calc(var(--traverse, 0) * 100%);
  background: var(--ivory);
  opacity: 0.3;
}

.chart-hud {
  position: fixed;
  top: 7.5rem;
  right: var(--gutter);   /* the instruments share the menu's right edge — one column, surveyed */
  text-align: right;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  color: var(--muted);
  pointer-events: none;
  line-height: 2;
}

.chart-hud b { color: var(--ivory); font-weight: 400; }
.chart-hud .peak { color: var(--ivory); }   /* ivory so the readout doesn't double the map's jade Les Platons pointer */

/* furniture waits for the chart itself */
/* 450ms: slow enough for the charted fade-in, quick enough for corners yielding under cards */
.chart-frame, .chart-corner, .chart-hud { opacity: 0; transition: opacity 450ms var(--ease); }
.charted .chart-frame { opacity: 0.9; }
.charted .chart-corner, .charted .chart-hud { opacity: 1; }

/* No depth wash over the map: the sheet's deep water sits at the brand charcoal
   (--ink) so it matches the menu and footer bars exactly. The map already reads
   as a layer behind the narrative — it lives in the fixed chart stage (z-index 0)
   under the station cards, and the still plates dim it to 0.4 — so it needs no
   tint of its own, and an off-brand warm wash would only pull it off charcoal. */

/* ----- the chart's land, on the charcoal ladder -----
   base: the whole sheet reads as one land tone (Jersey's), which the still
   plates below then dim uniformly behind the prose */
.chart-stage #gFrPlate path,
.chart-stage #gPlates path,
.chart-stage #gJerCoast path,
.chart-stage #gJerPlate path { fill: var(--land); }

/* the survey sheet reads Jersey as the subject: the other islands recede to
   --land-far and the French mainland further still to --land-france, so the eye
   settles on Jersey (which keeps --land, drawn crisp by gJerPlate) and its jade
   summit. Scoped to the journey (:not(.plate)) so the still plates stay uniform. */
.chart-stage:not(.plate) #gPlates path { fill: var(--land-far); }
.chart-stage:not(.plate) #gFrPlate path { fill: var(--land-france); }

/* still plates (about, contact, 404): the sheet recedes behind the prose.
   The dim is applied to the map, graticule and place-labels rather than the whole
   svg, so gMarks stays at full strength and the jade Les Platons trig — the one
   bright signature on the sheet — punches through the recede (its soundings are
   gated off at plate zoom, so only the summit mark shows). */
.chart-stage.plate #cam,
.chart-stage.plate #gGrid,
.chart-stage.plate #gGridLbl,
.chart-stage.plate #gLabels { opacity: 0.4; }

/* on a still plate that features Jersey (about), Jersey is lifted out of the
   0.4 recede: gJerPlate is drawn at a raised fill that, composited through the
   svg's 0.4 dim over the ink, lands back on --land. Derived from
   0.4·F + 0.6·#1F1F1D = --land (#3B3A36), so F ≈ #65635C. Contact and 404 carry
   no gJerPlate, so they stay uniformly dim. */
.chart-stage.plate #gJerPlate path { fill: #65635C; }
.charted .chart-stage.plate .chart-frame { opacity: 0.55; }
/* the plate frame carries a 0.55 fade, so its border is topped up to 0.2 to
   land back on the water's 0.11 effective shade (0.2 × 0.55 ≈ 0.11) */
.charted .chart-stage.plate .chart-frame { border-color: rgba(93, 202, 165, 0.2); }
.charted .chart-stage.plate .chart-corner { opacity: 0.6; }

/* ----- the journey (home) — and the shared chrome any charted page adopts
   via body.journey: the fixed header band, the gutter-aligned menu, the
   compact ink footer. One set of instruments across the whole sheet. ----- */

body.journey .site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  max-width: none;
  z-index: 20;
  /* the header owns the top edge: chart labels and grid numerals pass
     beneath this, never through the menu */
  /* a definitive menu bar: flat opaque ink, ruled off from the sheet by a
     single hairline — a clear edge, not a dissolve */
  background: var(--ink);
  border-bottom: 1px solid rgba(93, 202, 165, 0.11);   /* the water contour lines' exact shade: jade at 0.11, undimmed like this band */
  /* a menu bar, not a masthead: shallow, even padding keeps the band compact.
     The fixed band owns the very top edge, so on a notched phone it must clear the
     status-bar / notch: fold the device's top safe-area into the top padding, and
     let the side gutters grow to any left/right inset (landscape notch). env() is 0
     on devices with no cutout, so ordinary phones are unchanged; the band is opaque
     ink, so the inset area fills cleanly rather than leaving a bare strip. */
  padding-top: calc(clamp(1rem, 2.2vh, 1.4rem) + env(safe-area-inset-top));
  padding-bottom: clamp(1rem, 2.2vh, 1.4rem);
  padding-left: max(var(--gutter), env(safe-area-inset-left));
  padding-right: max(var(--gutter), env(safe-area-inset-right));
  pointer-events: none;
}

body.journey .site-header a { pointer-events: auto; }

/* with the header holding the top edge, the top corner coordinates retire;
   the bottom pair keeps the sheet referenced */
body.journey .c-tl, body.journey .c-tr { display: none; }

.survey { position: relative; z-index: 2; }

.station {
  min-height: 135vh;
  display: flex;
  align-items: flex-end;
  padding: 0 var(--gutter) 18vh;
}

.station.hero { min-height: 100vh; align-items: center; padding-bottom: 0; }

@supports (height: 100svh) {
  .station { min-height: 135svh; }
  .station.hero { min-height: 100svh; }
}

.station-card { max-width: 34rem; position: relative; }

/* the narrative floats above the washed map — a per-glyph shadow lifts the type
   clear of the chart's linework, no panel behind it. Kept tight to each letter so
   it reads precise rather than pooled: a near-solid 1px edge, a slight grounding
   drop, and a close dark halo hugging the glyph — all within a few pixels, so the
   type stays crisp even where the relief runs light behind it. */
/* the About stage runs its kicker, wordmark and reveal lines straight over the
   bright island the same way the survey cards float over the chart — so it earns
   the same per-glyph inking. Without it the muted-grey kicker and lines drop below
   legible contrast where the relief runs light beneath them. */
body.journey .station-card,
body.journey .stage {
  text-shadow:
    0 0 1px rgba(0, 0, 0, 1),
    0 1px 2px rgba(0, 0, 0, 0.95),
    0 0 3px rgba(0, 0, 0, 0.85);
}

/* a pool of ink under the headline — the chart's ivory linework otherwise competes
   with the ivory text. Feathered to nothing well before its edge so it reads as clear
   water, not a panel. (The closing invitation runs over the open sea, so it needs none.)
   Applied at every width: a phone runs this same feathered pool behind the headline,
   so the plate headlines are inked the way the desktop sheet inks them, not with a
   flat box. */
body.journey .page-head { position: relative; }
body.journey .page-head::before {   /* the plate pages' headline pools the water */
  content: "";
  position: absolute;
  inset: -7rem -9rem;
  /* matched to the deeper #141413 canvas so it stays invisible over the ground
     (no glow behind the left-column type) and only pools where the chart runs under */
  background: radial-gradient(closest-side, rgba(20, 20, 19, 0.88) 30%, rgba(20, 20, 19, 0.55) 60%, rgba(20, 20, 19, 0) 100%);
  z-index: -1;
  pointer-events: none;
}
/* the page-head box spans the container: pin its pool to the measure of the
   type instead, the same water the 34rem cards displace */
body.journey .page-head::before { right: auto; width: 54rem; }
/* Contact's type sits over the open canvas (the chart is off to the right), so it
   needs no pool at all — and pooling here only dims the grid under the text into a
   faint halo. Drop it; privacy keeps its pool, its prose running over the chart. */
body.full-relief .page-head::before { display: none; }

.station h1 {
  font-size: clamp(2.7rem, 6vw, 4.75rem);
  font-weight: 500;
  line-height: 1.1;
  margin: 0.9rem 0 1.2rem;
}

.station h2 {
  font-size: clamp(1.9rem, 4vw, 2.75rem);
  font-weight: 500;
  line-height: 1.15;
  margin: 0.9rem 0 1.1rem;
}

.station-lede {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 500;
  font-size: clamp(1.3rem, 2.4vw, 1.7rem);
  color: var(--body);
}

/* the persuasion copy holds the brand's long-form grey, not the furniture grey —
   muted is for apparatus (kickers, coordinates); the argument itself must carry */
.station-body { color: var(--body); max-width: 30rem; margin-top: 1rem; }
.station-body b { color: var(--ivory); font-weight: 400; }
.station-body b.accent { color: var(--accent); }   /* the thesis line carries the signature, not just emphasis */

.station-hint {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--muted);
  margin-top: 2.4rem;
}

/* the sounding line (hero): the survey begins by dropping a lead over the side.
   A plumb line falls from the scroll label and a jade sounding-weight descends
   it on a loop — the one moving mark that says "start here", drawn in the
   signature so the eye is carried down into the sheet. The label stays muted so
   the jade holds to a single point, not scattered over the words. */
.scroll-cue {
  margin-top: 2.4rem;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1rem;
}

.scroll-cue-label {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--muted);
}

.scroll-cue-plumb {
  position: relative;
  width: 1px;
  height: 40px;
  margin-left: 0.1rem;
  background: linear-gradient(to bottom, var(--line-strong), transparent);
}

/* the lead weight: a single jade point with a soft halo, dropped down the line */
.scroll-cue-plumb::after {
  content: "";
  position: absolute;
  left: -1.5px;
  top: 0;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 7px 1px rgba(93, 202, 165, 0.55);
  animation: sounding 2.6s var(--ease) infinite;
}

@keyframes sounding {
  0%   { transform: translateY(0);    opacity: 0; }
  12%  { opacity: 1; }
  72%  { opacity: 1; }
  100% { transform: translateY(37px); opacity: 0; }
}

/* still, but present: the weight rests at the line's head as a jade tick */
@media (prefers-reduced-motion: reduce) {
  .scroll-cue-plumb::after { animation: none; opacity: 1; transform: none; }
}

/* the arrival at Les Platons — long travel to the summit, but the card anchors
   to the station's end so the page's final resting position frames the whole
   sign-off: heading clear of the header band, CTA just above the title block */
.station.arrive {
  /* the summit still reads as the tallest climb (above the hero's 100 and near the
     135 reading stations), but the old 150 left ~750px of dead map above the sign-off
     on desktop — so the card, CTA and footer only surfaced at the very bottom and were
     easily missed. Trimmed so the payoff arrives sooner while the reveal is kept. */
  min-height: 115vh;
  align-items: flex-end;
  justify-content: center;   /* the terminus settles to the sheet's centre line */
  text-align: center;        /* the sign-off is the one centred beat — the survey arriving at its point */
  padding-bottom: var(--footer-lead);   /* shared lead-in, so every sheet closes at one depth */
}

@supports (height: 100svh) {
  .station.arrive { min-height: 115svh; }
}

.station.arrive .station-card { max-width: 40rem; }
.station.arrive .station-body { margin-left: auto; margin-right: auto; }

/* the sign-off card is the tallest on the sheet — tighter rhythm so it can sit
   beneath the header band on a laptop viewport. One gap must dominate: the break
   above the identity strip is ~3× the paragraph rhythm, so the heading block and
   the person read as two movements, with the name bound to Wayne's words below */
.station.arrive .station-body { margin-top: 0.85rem; }
.station.arrive .floor-id { margin: 1.2rem 0 0.8rem; }
/* the stacked portrait sits a touch smaller than the inline strip so the whole
   centred sign-off still clears the header band on a short laptop viewport */
.station.arrive .floor-avatar { width: 60px; height: 60px; }

.station-cta {
  display: inline-block;
  margin-top: 2rem;
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  letter-spacing: 0.12em;
  color: var(--ivory);
  text-decoration: none;
  border: 1px solid var(--muted);   /* visible at rest — the action must read against the linework */
  padding: 0.9rem 1.7rem;
  transition: border-color 300ms var(--ease);
}

.station-cta:hover { border-color: var(--accent); }

/* the survey's one action carries the sheet's accent at rest. At the summit it
   shares the frame with the jade Les Platons trig — the destination and the door,
   the two greens the whole journey earns. The HUD peak stays ivory so the readout
   doesn't add a third. */
/* a clear line's worth of air above the door, so the CTA sits a sentence lower than
   the invitation and reads as its own deliberate beat, not a tail of the paragraph */
.station.arrive .station-cta { border-color: var(--accent); margin-top: 3.2rem; }
.station.arrive .station-cta:hover { border-color: var(--accent-hover); }

/* the identity strip, as the artwork closes every piece */
.floor-id {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  margin: 2.2rem 0 1.6rem;
}

/* the terminus stacks its identity: the portrait crowns the name, the titles
   set beneath — a centred column closing the centred sheet */
.station.arrive .floor-id { flex-direction: column; align-items: center; justify-content: center; gap: 1rem; }
.station.arrive .floor-id > div { text-align: center; }
.floor-id > div { text-align: left; }

.floor-avatar {
  width: 72px;
  height: 72px;
  border-radius: 3px;   /* squared, not a circle — a mounted field-record portrait, not a memorial roundel */
  object-fit: cover;
  object-position: 50% 20%;
  filter: grayscale(100%);
  border: 1px solid var(--line);   /* the avatar stays ivory-and-line; jade is the trig's and the CTA's */
}

.floor-name {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1.6rem, 3vw, 2.1rem);
  line-height: 1.1;
}

.floor-role {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--muted);
  margin-top: 0.5rem;
}

/* the sheet's title block — ink runs full-bleed past the container.
   Compact on the journey: every pixel of footer pushes the sign-off card
   further past the sheet's end at full scroll. The shadow is offset one
   spread-length down so it carries the ink sideways and below only,
   leaving the sheet above clear for the feather */
/* the footer mirrors the header bar: a flat opaque band ruled off from the
   sheet by one definitive hairline at its chart-facing (top) edge — the same
   clean edge, not a dissolve. The two bars bracket the sheet identically. */
body.journey .site-footer {
  background: var(--ink);
  box-shadow: 0 100vmax 0 100vmax var(--ink);
  margin-top: 0;
  /* full-bleed like the header band so the rule runs edge to edge; the
     container cap is released, gutter padding still holds the credits in */
  max-width: none;
  border-top: 1px solid rgba(93, 202, 165, 0.11);   /* the water contour lines' exact shade: jade at 0.11, undimmed like this band */
  padding-top: clamp(1rem, 2.2vh, 1.4rem);
  /* the band runs to the very bottom edge, so fold in the home-indicator safe-area
     (0 on phones without one) — the credits never sit under the indicator bar */
  padding-bottom: calc(clamp(1rem, 2.2vh, 1.4rem) + env(safe-area-inset-bottom));
}

/* the border now carries the rule the header's border does; the internal
   <hr> would double it */
body.journey .site-footer .hairline { display: none; }
body.journey .footer-credits { margin-top: 1.25rem; }
body.journey .footer-credits:first-of-type { margin-top: 0; }

/* every charted sheet runs the same deep canvas: the body, menu bar and footer sit
   at #141413 (darker than the standard ink), so the survey, About and Contact share
   one ground. Applied via .journey — the shared chrome class on every charted page. */
html:has(body.journey) { background: #141413; }
body.journey { background: #141413; }
body.journey .site-header { background: #141413; }
body.journey .site-footer { background: #141413; box-shadow: 0 100vmax 0 100vmax #141413; }

.footer-credits {
  margin-top: 2rem;
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);   /* no opacity — at this size the ODbL line must hold 4.5:1 on ink */
}

.footer-credits a { color: var(--muted); }

.footer-credits + .footer-credits { margin-top: 0.5rem; }  /* imprint and chart credits read as one block */

@media (max-width: 720px) {
  .chart-hud { display: none; }     /* cards span the full width — no clear corner for instruments */
  .c-tl, .c-tr { display: none; }   /* the header owns the top edge */
  /* on a phone the reading column runs nearly full width, so the chart's place
     names sit unavoidably behind the type and the ink pool alone can't fully
     hold them off. Quiet the lettering here — the coastline and land silhouette
     still carry the sheet, and the words lead. This also settles the plate
     pages (contact, about), whose prose shares the same narrow column. */
  .chart-stage text.big  { opacity: 0.15; }
  .chart-stage text.mid  { opacity: 0.14; }
  .chart-stage text.sml  { opacity: 0.13; }
  .chart-stage text.tiny { opacity: 0.12; }
  .chart-stage text.water,
  .chart-stage text.water.big,
  .chart-stage text.water.sm { opacity: 0.5; }
  .chart-stage text.reef { opacity: 0.55; }
  .chart-stage.plate .chart-corner { display: none; }  /* plate pages have no card scrim to sit under */
  .station { padding: 0 1.5rem 16vh; min-height: 110vh; min-height: 110svh; }
  /* on a phone the chart is too sparse to carry a 135svh pause between
     stations: the narrative keeps walking. Hero and arrival keep their room. */
  .station.hero { min-height: 100vh; min-height: 100svh; }
  /* Bottom-anchoring is the desktop sheet's composition: the card seats low and
     the map fills the space above it. On a phone that map is mostly off-frame, so
     the same anchoring left ~550px of dead ground over each ~240px card — a whole
     empty screen before every reading beat, and the body running to the fold.
     Seat the survey cards in the middle of a shorter station instead: each message
     lands where the eye already rests, the relief breathing evenly above and below,
     and the scroll loses ~1000px of nothing. The top pad reserves the fixed header
     band (its ~77px, not the hero's generous --header-h) so the tallest card's
     kicker can never tuck up under the menu. Hero and arrival keep their own seats. */
  .station:not(.hero):not(.arrive) {
    align-items: center;
    min-height: 90vh;
    min-height: 90svh;
    padding-top: 5rem;
    padding-bottom: 1.5rem;
  }
  /* the sign-off is the tallest card on the sheet; on a phone it is taller than
     the gap between the fixed header and the footer, so bottom-anchoring tucked
     the kicker up under the menu bar. Let it flow from a clear seat below the
     header and scroll through to the CTA — the sign-off still lands last. */
  .station.arrive {
    min-height: auto;
    align-items: flex-start;
    padding-top: calc(var(--header-h) + clamp(2rem, 6vh, 4rem));
    /* the arrival is the last beat: the base .station's 16vh foot is breathing room
       for the *next* station, and there is none — it only stranded the footer a screen
       below the CTA, off the fold on shorter windows. Close the gap so the sign-off
       band follows the invitation directly. */
    padding-bottom: clamp(1.5rem, 5vh, 2.75rem);
  }
  /* on a phone the summit plate reads centred — the survey's one arrival, framed
     rather than ranged left like the reading stations before it */
  .station.arrive { text-align: center; }
  .station.arrive .station-body { margin-left: auto; margin-right: auto; }
  /* the identity strip stacks on a phone: portrait, then the name, then the role —
     one centred column rather than a portrait ranged alongside the words */
  .station.arrive .floor-id { flex-direction: column; gap: 0.9rem; }
  .station.arrive .floor-id > div { text-align: center; }

  /* the summit carries more than any other plate — headline, a paragraph, the
     identity strip and a second paragraph before the door. On a phone that stacked
     tight and read cramped. Open the rhythm so each beat lands as its own movement:
     a clear breath into the person, and a clearer one out to the invitation and the
     CTA. The card flows and scrolls, so the extra air costs nothing but room. */
  .station.arrive .station-body { margin-top: 1.5rem; }
  .station.arrive .floor-id { margin: 2.6rem 0 1.8rem; }
  .station.arrive .station-cta { margin-top: 2.6rem; }

  /* A discreet ground-quieter behind the reading column. On a phone the survey's
     body copy runs full width straight over Jersey's bright hillshade — the
     lightest thing on the sheet — and the per-glyph shadow alone leaves the ivory
     competing with grey. Pool a soft, low wash of the canvas ground under each
     card: keyed to the #141413 sheet (rgba(20,20,19)) it stays invisible over the
     dark sea and only settles where the brighter relief shows through, easing the
     land back a stop or two so the words lead — and feathered to nothing well
     before its edge, so no box is ever drawn. Deliberately light (0.6 core, not a
     scrim): the hillshade, coastline and marks still read clearly through it. The
     per-glyph shadow above still hugs each letter; this only quiets the ground. */
  body.journey .station-card::before {
    content: "";
    position: absolute;
    z-index: -1;
    inset: -4rem -1.5rem -4.5rem -1.5rem;
    background: radial-gradient(120% 82% at 34% 46%,
      rgba(20, 20, 19, 0.6) 24%,
      rgba(20, 20, 19, 0.38) 50%,
      rgba(20, 20, 19, 0.14) 72%,
      transparent 88%);
    /* The radial feathers the sides (and there they fall over dark sea, unseen), but its
       top and bottom run to the box edge still part-opaque, drawing a faint horizontal
       line where the pool crosses the bright relief. A vertical mask dissolves those two
       edges to nothing in the margins above and below the card, so no edge is ever drawn
       while the solid core still sits behind every line of type. */
    -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 16%, #000 84%, transparent 100%);
            mask-image: linear-gradient(to bottom, transparent 0, #000 16%, #000 84%, transparent 100%);
    pointer-events: none;
  }
  /* The hero alone frames the whole archipelago, so Jersey's north coast and its
     spot-height label (△136 · Les Platons) rise right into the opening lines. The
     shared pool above is kept light so terrain reads through it — but at the first
     impression that lets the high-chroma jade label tangle with the last line.
     Deepen the hero's pool alone: enough to absorb the label behind the words,
     still radial-feathered and top/bottom-masked so the island reads around them. */
  .station.hero .station-card::before {
    background: radial-gradient(120% 82% at 34% 46%,
      rgba(20, 20, 19, 0.92) 32%,
      rgba(20, 20, 19, 0.72) 56%,
      rgba(20, 20, 19, 0.32) 76%,
      transparent 92%);
  }
  /* The summit is the busiest plate: the camera zooms right onto Les Platons, so its
     spot-height label and the survey's own lettering crowd straight through the
     centred sign-off. Give the arrival the hero's deeper pool — centred here, since
     the terminus card is centre-aligned — so the map recedes behind the words. The
     celebrated jade trig and the CTA still sit in the clear air below the pooled
     type, undimmed: the two greens the journey earns are kept. */
  .station.arrive .station-card::before {
    background: radial-gradient(115% 84% at 50% 46%,
      rgba(20, 20, 19, 0.92) 34%,
      rgba(20, 20, 19, 0.7) 58%,
      rgba(20, 20, 19, 0.3) 78%,
      transparent 92%);
  }
  /* the type carries the desktop sheet's inking, not a flat scrim. The survey
     cards already lift on their per-glyph shadow and the plate headlines on the
     feathered radial pool (both unscoped, above). The plate reading column is the
     one place a phone differs from the desktop layout: there the prose sits in a
     narrow column held off the island, but here it runs full-width straight over
     the relief. Give that prose the very same per-glyph inking the survey cards
     carry, so the words lift off the chart without a panel behind them. */
  body.journey .block,
  body.journey .lost,
  body.journey .contact-form,
  body.journey .mailto-fallback {
    text-shadow:
      0 0 1px rgba(0, 0, 0, 1),
      0 1px 2px rgba(0, 0, 0, 0.95),
      0 0 3px rgba(0, 0, 0, 0.85);
  }
}

/* ---------- 404 ---------- */

.lost {
  min-height: 55vh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;   /* pin the message to the sheet's foot, as the survey's arrival does */
  padding-bottom: var(--footer-lead);   /* shared lead-in, so every sheet closes at one depth */
}

.lost h1 { font-size: clamp(2.4rem, 6vw, 4.2rem); max-width: 14em; }

.lost-line {
  width: 14rem;
  height: auto;
  margin-bottom: 2.5rem;
}

.lost-line path { stroke: var(--accent); stroke-width: 1; fill: none; }

.lost p {
  color: var(--body);
  max-width: var(--measure);
  margin-top: 1.6rem;
}

/* ---------- footer ---------- */

.site-footer {
  margin-top: var(--section-gap);
  padding-bottom: max(clamp(2rem, 5vh, 3.5rem), env(safe-area-inset-bottom));
}

.site-footer .hairline { margin-bottom: 2rem; }

.footer-row {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem 3rem;
  justify-content: space-between;
  align-items: baseline;
}

.footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 2.5rem;
  font-family: var(--font-mono);
  font-size: 0.8125rem;
}

.footer-links a { color: var(--body); text-decoration-color: var(--line); }
.footer-links a:hover { color: var(--ivory); }

/* ---------- scroll reveals (JS adds .js to <html>) ---------- */

.js .reveal {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 600ms var(--ease), transform 600ms var(--ease);
  transition-delay: calc(var(--reveal-order, 0) * 120ms);
}

.js .reveal.is-visible {
  opacity: 1;
  transform: none;
}

/* ---------- reduced motion: still, immediately ---------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    transition: none !important;
    animation: none !important;
  }
  .js .reveal { opacity: 1; transform: none; }
  .js .reveal .diagram .draw { stroke-dasharray: none; stroke-dashoffset: 0; }
  .js .reveal .diagram .diag-label { opacity: 1; }
  .chart-frame, .chart-corner, .chart-hud { transition: none; }
  .stage { height: auto; --pa: 1; --pj: 1; --pl: 1; }
  .stage-sticky { position: static; min-height: 0; padding: clamp(3rem, 8vh, 6rem) 0; }
  .stage-hint { display: none; }
}

/* ---------- small screens ---------- */

@media (max-width: 34rem) {
  .site-header { flex-direction: row; align-items: baseline; }
  .wordmark { font-size: 1.375rem; }
  /* three links and the wordmark share ~340px — the nav gives up tracking
     before anything is allowed to touch or wrap (the no-JS fallback) */
  .site-nav { gap: 0.9rem; }
  .site-nav a { font-size: 0.625rem; letter-spacing: 0.14em; }

  /* ----- with JS: the band folds to a hamburger dropdown -----
     The wordmark keeps the left, the trigger takes the right, and the three
     links drop as a panel cut from the same material as the menu bar: flat
     deep ink, one jade hairline. On a phone the band never has to hold the
     wordmark and the nav on one line, so nothing crowds or wraps. */
  .js .nav-toggle { display: flex; }
  .js .site-header { align-items: center; }

  .js .site-nav {
    position: absolute;
    top: calc(100% + 1px);   /* seated just under the band's hairline */
    right: var(--gutter);
    min-width: 11rem;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: 0.4rem 0;
    background: #141413;   /* the journey band's exact ground, so the panel reads as the bar extended */
    border: 1px solid rgba(93, 202, 165, 0.11);   /* the water contour lines' shade, as the band's rule */
    /* closed at rest; opened by .nav-open on the header */
    opacity: 0;
    visibility: hidden;
    transform: translateY(-0.5rem);
    transition: opacity 240ms var(--ease), transform 240ms var(--ease), visibility 0s linear 240ms;
  }

  .js .site-header.nav-open .site-nav {
    opacity: 1;
    visibility: visible;
    transform: none;
    transition: opacity 240ms var(--ease), transform 240ms var(--ease);
  }

  /* each destination is a full-width row with a comfortable tap target */
  .js .site-nav a {
    display: block;
    position: relative;
    padding: 0.5rem 1.4rem 0.5rem 2rem;   /* left room for the active mark; rows sit close, not spread */
    font-size: 0.6875rem;
    letter-spacing: 0.18em;
    color: var(--muted);
  }

  .js .site-nav a:hover,
  .js .site-nav a[aria-current="page"] { color: var(--ivory); }

  /* the active mark moves from above the label to a leading jade tick */
  .js .site-nav a[aria-current="page"]::before {
    left: 0.9rem;
    top: 50%;
    transform: translateY(-50%);
  }
}
