@charset "UTF-8";

/* Hellos — pattern C
   Four layers, front to back: readout (DOM) / frosted glass / creatures /
   blue field. Only the readout lives here; the other three are drawn by
   assets/js/hero-stamps.js into <canvas class="stage">.

   Scroll is the clock. hero-stamps.js publishes the scroll position as --scroll
   (0–1) on <html>, the creatures fall in the canvas, and everything in this
   file that appears or leaves reads that same number. No scroll listeners
   here, no IntersectionObservers — one value, one source. */

:root {
  /* duplicated in hero-stamps.js (FIELD_TOP / FIELD_BOTTOM) so the page never
     flashes a different blue before WebGL takes over. */
  --field: #2b4fa0;
  --ink: #fff;

  --mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  --display: 'Cabinet Grotesk', 'Arial Black', sans-serif;
  /* the notification is the one thing on the page pretending to be iOS, so it
     asks for the system UI face and nothing else */
  --system: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', system-ui, sans-serif;

  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);

  --hud: clamp(10px, 1.02vw, 17px);
  --pad-x: clamp(20px, 3.4vw, 62px);
  --pad-y: clamp(18px, 3vw, 52px);
  --safe-top: max(var(--pad-y), env(safe-area-inset-top, 0px));
  --safe-bottom: max(16px, env(safe-area-inset-bottom, 0px));
  --rule: max(1.2px, 0.11vw);
  --map-ui-alpha: calc(1 - clamp(0, calc((var(--map) - 0.44) / 0.10), 1));
}


* { box-sizing: border-box; }

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* The way past six viewport-heights of decorative scroll. Hidden the same way
   .sr-only is until it takes focus, then it becomes an ordinary instrument
   chip in the corner the name has not reached yet — the first thing a Tab
   lands on, and the only element allowed above .ui. */
.skip {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
  color: var(--ink);
}

.skip:focus-visible {
  position: fixed;
  z-index: 20;
  top: var(--pad-y);
  left: var(--pad-x);
  width: auto;
  height: auto;
  margin: 0;
  padding: 0.7em 1.1em;
  overflow: visible;
  clip: auto;
  border: var(--rule) solid var(--ink);
  border-radius: 999px;
  outline: 2px solid var(--ink);
  outline-offset: 3px;
  background: var(--field);
  font-family: var(--mono);
  font-size: var(--hud);
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
}

html {
  /* hero-stamps.js overwrites these every scroll event; the fallbacks keep the page
     coherent for the frame before the module boots. --scroll is the B sub-clock
     (0..1 across the first 55% of the page, capped at 0.92 so it never reaches
     B's landing); --map is simply the GLOBAL scroll position (0..1), which the
     map chapter and the footer both read. */
  --scroll: 0;
  --map: 0;
  background: var(--field);
}

body {
  margin: 0;
  font-family: var(--display);
  font-weight: 800;
  color: var(--ink);
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

/* the only element with height — scrolling this is what makes the cast fall.
   Pattern C runs the whole PRESERVED part of B over the first ~55% of the page
   (SPLIT = 0.55) and gives the rest to the overhead map chapter, which overlaps
   the whisper's tail. hero-stamps.js publishes --scroll (the B sub-clock, capped at
   B_END = 0.92 so B's landing/skyline never fire) and --map (the global scroll
   position), so every B-era rule below keeps working unchanged. */
.scroll-track { height: 640vh; height: 640svh; }

.scope {
  position: fixed;
  inset: 0 0 auto;
  height: 100vh;
  height: var(--view-height, 100dvh);
  overflow: hidden;
}

.stage {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

.ui {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 1;
}

/* The map chapter is intentionally free of instrument copy and framing. The
   notification/whisper finish on their own timelines; only the persistent HUD
   clears here so the final map has no surrounding words, grid-like corners or
   tracking brackets. */
.trackers {
  opacity: var(--map-ui-alpha);
}

@keyframes ui-in { to { opacity: 1; } }

/* ───────────────────────  the instrument  ─────────────────────── */

.r {
  position: absolute;
  margin: 0;
  font-family: var(--mono);
  font-size: var(--hud);
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: 0.055em;
  white-space: nowrap;
}

.regions-label {
  margin: 0 0 0.6em;
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  opacity: 0.75;
}
.regions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.4em 1.25em;
  margin: 0;
  padding: 0;
  list-style: none;
}
.regions li { white-space: nowrap; }

/* Exact onboarding film artwork; placement and departure follow the live LP. */
.r-mark {
  top: 50%;
  left: 50%;
  width: min(64vw, 980px);
  line-height: 0;
  transform: translate(-50%, -50%);
}
.r-mark__swell {
  display: block;
  position: relative;
  aspect-ratio: 528 / 186;
}
.r-mark__word, .r-mark__video {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.r-mark__video { opacity: 0; pointer-events: none; }
html[data-wordmark="pending"] .r-mark__word,
.r-mark[data-wordmark-phase="loading"] .r-mark__word,
.r-mark[data-wordmark-phase="arrival"] .r-mark__word,
.r-mark[data-wordmark-phase="float"] .r-mark__word { opacity: 0; }
.r-mark[data-wordmark-phase="arrival"] [data-wordmark-arrival],
.r-mark[data-wordmark-phase="float"] [data-wordmark-float] { opacity: 1; }
html[data-fallback] .r-mark__word { opacity: 1; }
html[data-fallback] .r-mark__video { opacity: 0; }
[data-fx] .r-mark {
  --defocus: clamp(0, calc((var(--scroll) - 0.05) / 0.12), 1);
  transform: translate(-50%, calc(-50% - var(--scroll) * 9vh));
  filter: blur(calc(var(--defocus) * var(--defocus) * 16px));
  opacity: calc(1 - clamp(0, calc((var(--scroll) - 0.13) / 0.09), 1));
}
.r-markmini {
  top: var(--safe-top);
  left: var(--pad-x);
  width: clamp(76px, 7.2vw, 110px);
  line-height: 0;
}
.r-markmini img { display: block; width: 100%; height: auto; }
@media (max-width: 600px) {
  .r-mark { width: 88vw; }
}
@media (prefers-reduced-motion: reduce) {
  html[data-wordmark] .r-mark__word { opacity: 1; }
  .r-mark__video { display: none; }
  [data-fx] .r-mark {
    filter: none;
    transform: translate(-50%, -50%);
    opacity: calc(1 - clamp(0, calc((var(--scroll) - 0.05) / 0.12), 1));
  }
}

.scroll-cue {
  position: absolute;
  left: 50%;
  bottom: max(28px, var(--safe-bottom));
  margin: 0;
  transform: translateX(-50%);
  font-family: var(--mono);
  font-size: clamp(10px, 0.85vw, 13px);
  font-weight: 500;
  line-height: 1;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  white-space: nowrap;
  pointer-events: none;
  opacity: calc(1 - clamp(0, calc((var(--scroll) - 0.015) / 0.055), 1));
}

/* ───────────────────────  trackers  ─────────────────────── */

/* hero-stamps.js writes --tx/--ty/--tw/--th (px), --live and the one-shot
   --lock-scale onto each tracker every frame. --live also handles the wrap: it
   drops to 0 at the very top and bottom of a lane so a bracket never blinks out
   mid-frame. */
.tracker {
  position: absolute;
  top: 0;
  left: 0;
  width: var(--tw, 0px);
  height: var(--th, 0px);
  transform:
    translate3d(var(--tx, 0px), var(--ty, 0px), 0)
    scale(var(--lock-scale, 1));
  transform-origin: 50% 50%;
  opacity: var(--live, 0);
}

.tracker i {
  position: absolute;
  /* both arms cut to one length — off the box's shorter side, so the corner
     stays a square L even when the tracker itself is stretched tall or wide */
  --arm: clamp(8px, calc(min(var(--tw, 0px), var(--th, 0px)) * 0.14), 24px);
  width: var(--arm);
  height: var(--arm);
  border: var(--rule) solid var(--ink);
  /* quiet enough to read as instrument markings, not a second subject */
  opacity: 0.35;
}

.tracker i:nth-child(1) { top: 0; left: 0; border-right: 0; border-bottom: 0; }
.tracker i:nth-child(2) { top: 0; right: 0; border-left: 0; border-bottom: 0; }
.tracker i:nth-child(3) { bottom: 0; left: 0; border-right: 0; border-top: 0; }
.tracker i:nth-child(4) { bottom: 0; right: 0; border-left: 0; border-top: 0; }

.tracker b {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 0;
  height: 0;
}

.tracker b::before,
.tracker b::after {
  content: '';
  position: absolute;
  background: var(--ink);
}

.tracker b::before {
  width: clamp(8px, 0.75vw, 13px);
  height: var(--rule);
  transform: translate(-50%, -50%);
}

.tracker b::after {
  width: var(--rule);
  height: clamp(8px, 0.75vw, 13px);
  transform: translate(-50%, -50%);
}

/* ═══════════════════  the notification  ═══════════════════ */

/* It drops in from above the top edge, the way iOS delivers one.

   hero-stamps.js writes --drop (the spring: 0 = offstage above, 1 = settled, and it
   passes 1 briefly on the overshoot) and --lit (opacity). The spring is solved
   in JS because neither a transition nor a cubic-bezier can produce a damped
   oscillation, and this one has to stay locked to scroll position rather than
   run on a clock — scrub back up and the banner springs back out the way it
   came.

   It still enters from above the top edge, but it comes to rest in the middle
   of the frame rather than under the status bar — this is a page, not a phone,
   and the centre is where the eye already is. --gap is the distance from that
   resting place out past the top of the viewport, so the banner always starts
   fully offstage however tall it grows. */
.notice {
  --drop: 0;
  --lit: 0;
  --gap: calc(50vh + 70%);

  /* An opacity-0 card with a backdrop-filter is still a live compositor layer,
     and this one is up for the whole page either side of its ten percent of
     scroll. hero-stamps.js flips the inline visibility on with --lit (updateBanner),
     so the glass only costs anything while it is actually lit. */
  visibility: hidden;
  position: absolute;
  top: 50%;
  left: 50%;
  width: min(460px, calc(100vw - 30px));
  opacity: var(--lit);
  /* scale trails the slide slightly, so it reads as glass settling into the
     frame rather than as a card being resized */
  transform:
    translate(-50%, calc(-50% + (var(--drop) - 1) * var(--gap)))
    scale(calc(0.965 + var(--drop) * 0.035));
}

/* ── Liquid Glass ──
   Four things make it read as glass rather than as a frosted rectangle, and
   all four are doing work here:

   1. the backdrop is not just blurred but brightened and saturated — glass
      concentrates what is behind it, it doesn't only diffuse it
   2. a lit rim traced round the whole edge (::before), brightest where a
      light above-left would catch it and again on the opposite shoulder
   3. light pooling on the inner top surface (::after) — this is what gives
      the panel thickness instead of reading as a flat sheet
   4. a shadow far enough below to say the thing is floating, not printed

   The layout numbers stay iOS's: 38px icon, 15px title over a 13px app line. */
.notice__card {
  position: relative;
  display: flex;
  gap: 14px;
  align-items: flex-start;
  padding: 17px 19px 18px;
  border-radius: 30px;
  /* Darkened rather than whitened. The first pass brightened the backdrop,
     which is right for glass over a light wall but wrong here — the type is
     white, so lifting the panel toward white ate the contrast underneath it.
     Taking the backdrop down instead keeps it reading as glass while giving
     the text something to sit on. */
  background: rgba(9, 24, 62, 0.32);
  backdrop-filter: blur(30px) saturate(145%) brightness(0.82);
  -webkit-backdrop-filter: blur(30px) saturate(145%) brightness(0.82);
  box-shadow:
    0 22px 55px -14px rgba(3, 10, 32, 0.55),
    0 5px 16px -6px rgba(3, 10, 32, 0.35);
  font-family: var(--system);
  color: #fff;
  isolation: isolate;
}

/* the rim. A gradient border done as a masked ring rather than a border, so
   it can vary in brightness around the edge the way a real bevel catches
   light — a plain border colour is the single biggest tell of fake glass. */
.notice__card::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: inherit;
  padding: 1px;
  background: linear-gradient(
    148deg,
    rgba(255, 255, 255, 0.92) 0%,
    rgba(255, 255, 255, 0.28) 22%,
    rgba(255, 255, 255, 0.07) 48%,
    rgba(255, 255, 255, 0.22) 74%,
    rgba(255, 255, 255, 0.72) 100%
  );
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  pointer-events: none;
}

/* the thickness: light gathering just inside the top surface, plus a wide
   soft pool spilling in from above the panel */
.notice__card::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: inherit;
  background: radial-gradient(
    120% 80% at 50% -18%,
    rgba(255, 255, 255, 0.11),
    transparent 68%
  );
  pointer-events: none;
}

/* content rides above both glass layers */
.notice__icon,
.notice__body { position: relative; z-index: 2; }

.notice__icon {
  flex: 0 0 auto;
  width: 52px;
  height: 52px;
  padding: 4px;
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.12);
  box-shadow: 0 6px 18px rgba(6, 16, 42, 0.2);
  object-fit: cover;
}

.notice__body {
  flex: 1 1 auto;
  min-width: 0;
}

.notice__body p { margin: 0; }

.notice__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 4px;
  font-size: 14px;
  line-height: 1.25;
  letter-spacing: 0.01em;
  color: rgba(255, 255, 255, 0.74);
}

.notice__app {
  font-weight: 500;
  letter-spacing: 0.01em;
}

.notice__time { flex: 0 0 auto; }

.notice__title {
  font-size: 18px;
  font-weight: 600;
  line-height: 1.28;
  letter-spacing: -0.012em;
}

.notice__body .notice__text {
  margin-top: 2px;
  font-size: 16px;
  font-weight: 400;
  line-height: 1.28;
  letter-spacing: -0.012em;
  color: rgba(255, 255, 255, 0.93);
}

/* The onboarding example keeps each speaker unmistakable. Reply, photograph,
   then Toasty's answer; the page's shared scroll clock drives each turn. */
.whisper {
  position: absolute;
  top: 50%;
  left: 50%;
  width: min(580px, calc(100vw - var(--pad-x) * 2));
  transform: translate(-50%, -50%);
  font-family: var(--display);
  font-weight: 800;
  letter-spacing: -0.03em;
}
.whisper p { margin: 0; }
.whisper__turn {
  opacity: clamp(0, calc((var(--scroll) - var(--from, 9)) / 0.065), 1);
}
.whisper__turn--you { text-align: right; }
.whisper__turn--toasty { margin-top: clamp(24px, 4dvh, 42px); }
.whisper .whisper__from {
  display: flex;
  align-items: center;
  gap: 9px;
  margin-bottom: 10px;
  font-family: var(--system);
  font-size: 13px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0;
}
.whisper__turn--you .whisper__from { justify-content: flex-end; }
.whisper__line {
  font-size: clamp(27px, 3.4vw, 44px);
  line-height: 1.08;
  text-wrap: balance;
}
.whisper__avatar { width: 30px; height: 30px; border-radius: 50%; object-fit: cover; }
.whisper__photo {
  display: block;
  width: clamp(80px, 13dvh, 130px);
  height: auto;
  margin: 18px 0 0 auto;
  border: 4px solid #f6f0df;
  border-radius: 7px;
  transform: rotate(5deg);
  box-shadow: 0 8px 18px rgba(5, 20, 48, 0.2);
}

@media (max-width: 640px) {
  .scroll-cue { font-size: 9px; }

  .notice {
    width: min(420px, calc(100vw - 36px));
  }

  .notice__card {
    gap: 11px;
    padding: 14px 15px 15px;
    border-radius: 25px;
  }

  .notice__icon {
    width: 44px;
    height: 44px;
    padding: 3px;
  }

  .notice__head {
    gap: 8px;
    margin-bottom: 3px;
    font-size: 12px;
  }

  .notice__title { font-size: 16px; }

  .notice__body .notice__text {
    font-size: 14px;
    line-height: 1.34;
  }

  .scroll-cue {
    bottom: max(30px, 4vh);
  }
}

/* ───────────────────────  the footer  ─────────────────────── */

/* Closing information shares one flow; its height reserves space for the map. */
.foot {
  --foot-span: 0.04;

  position: absolute;
  left: 50%;
  bottom: var(--safe-bottom);
  transform: translateX(-50%);
  width: min(820px, calc(100vw - var(--pad-x) * 2));
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

/* Driven by --map, which in pattern C is just the GLOBAL scroll position: the
   words arrive over the very last of the page (0.90–1.0), as the last pins are
   planting on the map, instead of at the end of the fall. The rise and the
   stagger are the same shape B used, only on the new clock. */
.foot > * {
  margin: 0;
  opacity: clamp(0, calc((var(--map) - var(--in)) / var(--foot-span)), 1);
  transform: translateY(calc((1 - clamp(0, calc((var(--map) - var(--in)) / var(--foot-span)), 1)) * 0.7em));
  /* the map behind is busy hairline-on-blue; a soft shadow keeps the readout
     legible over it without a solid plate that would break the instrument feel */
  text-shadow: 0 1px 18px rgba(6, 16, 42, 0.55), 0 1px 3px rgba(6, 16, 42, 0.5);
}

.foot__regions { --in: 0.92; }
.foot__cta  { --in: 0.95; }
.foot__legal { --in: 0.95; }

.foot__regions .regions {
  font-family: var(--display);
  font-size: clamp(15px, 1.8vw, 22px);
  letter-spacing: -0.015em;
}
.foot__regions .regions-label { margin-bottom: 8px; }

/* Real link boxes, with no negative margins into the CTA's tap target. */
.foot__legal {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0 16px;
  margin-top: 12px;
  font-family: var(--system);
  font-size: 11px;
  font-weight: 400;
}
.foot__legal a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  pointer-events: auto;
  color: rgba(255, 255, 255, 0.85);
  text-decoration: none;
}
.foot__legal a:hover, .foot__legal a:focus-visible {
  text-decoration: underline;
  text-underline-offset: 0.3em;
}

/* The one control on the page. A compact black launch CTA breaks cleanly from
   the blue map and gives the closing screen one obvious action. The inner shell
   owns hover and press transforms so the link's scroll entrance stays intact. */
.foot__cta {
  margin-top: clamp(18px, 1.7vw, 28px);
  pointer-events: auto;
  color: var(--ink);
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}

.foot__cta-shell {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5em;
  width: clamp(210px, 22vw, 280px);
  min-height: clamp(60px, 5.8vw, 76px);
  padding: 0.92em 1.4em;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(180deg, #2a2a2a 0%, #111 54%, #050505 100%);
  box-shadow:
    inset 0 2px 2px rgba(255, 255, 255, 0.15),
    inset 0 0 0 1px rgba(255, 255, 255, 0.07),
    inset 0 -6px 7px rgba(0, 0, 0, 0.38),
    0 14px 26px rgba(4, 8, 18, 0.34),
    0 25px 50px rgba(4, 8, 18, 0.2);
  font-family: var(--display);
  font-size: clamp(17px, 1.45vw, 21px);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.035em;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);
  transition:
    transform 220ms var(--ease-out),
    filter 180ms var(--ease-out),
    box-shadow 220ms var(--ease-out);
}

.foot__cta-logo {
  width: 0.82em;
  height: auto;
  flex: none;
}

.foot__cta:focus-visible {
  outline: none;
}

.foot__cta:focus-visible .foot__cta-shell {
  outline: 2px solid var(--ink);
  outline-offset: 4px;
}

.foot__cta:active .foot__cta-shell {
  transform: translateY(3px) scale(0.98);
}

@media (hover: hover) and (pointer: fine) {
  .foot__cta:hover .foot__cta-shell {
    transform: translateY(3px) scale(0.99);
    filter: brightness(0.94);
    box-shadow:
      inset 0 3px 4px rgba(0, 0, 0, 0.42),
      inset 0 0 0 1px rgba(255, 255, 255, 0.05),
      inset 0 -4px 6px rgba(0, 0, 0, 0.34),
      0 8px 16px rgba(4, 8, 18, 0.3),
      0 14px 28px rgba(4, 8, 18, 0.16);
  }

  .foot__cta:active .foot__cta-shell {
    transform: translateY(5px) scale(0.975);
    box-shadow:
      inset 0 2px 3px rgba(0, 0, 0, 0.35),
      inset 0 0 0 1px rgba(255, 255, 255, 0.04),
      inset 0 -4px 6px rgba(0, 0, 0, 0.38),
      0 8px 16px rgba(4, 8, 18, 0.26);
  }
}

@media (prefers-reduced-motion: reduce) {
  .foot__cta-shell {
    transition-property: filter, box-shadow;
  }

  .foot__cta:hover .foot__cta-shell,
  .foot__cta:active .foot__cta-shell {
    transform: none;
  }
}

/* The skyline that used to live here as an <img> is drawn by hero-stamps.js inside
   the canvas now — behind the cast, in front of the field — so the landed row
   stands in the street instead of floating over the picture. Its dusk dimming
   and late arrival moved with it (CITY_* in hero-stamps.js). */

/* The voice is not standing there through the whole descent.
 *
 * Its beats rest at 0.16 opacity so they read as unsaid rather than absent,
 * but that also meant the whole paragraph was faintly legible from the top of
 * the page — you could read the ending before the notification had even
 * arrived. Gating the block fixes that: it turns up only once the banner has
 * been and gone, which is the moment the thing that sent it starts talking.
 *
 * And it clears again before the landing, so what you are left looking at is
 * the cast rather than the words about them. That is the whole descent: you
 * stop being told they are there, and you see them. */
.whisper {
  opacity: calc(
    clamp(0, calc((var(--scroll) - 0.625) / 0.06), 1)
    * (1 - clamp(0, calc((var(--map) - 0.61) / 0.08), 1))
  );
}

/* ───────────────────────  no module  ─────────────────────── */

/* index.html's __hellosFallback() tags <html data-fallback> when hero-stamps.js
   never runs — a failed import, no importmap support, or no WebGL. There is no
   --scroll and no --map then, so every scroll-driven reveal below is stuck at
   its opening value and the page shows six viewport-heights of nothing with
   two unreachable links at the far end. This is the static END state instead:
   one screen tall, the instrument readouts that were promising a sequence
   removed, and the two things worth acting on brought into view. */
html[data-fallback] .scroll-track { height: 100svh; }
html[data-fallback] .scroll-cue { display: none; }
html[data-fallback] :is(.stage, .banner, .whisper, .trackers) { display: none; }
html[data-fallback] .r-mark {
  top: 30%;
  width: min(78vw, 780px);
  opacity: 1;
  filter: none;
  transform: translate(-50%, -50%);
}
html[data-fallback] .foot > * { opacity: 1; transform: none; }

@media (max-height: 680px) {
  .whisper__photo { width: 80px; margin-top: 12px; }
}

/* On a short landscape screen, the two replies sit beside each other. */
@media (max-height: 480px) and (min-aspect-ratio: 6/5) {
  .whisper { display: grid; grid-template-columns: 1fr 1fr; gap: 40px; width: min(760px, calc(100vw - 80px)); }
  .whisper__turn--toasty { margin-top: 44px; }
  .whisper__line { font-size: 26px; }
  .whisper__photo { width: 64px; }
  .foot {
    left: auto;
    right: 20px;
    top: 50%;
    bottom: auto;
    width: calc(50vw - 40px);
    transform: translateY(-50%);
  }
  .foot__cta { margin-top: 10px; }
  .foot__cta-shell { min-height: 48px; padding: 0.65em 1.4em; }
  .foot__legal { margin-top: 4px; }
  html[data-fallback] .r-mark { top: 50%; left: 25%; width: 40vw; }
}

/* ───────────────────────  no-js  ─────────────────────── */

.fallback {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(560px, calc(100vw - 48px));
  margin: 0;
  font-family: var(--system);
  font-size: 18px;
  font-weight: 400;
  line-height: 1.6;
  text-align: center;
}
.fallback a { color: inherit; text-underline-offset: 0.2em; }

@media (prefers-reduced-motion: reduce) {
  .ui { animation-duration: 0.01ms; animation-delay: 0ms; }
}

/* Native map stationery: a white name cut out of blue, attached to its art. */
.map-stickers { position: absolute; inset: 0; visibility: hidden; --pin-size: 70px; }
.map-credit { position: absolute; top: var(--map-credit-top); right: var(--map-credit-right); margin: 0; font: 10px/1.4 var(--system); color: rgba(255,255,255,.8); opacity: clamp(0, calc((var(--map) - .55) / .10), 1); }
.map-credit a { display: inline-flex; align-items: center; min-height: 24px; color: inherit; text-underline-offset: 3px; pointer-events: auto; }
.map-credit a:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }
.map-sticker { position: absolute; left: 0; top: 0; width: 0; height: 0; opacity: 0; }
.map-sticker-filters { position: absolute; width: 0; height: 0; pointer-events: none; }
.map-sticker__art { position: absolute; z-index: 2; width: var(--pin-size); height: var(--pin-size); object-fit: contain; transform: translate(-50%,-50%); filter: url(#map-sticker-outline); }
.map-sticker__label {
  position: absolute; z-index: 3; left: 0; bottom: calc(var(--pin-size) * .26);
  width: 82px; margin: 0; text-align: center;
  font-family: var(--display); font-size: 13px; font-weight: 800; line-height: 1.04;
  color: #fff; -webkit-text-stroke: .44em #1f69df; paint-order: stroke fill;
  filter: drop-shadow(0 2px 1px rgba(4,25,69,.28));
  transform: translateX(-50%); text-wrap: balance;
}
.map-sticker__photo {
  position: absolute; z-index: 4; width: 37px; height: 43px; left: calc(var(--pin-size) * .26); top: -4px;
  object-fit: cover; border: 3px solid #fff8e7; border-radius: 3px;
  box-shadow: 1px 3px 5px rgba(4,25,69,.24); transform: rotate(8deg);
}
.map-sticker__ground { position: absolute; z-index: 0; left: 0; top: -4px; width: calc(var(--pin-size) * .42); height: 8px; border-radius: 50%; background: rgba(5,24,55,.26); filter: blur(2px); }
.map-sticker__point { position: absolute; z-index: 1; left: 0; top: 0; width: 4px; height: 4px; background: #f6f0df; border-radius: 50%; }
@media (min-width: 1000px) { .map-sticker__label { width: 92px; font-size: 14px; } }
.map-stickers.is-compact .map-sticker__label { width: 68px; font-size: 12px; }
.map-stickers.is-compact [data-pin="hiking"] .map-sticker__label { left: -6px; }
.map-stickers.is-compact [data-pin="cafe"] .map-sticker__label { left: 6px; }
html[data-fallback] .map-stickers { display: none; }

/* Small corner readings frame the journey; the closing CTA gets its own space. */
.r-regions { top: var(--pad-y); right: var(--pad-x); text-align: right; font-size: clamp(10px, .9vw, 12px); line-height: 1.55; letter-spacing: .06em; }
.r-regions span { display: block; }
.r-regions__caption { margin-bottom: 4px; font-size: .8em; text-transform: uppercase; opacity: .6; }
.r-user { bottom: calc(var(--safe-bottom) + 24px); left: var(--pad-x); font-size: clamp(9px, .8vw, 12px); line-height: 1.6; text-transform: uppercase; font-variant-numeric: tabular-nums; }
.r-user > span { display: block; }
.r-platform { right: var(--pad-x); bottom: calc(var(--safe-bottom) + 24px); max-width: 45%; text-align: right; font-size: clamp(9px, .8vw, 12px); text-transform: uppercase; }
.r-user, .r-platform { opacity: calc(1 - clamp(0, calc((var(--map) - .74) / .10), 1)); }
.r-regions { opacity: calc(1 - clamp(0, calc((var(--map) - .60) / .10), 1)); }
@media (max-width: 640px) { .scroll-cue { bottom: 84px; } }
html[data-fallback] .r-regions, html[data-fallback] .r-user, html[data-fallback] .r-platform { display: none; }
