/* ==========================================================================
   Vezo — thevezo.com
   One stylesheet for the whole site. The component blocks below map 1:1 to the
   markup in index.html; the legal pages reuse only the nav, footer and prose
   rules. No build step: this file ships as authored.
   ========================================================================== */

/* --- Brand typeface -------------------------------------------------------
   Space Grotesk is the app's typeface (sf-pulse/src/theme/index.ts). Self
   hosted as a variable woff2 so the site owns its critical path — no
   third-party DNS, TLS and cache lookup sitting in front of first paint. */
@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('../font/space-grotesk-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('../font/space-grotesk-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* --- Tokens --------------------------------------------------------------- */
:root {
  /* Surfaces run warm — the same off-white family the app uses, so the real
     screenshots sit in the page instead of on top of it. */
  --bg: #F5F1EB;
  --bg-warm: #EDE4DB;
  --surface: #FFFFFF;
  --ink: #0E0D0B;
  /* Body copy. Darkened from #55524C, which measured 6.9:1 on --bg — over the
     4.5:1 AA needs but just under the 7:1 for AAA. This clears 7.3:1, so every
     paragraph on the site is AAA rather than most of them. */
  --ink-2: #504D47;
  --ink-3: #8B867E;
  --line: rgba(14, 13, 11, 0.10);
  --line-soft: rgba(14, 13, 11, 0.06);
  --accent: #E4573D;

  /* The Vezo logo gradient, verbatim from sf-pulse/src/theme/index.ts. */
  --g1: #FF826F;
  --g2: #E58EA8;
  --g3: #AA91CF;
  --g4: #718DE9;
  --g5: #7A5E9F;
  --gradient: linear-gradient(115deg, var(--g1), var(--g2) 28%, var(--g3) 52%, var(--g4) 76%, var(--g5));

  --r-sm: 14px;
  --r-md: 22px;
  --r-lg: 32px;
  --r-xl: 44px;

  --wrap: 1180px;
  --gutter: clamp(20px, 5vw, 48px);
  --nav-h: 68px;

  /* One easing curve for everything that moves. */
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  --dur: 0.75s;

  --shadow-sm: 0 1px 2px rgba(14, 13, 11, 0.04), 0 6px 18px rgba(14, 13, 11, 0.05);
  --shadow-md: 0 2px 6px rgba(14, 13, 11, 0.05), 0 18px 44px rgba(14, 13, 11, 0.09);
}

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Anchor targets clear the sticky header instead of hiding behind it. */
  scroll-padding-top: calc(var(--nav-h) + 16px);
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: 'Space Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  /* `clip` rather than `hidden`: it contains the decorative glows without
     silently killing `position: sticky` further down the page. */
  overflow-x: clip;
}

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

img { height: auto; }
a { color: inherit; }
button { font: inherit; color: inherit; }

h1, h2, h3, h4 {
  margin: 0;
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 1.02;
}

p { margin: 0; }
ul, ol { margin: 0; padding: 0; }

/* --- Focus ---------------------------------------------------------------- */
:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
  border-radius: 6px;
}

.section--dark :focus-visible,
.cta :focus-visible { outline-color: #fff; }

.skip-link {
  position: absolute;
  left: 12px;
  top: -60px;
  z-index: 200;
  padding: 12px 18px;
  background: var(--ink);
  color: #fff;
  border-radius: 999px;
  font-size: 15px;
  font-weight: 500;
  text-decoration: none;
  transition: top 0.2s var(--ease);
}

.skip-link:focus { top: 12px; }

/* --- Layout --------------------------------------------------------------- */
.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section { position: relative; padding-block: clamp(88px, 12vw, 168px); }
.section--warm { background: var(--bg-warm); }
.section--tight { padding-block: clamp(64px, 8vw, 112px); }
.section--dark { background: #0E0D0B; color: #F4F1EA; }
.section--dark .lead,
.section--dark .eyebrow { color: rgba(244, 241, 234, 0.66); }

.eyebrow {
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 22px;
}

.section-head {
  max-width: 44ch;
  margin-bottom: clamp(32px, 4vw, 52px);
}

.h2 { font-size: clamp(2.1rem, 5vw, 3.7rem); }
.h3 { font-size: clamp(1.35rem, 2.2vw, 1.75rem); letter-spacing: -0.025em; }

.lead {
  font-size: clamp(1.05rem, 1.5vw, 1.28rem);
  line-height: 1.55;
  color: var(--ink-2);
  max-width: 34ch;
}

.lead--wide { max-width: 52ch; }
.measure { max-width: 62ch; }

/* --- Buttons -------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 52px;
  padding: 0 28px;
  border: 1px solid transparent;
  border-radius: 999px;
  font-size: 16px;
  font-weight: 500;
  letter-spacing: -0.01em;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: transform 0.35s var(--ease), background-color 0.35s var(--ease),
    border-color 0.35s var(--ease), color 0.35s var(--ease), box-shadow 0.35s var(--ease);
}

.btn--primary { background: var(--ink); color: #fff; box-shadow: var(--shadow-sm); }
.btn--ghost { background: transparent; border-color: var(--line); color: var(--ink); }
.btn--light { background: #fff; color: var(--ink); }
.btn--onDark {
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  border-color: rgba(255, 255, 255, 0.28);
}
.btn--sm { min-height: 44px; padding: 0 20px; font-size: 15px; }

@media (hover: hover) {
  .btn:hover { transform: translateY(-2px); }
  .btn--primary:hover { box-shadow: var(--shadow-md); }
  .btn--ghost:hover { border-color: var(--ink); }
  .btn--onDark:hover { background: rgba(255, 255, 255, 0.2); }
}

.btn:active { transform: translateY(0); }
.btn__icon { width: 17px; height: 17px; flex: none; }

/* --- Navigation ----------------------------------------------------------- */
.site-nav {
  position: sticky;
  top: 0;
  z-index: 100;
  height: var(--nav-h);
  display: flex;
  align-items: center;
  /* Glass, but only once the page has moved — over the hero it stays clear. */
  background: rgba(245, 241, 235, 0);
  border-bottom: 1px solid transparent;
  transition: background-color 0.4s var(--ease), border-color 0.4s var(--ease);
}

.site-nav.is-stuck {
  background: rgba(245, 241, 235, 0.78);
  backdrop-filter: saturate(180%) blur(18px);
  -webkit-backdrop-filter: saturate(180%) blur(18px);
  border-bottom-color: var(--line-soft);
}

.site-nav__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  width: 100%;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 11px;
  text-decoration: none;
  font-weight: 700;
  font-size: 19px;
  letter-spacing: 0.14em;
  flex: none;
}

.brand img { width: 30px; height: 30px; border-radius: 8px; }

.nav-links {
  display: flex;
  align-items: center;
  gap: 34px;
  list-style: none;
}

.nav-links a {
  /* Padded to clear the 24px minimum target size; the gap is reduced to
     compensate so the bar still reads as spaced-out links. */
  display: inline-flex;
  align-items: center;
  min-height: 30px;
  padding-inline: 2px;
  text-decoration: none;
  font-size: 15.5px;
  color: var(--ink-2);
  transition: color 0.25s var(--ease);
}

@media (hover: hover) {
  .nav-links a:hover { color: var(--ink); }
}

.nav-actions { display: flex; align-items: center; gap: 12px; flex: none; }

.nav-toggle {
  display: none;
  width: 44px;
  height: 44px;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 999px;
  cursor: pointer;
}

.nav-toggle span {
  display: block;
  position: relative;
  width: 17px;
  height: 1.5px;
  background: var(--ink);
  transition: background-color 0.2s var(--ease);
}

.nav-toggle span::before,
.nav-toggle span::after {
  content: '';
  position: absolute;
  left: 0;
  width: 17px;
  height: 1.5px;
  background: var(--ink);
  transition: transform 0.35s var(--ease);
}

.nav-toggle span::before { top: -5.5px; }
.nav-toggle span::after { top: 5.5px; }
.nav-toggle[aria-expanded='true'] span { background: transparent; }
.nav-toggle[aria-expanded='true'] span::before { transform: translateY(5.5px) rotate(45deg); }
.nav-toggle[aria-expanded='true'] span::after { transform: translateY(-5.5px) rotate(-45deg); }

/* The mobile panel is present in the DOM at every width but only laid out
   under the 780px breakpoint. */
.nav-panel { display: none; }

/* --- Hero ----------------------------------------------------------------- */
.hero {
  position: relative;
  padding-bottom: 0;
  text-align: center;
  isolation: isolate;
  /* Full-bleed so the aurora can run edge to edge; the inner .wrap keeps the
     content on the same measure as every other section. */
  overflow: hidden;
}

.hero__inner { padding-top: clamp(48px, 8vw, 92px); }

/* --- Aurora ---------------------------------------------------------------
   The hero's moving light. Five soft orbs in the logo gradient's own colours,
   each drifting on its own clock so the field never visibly loops. Only
   `transform` animates — the blur is rasterised once per layer and then just
   moved, which is what keeps this off the main thread. `multiply` lets the
   orbs deepen where they cross instead of washing out, the way the app icon
   does. */
.aurora {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
  /* The light is kept off the words. Colour behind a headline is the thing
     that makes a hero tiring to read — long copy on a shifting field takes
     constant re-focusing, and it is worst for older eyes and for anyone with
     low vision. So the top third stays clean warm off-white and the gradient
     only comes up around the device, which reads as the phone casting it. */
  mask-image: linear-gradient(to bottom,
    transparent 0%, transparent 30%, rgba(0, 0, 0, 0.14) 46%,
    rgba(0, 0, 0, 0.6) 62%, #000 76%, #000 90%, transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom,
    transparent 0%, transparent 30%, rgba(0, 0, 0, 0.14) 46%,
    rgba(0, 0, 0, 0.6) 62%, #000 76%, #000 90%, transparent 100%);
}

.aurora__orb {
  position: absolute;
  border-radius: 50%;
  filter: blur(58px);
  mix-blend-mode: multiply;
  opacity: 0.5;
  will-change: transform;
}

/* Each orb holds its colour across the middle third before falling off, so
   the field reads as the logo gradient rather than a pale wash. */
.aurora__orb--1,
.aurora__orb--2,
.aurora__orb--3,
.aurora__orb--4,
.aurora__orb--5 { background-repeat: no-repeat; }

.aurora__orb--1 {
  width: 34vw; height: 34vw; min-width: 300px; min-height: 300px;
  top: 44%; left: -2%;
  background: radial-gradient(circle at 50% 50%, var(--g1) 0%, var(--g1) 30%, transparent 72%);
  animation: drift1 26s var(--ease) infinite alternate;
}

.aurora__orb--2 {
  width: 30vw; height: 30vw; min-width: 270px; min-height: 270px;
  top: 40%; right: -4%;
  background: radial-gradient(circle at 50% 50%, var(--g4) 0%, var(--g4) 28%, transparent 72%);
  animation: drift2 32s var(--ease) infinite alternate;
}

.aurora__orb--3 {
  width: 38vw; height: 38vw; min-width: 330px; min-height: 330px;
  top: 62%; left: 26%;
  background: radial-gradient(circle at 50% 50%, var(--g3) 0%, var(--g3) 26%, transparent 70%);
  animation: drift3 23s var(--ease) infinite alternate;
}

.aurora__orb--4 {
  width: 34vw; height: 34vw; min-width: 300px; min-height: 300px;
  bottom: -2%; left: 8%;
  background: radial-gradient(circle at 50% 50%, var(--g2) 0%, var(--g2) 30%, transparent 72%);
  animation: drift4 35s var(--ease) infinite alternate;
}

.aurora__orb--5 {
  width: 38vw; height: 38vw; min-width: 320px; min-height: 320px;
  bottom: -6%; right: 6%;
  background: radial-gradient(circle at 50% 50%, var(--g5) 0%, var(--g5) 26%, transparent 72%);
  animation: drift5 29s var(--ease) infinite alternate;
}

@keyframes drift1 { from { transform: translate3d(0, 0, 0) scale(1); }
                    to   { transform: translate3d(15vw, 9vh, 0) scale(1.24); } }
@keyframes drift2 { from { transform: translate3d(0, 0, 0) scale(1.1); }
                    to   { transform: translate3d(-14vw, 12vh, 0) scale(0.86); } }
@keyframes drift3 { from { transform: translate3d(0, 0, 0) scale(0.95); }
                    to   { transform: translate3d(-12vw, -11vh, 0) scale(1.22); } }
@keyframes drift4 { from { transform: translate3d(0, 0, 0) scale(1.05); }
                    to   { transform: translate3d(13vw, -13vh, 0) scale(0.84); } }
@keyframes drift5 { from { transform: translate3d(0, 0, 0) scale(1); }
                    to   { transform: translate3d(-11vw, -8vh, 0) scale(1.26); } }

/* Film grain. Large blurred gradients band badly on wide gamut displays;
   a little noise on top hides the steps. Static — it never animates. */
.aurora__grain {
  position: absolute;
  inset: 0;
  opacity: 0.2;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.55'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}

/* Slightly smaller, and noticeably looser than the display setting used
   further down the page. At 0.95 line-height the descenders of one line
   nearly touch the caps of the next, which is exactly the kind of tight
   setting that gets hard to track for older readers — 1.04 costs a few
   pixels of height and reads far more calmly. */
.hero__title {
  font-size: clamp(2.6rem, 7.4vw, 5.4rem);
  line-height: 1.04;
  letter-spacing: -0.025em;
  max-width: 16ch;
  margin-inline: auto;
  text-wrap: balance;
}

.hero__lead {
  margin: clamp(22px, 3vw, 30px) auto 0;
  max-width: 44ch;
  font-size: clamp(1.1rem, 1.7vw, 1.4rem);
  line-height: 1.5;
  text-align: center;
  text-wrap: pretty;
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  justify-content: center;
  margin-top: clamp(30px, 4vw, 42px);
}

/* 15px rather than 14: this is the smallest type in the hero, and it sits
   lowest, closest to where the gradient starts. Kept dark for the same
   reason. */
.hero__note { margin-top: 20px; font-size: 15px; color: #403D38; }

/* The device sits in a stage that owns the gradient light behind it. */
.hero__stage {
  position: relative;
  margin-top: clamp(44px, 6vw, 76px);
  display: flex;
  justify-content: center;
  /* The map is shown whole rather than bleeding off the fold: its bottom edge
     is the app's tab bar, and slicing through that reads as a mistake in a way
     a cropped phone body never did. */
  padding-bottom: clamp(56px, 8vw, 104px);
}

/* No device frame — at this width a bezel would shrink the thing the hero is
   about, and the map already reads as a screen. Capped at the source's own
   919px so it is never upscaled. */
.hero__device {
  width: 100%;
  max-width: 919px;
  will-change: transform;
}

.hero__frame {
  position: relative;
  border-radius: clamp(16px, 2vw, 28px);
  overflow: hidden;
  box-shadow:
    0 2px 8px rgba(14, 13, 11, 0.06),
    0 30px 70px rgba(14, 13, 11, 0.20);
  /* Rises into place once, a beat after the headline. Transform only — the
     image is painted at full opacity from the first frame, so animating this
     costs nothing against LCP. */
  animation: heroIn 1.15s cubic-bezier(0.16, 1, 0.3, 1) 0.18s both;
}

.hero__map { width: 100%; border-radius: inherit; }

/* --- Hero halo ------------------------------------------------------------
   The animated part of the hero background. A conic ring of the logo colours,
   blurred until it is only light, turning slowly behind the map — so the
   colour visibly moves around the panel without anything happening to the
   screenshot itself.

   Rotation of an already-rasterised blur is a compositor transform, which is
   why this can turn continuously and cost nothing. */
.hero__halo {
  position: absolute;
  left: 50%;
  top: -6%;
  width: min(1180px, 118%);
  aspect-ratio: 1 / 0.78;
  z-index: 0;
  pointer-events: none;
  border-radius: 50%;
  background: conic-gradient(
    from 0deg,
    var(--g1), var(--g2), var(--g3), var(--g4), var(--g5), var(--g1)
  );
  filter: blur(80px);
  opacity: 0.42;
  transform: translateX(-50%);
  animation: haloTurn 34s linear infinite;
}

@keyframes haloTurn {
  from { transform: translateX(-50%) rotate(0deg); }
  to { transform: translateX(-50%) rotate(360deg); }
}

/* The device sits above the halo. */
.hero__device { position: relative; z-index: 1; }

@keyframes heroIn {
  from { transform: translateY(34px) scale(0.975); }
  to { transform: none; }
}

/* --- Device presentation --------------------------------------------------
   The screenshots already carry Apple's own device render, so `.phone` only
   adds light and motion — it never redraws the frame. */
.phone {
  width: clamp(228px, 26vw, 320px);
  filter: drop-shadow(0 24px 56px rgba(14, 13, 11, 0.20));
}

.phone--lg { width: clamp(250px, 30vw, 356px); }

.device-stage {
  position: relative;
  display: flex;
  justify-content: center;
  isolation: isolate;
}

.device-stage::before {
  content: '';
  position: absolute;
  /* Stays inside the column. A negative inline inset spills past the page
     gutter and makes the document scroll sideways — and because it is a
     pseudo-element, nothing in the DOM shows up as the culprit. The stage is
     already far wider than the device, so the glow reads the same. */
  inset: 6% 0 12%;
  background: var(--gradient);
  filter: blur(72px);
  opacity: 0.3;
  border-radius: 50%;
  z-index: -1;
}

/* --- Section 2: the map scene --------------------------------------------- */
.mapscene {
  position: relative;
  border-radius: var(--r-xl);
  background:
    radial-gradient(120% 100% at 50% 0%, rgba(113, 141, 233, 0.16), transparent 62%),
    var(--bg-warm);
  border: 1px solid var(--line-soft);
  padding: clamp(36px, 6vw, 72px) clamp(20px, 4vw, 56px) 0;
  overflow: hidden;
}

.mapscene__grid {
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(var(--line-soft) 1px, transparent 1px),
    linear-gradient(90deg, var(--line-soft) 1px, transparent 1px);
  background-size: 64px 64px;
  mask-image: radial-gradient(80% 70% at 50% 45%, #000 40%, transparent 100%);
  -webkit-mask-image: radial-gradient(80% 70% at 50% 45%, #000 40%, transparent 100%);
  pointer-events: none;
}

.mapscene__inner {
  position: relative;
  display: flex;
  justify-content: center;
  min-height: clamp(360px, 46vw, 560px);
}

.mapscene .phone {
  align-self: flex-end;
  /* Runs off the bottom edge of the scene on purpose — the device reads as
     rising out of the map rather than pasted onto it. */
  margin-bottom: calc(-1 * clamp(60px, 9vw, 130px));
  position: relative;
  z-index: 2;
}

/* The markers sit in their own layer over the scene. The list itself must not
   swallow pointer events or it would blanket the whole map. */
.pins {
  position: absolute;
  inset: 0;
  list-style: none;
  pointer-events: none;
  z-index: 3;
}

.pin {
  pointer-events: auto;
  position: absolute;
  z-index: 3;
  /* Start state for the scroll reveal; .is-in releases it. */
  opacity: 0;
  transform: translateY(10px) scale(0.9);
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
  transition-delay: var(--d, 0s);
}

/* A real button, so the categories can be clicked, tapped and tabbed to. On
   touch there is no pointer to be near, which made the whole interaction
   invisible on phones — tapping is the same gesture doing the same job. */
.pin__btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 16px 6px 6px;
  background: rgba(255, 255, 255, 0.82);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid var(--line-soft);
  border-radius: 999px;
  font-size: 14px;
  font-weight: 500;
  white-space: nowrap;
  box-shadow: var(--shadow-sm);
  cursor: pointer;
}

.pin.is-in { opacity: 1; transform: none; }

/* Category icon. The colour comes from an inline --c per marker so each one
   carries its own slice of the logo gradient. */
.pin__icon {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  flex: none;
  border-radius: 50%;
  color: var(--c, var(--g4));
  background: color-mix(in srgb, var(--c, var(--g4)) 15%, transparent);
}

.pin__icon svg { width: 15px; height: 15px; }

/* --- Proximity ------------------------------------------------------------
   --near is written by site.js every frame: 0 when the pointer is far, 1 when
   it is on the marker, eased in between. Everything below interpolates off
   that single number, so the markers wake up as the pointer approaches rather
   than snapping the moment it lands on one.

   Deliberately no transitions on these properties. --near is already smoothed
   in JS, and a CSS transition on top would lag behind a value that has
   changed again before it finishes. */
.pin.is-in {
  transform:
    translateY(calc(var(--near, 0) * -7px))
    scale(calc(1 + var(--near, 0) * 0.09));
  z-index: calc(3 + var(--near, 0) * 3);
}

.pin.is-in .pin__btn {
  background: color-mix(in srgb, #fff calc(82% + var(--near, 0) * 18%), transparent);
  box-shadow:
    var(--shadow-sm),
    0 calc(var(--near, 0) * 16px) calc(var(--near, 0) * 32px) rgba(14, 13, 11, calc(var(--near, 0) * 0.16)),
    0 0 0 calc(var(--near, 0) * 1.5px) color-mix(in srgb, var(--c, var(--g4)) 45%, transparent);
}

.pin.is-in .pin__icon {
  background: color-mix(in srgb, var(--c, var(--g4)) calc(15% + var(--near, 0) * 85%), transparent);
  color: color-mix(in srgb, #fff calc(var(--near, 0) * 100%), var(--c, var(--g4)));
  transform: scale(calc(1 + var(--near, 0) * 0.1));
}

/* While the pointer is anywhere in the scene the far markers recede, so the
   ones you are approaching are briefly the only categories on the Bay. */
.mapscene.is-pointer .pin.is-in,
.mapscene.has-lock .pin.is-in {
  opacity: calc(0.42 + var(--near, 0) * 0.58);
}

.pin--a { left: 4%; top: 12%; --c: var(--g1); }
.pin--b { right: 6%; top: 7%; --c: var(--g4); }
.pin--c { left: 1%; top: 46%; --c: var(--g2); }
.pin--d { right: 2%; top: 38%; --c: var(--g5); }
.pin--e { left: 8%; top: 74%; --c: var(--g3); }
.pin--f { right: 9%; top: 68%; --c: var(--g1); }

/* --- Split layout (sections 3, 5, 6) -------------------------------------- */
.split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(36px, 6vw, 88px);
  align-items: center;
}

.split--reverse .split__media { order: -1; }
.split__body { max-width: 46ch; }

/* Standing caveat under a claim — quieter than body copy, never hidden. */
.disclaimer {
  margin-top: 22px;
  max-width: 46ch;
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink-3);
}

/* --- Section 4: the loop -------------------------------------------------- */
.loop {
  display: grid;
  grid-template-columns: 0.95fr 1.05fr;
  gap: clamp(36px, 6vw, 80px);
}

.loop__sticky {
  position: sticky;
  top: calc(var(--nav-h) + clamp(28px, 6vw, 72px));
  align-self: start;
}

/* The four steps share a rail, and the filled part of it tracks how far you
   have read. The dots sit on the same axis, so the fill visibly reaches each
   one as its step becomes the active one. */
.loop__steps {
  position: relative;
  list-style: none;
  display: grid;
  gap: 4px;
  padding-left: 0;
}

.loop__steps::before,
.loop__steps::after {
  content: '';
  position: absolute;
  left: 18px;
  top: 0;
  width: 2px;
  border-radius: 2px;
}

.loop__steps::before { bottom: 0; background: var(--line); }

.loop__steps::after {
  height: calc(var(--p, 0) * 100%);
  background: linear-gradient(
    to bottom,
    var(--g1), var(--g2) 28%, var(--g3) 52%, var(--g4) 76%, var(--g5)
  );
}

.loop__step {
  position: relative;
  padding: clamp(26px, 3vw, 38px) 0 clamp(26px, 3vw, 38px) 52px;
  border-top: 1px solid var(--line-soft);
}

.loop__step:last-child { border-bottom: 1px solid var(--line-soft); }

.loop__step::before {
  content: '';
  position: absolute;
  left: 14px;
  top: calc(clamp(26px, 3vw, 38px) + 11px);
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--ink-3);
  opacity: 0.4;
  transition: opacity 0.5s var(--ease), transform 0.5s var(--ease), background-color 0.5s var(--ease);
}

.loop__step.is-active::before {
  opacity: 1;
  transform: scale(1.5);
  /* The logo gradient, not the standalone coral accent — this dot sits on the
     brand rail and should be made of the same colours it is travelling down. */
  background: var(--gradient);
}

.loop__step h3 {
  font-size: clamp(1.5rem, 2.6vw, 2.1rem);
  color: var(--ink-3);
  transition: color 0.5s var(--ease);
}

.loop__step.is-active h3 { color: var(--ink); }
.loop__step p { margin-top: 10px; color: var(--ink-2); max-width: 40ch; }

/* --- Postcard stack -------------------------------------------------------
   Photos from events, dealt out like a handful of prints. They sit squared up
   on top of each other and fan apart as the section arrives, which is the
   whole point: a night out is a pile of pictures, not a grid.

   Every card animates from the same stacked origin, so the fan is created
   entirely by the end state — nothing has to be measured in JS. */
.stack {
  position: relative;
  display: grid;
  place-items: center;
  min-height: clamp(320px, 42vw, 460px);
  perspective: 1200px;
}

.stack__card {
  grid-area: 1 / 1;
  width: min(300px, 66vw);
  padding: 12px 12px 44px;
  background: var(--surface);
  border-radius: 8px;
  box-shadow:
    0 1px 2px rgba(14, 13, 11, 0.10),
    0 18px 44px rgba(14, 13, 11, 0.18);
  /* Stacked and square before the reveal. */
  transform: rotate(0deg) translate(0, 0) scale(0.94);
  opacity: 0;
  transition:
    transform 1s cubic-bezier(0.22, 1.3, 0.4, 1),
    opacity 0.7s var(--ease);
  transition-delay: var(--d, 0s);
}

.stack__card img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: 3px;
  background: var(--bg-warm);
}

.stack__caption {
  margin-top: 12px;
  font-size: 14px;
  color: var(--ink-3);
  text-align: center;
}

/* Dealt out. Each card gets its own resting angle and offset. */
.stack.is-in .stack__card { opacity: 1; }
.stack.is-in .stack__card--1 { transform: rotate(-9deg) translate(-22%, 4%) scale(1); }
.stack.is-in .stack__card--2 { transform: rotate(4deg) translate(14%, -6%) scale(1); }
.stack.is-in .stack__card--3 { transform: rotate(-2deg) translate(-2%, 12%) scale(1); }

/* The top card lifts a little on hover, like lifting a print off the pile. */
@media (hover: hover) {
  .stack__card { transition-property: transform, opacity, box-shadow; }
  .stack.is-in .stack__card:hover {
    transform: rotate(0deg) translate(0, -8px) scale(1.03);
    box-shadow: 0 2px 4px rgba(14, 13, 11, 0.12), 0 28px 60px rgba(14, 13, 11, 0.24);
    z-index: 4;
  }
}

@media (max-width: 780px) {
  .stack { min-height: 340px; }
  .stack__card { width: min(230px, 60vw); padding: 9px 9px 34px; }
  .stack.is-in .stack__card--1 { transform: rotate(-8deg) translate(-16%, 4%) scale(1); }
  .stack.is-in .stack__card--2 { transform: rotate(5deg) translate(12%, -6%) scale(1); }
}

/* --- Section 7: memories -------------------------------------------------- */
.memory {
  position: relative;
  display: grid;
  place-items: center;
  min-height: clamp(300px, 40vw, 440px);
}

.memory__card {
  position: absolute;
  width: min(320px, 78vw);
  padding: 22px;
  border-radius: var(--r-md);
  background: var(--surface);
  border: 1px solid var(--line-soft);
  box-shadow: var(--shadow-md);
  transition: transform 0.9s var(--ease), opacity 0.9s var(--ease);
}

.memory__card--live { transform: translateY(0) rotate(-3deg); }

.memory__card--saved {
  transform: translateY(26px) rotate(4deg);
  background: var(--bg-warm);
}

.memory.is-in .memory__card--live { transform: translateY(-30px) rotate(-6deg); opacity: 0.45; }
.memory.is-in .memory__card--saved { transform: translateY(34px) rotate(3deg); }

.memory__label {
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.memory__title { margin-top: 8px; font-size: 1.15rem; font-weight: 700; letter-spacing: -0.02em; }
.memory__meta { margin-top: 6px; font-size: 14px; color: var(--ink-3); }

.memory__swatch {
  height: 96px;
  margin-top: 16px;
  border-radius: var(--r-sm);
  background: var(--gradient);
  opacity: 0.9;
}

/* --- Feature grid --------------------------------------------------------- */
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(258px, 1fr));
  gap: clamp(20px, 3vw, 34px);
  margin-top: clamp(40px, 5vw, 64px);
  list-style: none;
}

/* Four equal features. auto-fit would fit three across the 1084px content
   column and orphan the fourth, so the breakpoints are set explicitly. */
.grid--quad { grid-template-columns: repeat(4, minmax(0, 1fr)); }

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

@media (max-width: 560px) {
  .grid--quad { grid-template-columns: minmax(0, 1fr); }
}

.feature { border-top: 1px solid var(--line); padding-top: 22px; }
.feature h3 { font-size: 1.12rem; letter-spacing: -0.02em; }
.feature p { margin-top: 9px; color: var(--ink-2); font-size: 15.5px; line-height: 1.6; }
.section--dark .feature { border-top-color: rgba(244, 241, 234, 0.16); }
.section--dark .feature p { color: rgba(244, 241, 234, 0.68); }

/* --- FAQ ------------------------------------------------------------------ */
.faq { margin-top: clamp(36px, 5vw, 56px); max-width: 820px; }
.faq__item { border-top: 1px solid var(--line); }
.faq__item:last-child { border-bottom: 1px solid var(--line); }

.faq__q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  width: 100%;
  padding: 26px 0;
  font-size: clamp(1.02rem, 1.6vw, 1.18rem);
  font-weight: 500;
  letter-spacing: -0.02em;
  text-align: left;
  cursor: pointer;
  list-style: none;
}

.faq__q::-webkit-details-marker { display: none; }
.faq__sign { position: relative; width: 15px; height: 15px; flex: none; }

.faq__sign::before,
.faq__sign::after {
  content: '';
  position: absolute;
  inset: 50% 0 auto;
  height: 1.5px;
  background: var(--ink);
  transition: transform 0.4s var(--ease);
}

.faq__sign::after { transform: rotate(90deg); }
.faq__item[open] .faq__sign::after { transform: rotate(0deg); }
.faq__a { padding: 0 0 28px; max-width: 66ch; color: var(--ink-2); }
.faq__a a { color: var(--ink); text-underline-offset: 3px; }
.faq__a p + p { margin-top: 12px; }
.faq__item[open] .faq__a { animation: faqIn 0.45s var(--ease); }

@keyframes faqIn {
  from { opacity: 0; transform: translateY(-6px); }
  to { opacity: 1; transform: none; }
}

/* --- Final CTA ------------------------------------------------------------ */
.cta {
  position: relative;
  overflow: hidden;
  color: #fff;
  text-align: center;
  background: var(--gradient);
  background-size: 220% 220%;
  animation: ctaShift 26s ease-in-out infinite alternate;
}

@keyframes ctaShift {
  from { background-position: 0% 50%; }
  to { background-position: 100% 50%; }
}

.cta__title {
  font-size: clamp(2.8rem, 9.5vw, 7rem);
  line-height: 0.92;
  text-wrap: balance;
}

.cta .btn--light { margin-top: clamp(32px, 4vw, 46px); }
.cta__note { margin-top: 20px; font-size: 14px; color: rgba(255, 255, 255, 0.82); }

/* --- Footer --------------------------------------------------------------- */
.site-footer { background: var(--bg-warm); padding-block: clamp(56px, 7vw, 88px) 40px; }

.site-footer__top {
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  gap: clamp(32px, 5vw, 64px);
  padding-bottom: 44px;
  border-bottom: 1px solid var(--line-soft);
}

.site-footer__blurb { margin-top: 18px; color: var(--ink-2); max-width: 40ch; font-size: 15.5px; }

.footer-nav { display: flex; flex-wrap: wrap; gap: 12px 48px; align-content: start; }
.footer-nav ul { list-style: none; display: grid; gap: 2px; }

.footer-nav h3 {
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 16px;
  font-weight: 500;
}

.footer-nav a {
  /* Same 24px-minimum reasoning as the header links. */
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  font-size: 15.5px;
  color: var(--ink-2);
  text-decoration: none;
}

@media (hover: hover) {
  .footer-nav a:hover { color: var(--ink); }
}

.site-footer__bottom {
  display: flex;
  flex-wrap: wrap;
  gap: 12px 28px;
  justify-content: space-between;
  padding-top: 26px;
  font-size: 14px;
  color: var(--ink-3);
}

/* --- Legal pages ---------------------------------------------------------- */
.legal { padding-block: clamp(56px, 8vw, 104px) clamp(72px, 9vw, 120px); }
.legal__head { max-width: 760px; }
.legal__title { font-size: clamp(2.2rem, 5.5vw, 3.4rem); }
.legal__meta { margin-top: 16px; font-size: 15px; color: var(--ink-3); }

.legal__tabs { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 30px; list-style: none; }

.legal__tabs a {
  display: inline-block;
  padding: 10px 18px;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: 14.5px;
  color: var(--ink-2);
  text-decoration: none;
  transition: border-color 0.3s var(--ease), color 0.3s var(--ease);
}

.legal__tabs a[aria-current='page'] { background: var(--ink); border-color: var(--ink); color: #fff; }

@media (hover: hover) {
  .legal__tabs a:hover { border-color: var(--ink); color: var(--ink); }
}

.prose { max-width: 68ch; margin-top: clamp(40px, 5vw, 60px); }

.prose h2 {
  font-size: clamp(1.15rem, 2vw, 1.35rem);
  letter-spacing: -0.02em;
  margin-top: 44px;
  padding-top: 22px;
  border-top: 1px solid var(--line-soft);
}

.prose h2:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
.prose p { margin-top: 12px; color: var(--ink-2); }
.prose a { color: var(--ink); text-underline-offset: 3px; }
.prose ul { margin-top: 12px; padding-left: 20px; color: var(--ink-2); }
.prose li { margin-top: 8px; }

/* --- Scroll reveal --------------------------------------------------------
   One base rule, several entrances. Everything arriving identically is what
   made the page feel flat; giving each kind of thing its own move — copy
   rising, media settling in from a slight overshoot, columns coming in from
   the side they live on — reads as choreography instead of a single effect
   applied everywhere.

   Every variant lands on the same final state (opacity 1, no transform, no
   filter), so nothing can be stranded mid-animation. */
[data-reveal] {
  opacity: 0;
  transition:
    opacity var(--dur) var(--ease),
    transform var(--dur) var(--ease),
    filter var(--dur) var(--ease);
  transition-delay: var(--d, 0s);
  transform: translateY(26px);
}

/* Slight overshoot. Used where something should feel like it lands rather
   than slides to a stop. */
[data-reveal='pop'],
[data-reveal='media'] {
  transition-timing-function: cubic-bezier(0.34, 1.42, 0.64, 1);
}

[data-reveal='pop'] { transform: translateY(24px) scale(0.94); }

/* Media settles out of a soft focus — the blur is what stops a photo or a
   screenshot from just sliding like a block of text. */
[data-reveal='media'] {
  transform: translateY(30px) scale(0.96);
  filter: blur(10px);
}

[data-reveal='left'] { transform: translate3d(-42px, 12px, 0); }
[data-reveal='right'] { transform: translate3d(42px, 12px, 0); }

/* For a big display line: rises out of its own baseline rather than fading in
   as a block. */
[data-reveal='rise'] {
  transform: translateY(0.34em);
  clip-path: inset(-0.25em -0.2em -0.18em -0.2em);
}

[data-reveal].is-in {
  opacity: 1;
  transform: none;
  filter: none;
  clip-path: inset(-100% -100% -100% -100%);
}

/* --- Word-by-word headings ------------------------------------------------
   Each word sits in its own clipped line and rides up out of it. Split by
   site.js; until that runs the heading is ordinary text, which is also what a
   crawler and a copy-paste see.

   The mask needs room below the baseline or descenders get sliced, hence the
   padding/negative-margin pair — the box grows to fit the tail of a "g" while
   the line still occupies its normal height. */
.is-split .word {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
  padding-bottom: 0.14em;
  margin-bottom: -0.14em;
}

.is-split .word__i {
  display: inline-block;
  transform: translateY(115%);
  transition: transform 0.78s cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: calc(var(--wi) * 0.042s + var(--d, 0s));
}

.is-in.is-split .word__i { transform: none; }

/* Splitting supplies the entrance, so the block-level reveal stands down and
   only the words move. */
[data-reveal][data-split] {
  opacity: 1;
  transform: none;
  clip-path: none;
}

/* Without JS nothing would ever get `.is-in`, so the page must not depend on
   it for legibility. */
.no-js [data-reveal],
.no-js .pin {
  opacity: 1;
  transform: none;
  filter: none;
  clip-path: none;
}


/* --- Responsive ----------------------------------------------------------- */
@media (max-width: 900px) {
  .split,
  .loop { grid-template-columns: 1fr; }
  .split--reverse .split__media { order: 0; }
  .loop__sticky { position: static; }
  .site-footer__top { grid-template-columns: 1fr; }
  .lead { max-width: 52ch; }
}

@media (max-width: 780px) {
  body { font-size: 16px; }
  .nav-links { display: none; }
  .nav-toggle { display: inline-flex; }
  /* The bar CTA would crowd the toggle at this width; it moves into the
     opened panel instead. */
  .nav-actions .btn { display: none; }

  .site-nav.is-open {
    background: rgba(245, 241, 235, 0.96);
    backdrop-filter: saturate(180%) blur(18px);
    -webkit-backdrop-filter: saturate(180%) blur(18px);
  }

  .nav-panel {
    position: fixed;
    inset: var(--nav-h) 0 auto;
    display: grid;
    gap: 0;
    padding: 6px var(--gutter) 30px;
    background: rgba(245, 241, 235, 0.97);
    backdrop-filter: saturate(180%) blur(18px);
    -webkit-backdrop-filter: saturate(180%) blur(18px);
    border-bottom: 1px solid var(--line-soft);
    list-style: none;
    /* Collapsed by default; height animates so the panel does not pop. */
    max-height: 0;
    overflow: hidden;
    opacity: 0;
    visibility: hidden;
    transition: max-height 0.5s var(--ease), opacity 0.35s var(--ease), visibility 0.35s;
  }

  .nav-panel.is-open { max-height: 78vh; opacity: 1; visibility: visible; }

  .nav-panel a:not(.btn) {
    display: block;
    padding: 16px 0;
    font-size: 20px;
    letter-spacing: -0.02em;
    text-decoration: none;
    border-bottom: 1px solid var(--line-soft);
  }

  .nav-panel .btn { width: 100%; margin-top: 20px; }

  .hero__stage { padding-bottom: clamp(40px, 9vw, 64px); }

  /* Five blurred layers is more compositing than a phone needs for an effect
     this diffuse. Three, blurred less, is visually the same here. */
  .aurora__orb { filter: blur(42px); opacity: 0.4; }
  .aurora__orb--3,
  .aurora__orb--5 { display: none; }
  .aurora__grain { opacity: 0.22; }

  .mapscene { padding-inline: 16px; border-radius: var(--r-lg); }
  .mapscene__inner { min-height: 400px; }
  /* Six floating pins is noise at this width — keep the four that frame the
     device and drop the pair that would collide with it. */
  .pin { font-size: 13px; padding: 7px 12px 7px 9px; }
  .pin--c,
  .pin--d { display: none; }
  .pin--a { left: 0; top: 4%; }
  .pin--b { right: 0; top: 14%; }
  .pin--e { left: 0; top: 62%; }
  .pin--f { right: 0; top: 72%; }
  .phone,
  .phone--lg { width: min(250px, 62vw); }
  .loop__step { padding-left: 40px; }
  .loop__step::before { left: 8px; }
}

@media (max-width: 420px) {
  .hero__actions { flex-direction: column; align-items: stretch; }
  .hero__actions .btn { width: 100%; }
}

/* Wide desktop: let the type breathe rather than stretching the measure. */
@media (min-width: 1500px) {
  :root { --wrap: 1260px; }
}

/* --- Motion preferences --------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }

  /* Reveals must not strand content invisible once their transition is gone.
     Every variant's start state is cleared here, not just the transform —
     a blurred or clipped element left behind is as broken as an invisible one. */
  [data-reveal],
  .pin {
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
    clip-path: none !important;
  }

  /* Split words live inside an overflow-hidden mask, so a stranded transform
     here does not dim them — it hides them completely. */
  .is-split .word__i { transform: none !important; }

  /* Proximity is skipped entirely in JS, but the markers still resolve their
     transform through --near, so pin it at rest. */
  .pin.is-in { transform: none !important; }

  .memory__card--live { transform: rotate(-3deg) !important; opacity: 1 !important; }
  .memory__card--saved { transform: translateY(30px) rotate(3deg) !important; }
  /* The parallax target, the entrance wrapper and the halo all rest. */
  .hero__device,
  .hero__frame { transform: none !important; }
  .hero__halo { animation: none !important; }
}
