/* Bueno — the scrolling-device landing page.
 *
 * Same token set as styles.css (DESIGN.md), same rule about colour: the chrome
 * is monochrome and every coloured pixel is subject matter — a brand mark, or
 * the app's own score badges inside the phone.
 *
 * The whole page is driven by ONE number, --adv: a continuous position through
 * the filmstrip measured in screens (0 .. n-1). scroll.js writes it to :root and
 * nothing else; captions, the rail and the device all derive from it here. That
 * is also what makes the page capturable frame-by-frame — ?p=0.42 sets --adv
 * directly and every frame is a pure function of it, with no transition to
 * catch mid-flight.
 */

:root {
  /* The room's light level, written by the driver: 0 dark, 1 light, and every
     screen of the walkthrough alternates. Every colour below is mixed from it,
     so the whole page crossfades with the scroll rather than switching. */
  --tone: 0;
  /* The grey that overlays are made of: white on a dark room, near-black on a
     light one. A number, so it can be an rgb() channel. */
  --g: calc(255 - 235 * var(--tone));

  --ground: color-mix(in oklab, #eceeef calc(var(--tone) * 100%), #0a0a0a);
  --canvas: color-mix(in oklab, #f4f5f6 calc(var(--tone) * 100%), #0f0f0f);
  --raised: color-mix(in oklab, #ffffff calc(var(--tone) * 100%), #1f1f1f);
  --ink: color-mix(in oklab, #141414 calc(var(--tone) * 100%), #ffffff);
  --ink-muted: color-mix(in oklab, #6f6f6f calc(var(--tone) * 100%), #adadad);
  --ink-subtle: color-mix(in oklab, #8c8c8c calc(var(--tone) * 100%), #878787);

  --surface-1: rgb(var(--g) var(--g) var(--g) / 0.06);
  --surface-2: rgb(var(--g) var(--g) var(--g) / 0.09);
  --hairline: rgb(var(--g) var(--g) var(--g) / 0.12);

  --lift: 0 10px 30px rgba(0, 0, 0, 0.5), 0 2px 6px rgba(0, 0, 0, 0.35);
  --lift-device: 0 40px 90px -20px rgba(0, 0, 0, 0.75), 0 12px 28px rgba(0, 0, 0, 0.5);

  --radius-card: 22px;
  --radius-panel: 32px;
  --radius-pill: 9999px;

  --gutter: 20px;
  --ease: cubic-bezier(0.32, 0.72, 0, 1);

  /* Written by scroll.js. */
  --adv: 0;
  --n: 5;

  color-scheme: dark;
}

@media (min-width: 720px)  { :root { --gutter: 24px; } }
@media (min-width: 1280px) { :root { --gutter: 32px; } }

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

html { scroll-behavior: smooth; }

body {
  margin: 0;
  padding: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-size: 16px;
  font-weight: 450;
  line-height: 1.375;
  letter-spacing: 0.2px;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.panel {
  position: relative;
  background: var(--canvas);
  overflow: clip;
}

/* ---------------------------------------------------------------- room
   Depth on a dark page is light, not layers: one warm key behind the device,
   one cool fill behind the copy, and grain so the gradients do not band.
   All of it is painted — no images, nothing to load. */
.room {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: clip;
  /* Lights are for the dark room. In the light one they would only fog it. */
  opacity: calc(1 - var(--tone) * 0.88);
}

.room__key,
.room__fill {
  position: absolute;
  border-radius: 50%;
  filter: blur(10px);
}

/* Behind the phone, slightly above it: the light the device is lit by. */
.room__key {
  right: -6%;
  top: -22%;
  width: 62vw;
  height: 62vw;
  background: radial-gradient(circle at 50% 50%,
    rgba(255, 244, 226, 0.15) 0%,
    rgba(255, 236, 210, 0.07) 30%,
    rgba(255, 236, 210, 0) 62%);
}

/* Cooler, lower, behind the words, so the two halves of the screen are not the
   same flat black. */
.room__fill {
  left: -18%;
  bottom: -34%;
  width: 70vw;
  height: 70vw;
  background: radial-gradient(circle at 50% 50%,
    rgba(150, 180, 255, 0.07) 0%,
    rgba(120, 150, 255, 0.035) 34%,
    rgba(120, 150, 255, 0) 64%);
}

/* Fine, static, and cheap: one tile of SVG noise. 3.5% is the point where it
   stops being visible as texture and starts reading as depth. */
/* The floor. A plane the device can stand on, and the reason the bottom of the
   screen is not the same black as the top. */
.room__floor {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 42%;
  background:
    linear-gradient(to top, rgba(255, 255, 255, 0.045), rgba(255, 255, 255, 0) 70%),
    radial-gradient(120% 80% at 72% 100%, rgba(255, 246, 232, 0.06), transparent 60%);
}

/* Corners fall away, so the lit middle reads as the middle of something. */
.room__vignette {
  position: absolute;
  inset: 0;
  background: radial-gradient(130% 95% at 50% 46%,
    transparent 38%,
    rgba(0, 0, 0, 0.45) 100%);
}

.room__grain {
  position: absolute;
  inset: 0;
  opacity: 0.035;
  mix-blend-mode: screen;
  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.8' numOctaves='3'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---------------------------------------------------------------- stage
   Height = how far you scroll, and nothing more. No runway at either end: the
   hero is pinned from the first pixel to the last, so the page never shows
   anything but itself. The beat of stillness on each screen comes from HOLD in
   scroll.js, not from empty scroll. */
.stage { height: calc(var(--n) * 90vh); }

.pin {
  position: sticky;
  top: 0;
  /* svh, not vh: on a phone the browser chrome must not make this taller than
     what is actually visible, or the page scrolls when it should be frozen. */
  height: 100svh;
  display: flex;
  align-items: center;
  overflow: clip;
}

/* ---------------------------------------------------------------- hero */
.hero {
  position: relative;
  z-index: 2;
  width: 100%;
  max-width: 1320px;
  margin: 0 auto;
  padding: 0 clamp(24px, 5vw, 76px);
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
  align-items: center;
  gap: clamp(32px, 5vw, 72px);
}

/* ---------------------------------------------------------------- left */
/* One rhythm down the column: 20 between the things that belong together,
   28 between the blocks that do not. */
/* Two measures, not four: the headline row gets the wider one because it has
   an icon beside it, and everything that is read as prose keeps the narrower
   one. Both share a left edge. */
.say { max-width: 640px; }
.steps, .sub, .signup, .stores { max-width: 560px; }

/* The icon as it looks on a home screen: the real artwork, the platform's
   corner, lit by the same key as everything else. 22.5% is the radius that
   reads as an iOS squircle without drawing one. */
/* The lockup: icon and name, the size a product name should be on its own
   landing page. Everything under it is the pitch; this is whose pitch it is. */
.brand {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-bottom: 22px;
}

.brand__name {
  font-size: clamp(30px, 2.6vw, 40px);
  font-weight: 680;
  letter-spacing: -0.035em;
  line-height: 1;
  color: var(--ink);
}

/* No pill: one quiet line under the store buttons, where it is a fact about
   them rather than a badge stuck on the headline. */
.soon {
  margin: 0 0 14px;
  font-size: 13px;
  letter-spacing: 0.2px;
  color: var(--ink-muted);
}

.appicon {
  display: block;
  flex: none;
  width: clamp(64px, 5.4vw, 88px);
  height: auto;
  aspect-ratio: 1;
  border-radius: 22.5%;
  box-shadow:
    0 1px 2px rgba(0, 0, 0, 0.5),
    0 18px 34px -10px rgba(0, 0, 0, 0.75),
    0 0 0 1px rgba(255, 255, 255, 0.06) inset;
}

/* Not flat white: the top of the line catches the key light and the bottom
   falls off, which is what stops a big headline reading as a sticker. */
h1 {
  background: linear-gradient(175deg,
    var(--ink) 18%,
    color-mix(in oklab, var(--ink) 72%, transparent));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  margin: 0;
  /* A clipped gradient box is exactly the line box, so descenders and the
     period were being cut off at the bottom. This gives them the room. */
  padding-bottom: 0.12em;
  margin-bottom: 18px;
  font-size: clamp(32px, 3.6vw, 56px);
  font-weight: 680;
  line-height: 1.06;
  /* Tracking inverts across the scale — opened below 20px, tightened above. */
  letter-spacing: -0.032em;
}

.sub {
  margin: 0 0 26px;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 8px;
  font-size: 15px;
  font-weight: 440;
  line-height: 1.5;
  color: var(--ink-muted);
}

.sub li {
  position: relative;
  padding-left: 21px;
}

/* A dash, not a dot: the list is four statements, not a checklist. */
.sub li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.72em;
  width: 7px;
  height: 1.5px;
  border-radius: 1px;
  background: var(--ink);
  opacity: 0.45;
}

/* The step captions are stacked in one grid cell so that swapping them cannot
   reflow the column — the headline above must not move by a single pixel. */
.steps {
  display: grid;
  margin-bottom: 22px;
  min-height: 30px;
}

.step {
  grid-area: 1 / 1;
  margin: 0;
  align-self: start;
  font-size: 17px;
  line-height: 1.45;
  letter-spacing: -0.005em;
  color: var(--ink-muted);
  /* opacity and offset are set per-element by scroll.js from --adv. */
  opacity: 0;
  transform: translateY(8px);
  will-change: opacity, transform;
}
.step b { font-weight: 640; color: var(--ink); }

/* ---- signup.
   One pill holding the field and the button, the way the hero page does it.
   The button is the page's only filled control, which is how it stays the
   obvious next thing to do on a page with no other affordance. */
.signup { margin-bottom: 16px; }

/* Rounded, not capsule. The pill chip is the shape this page is trying not to
   be, so the one control on it is a soft rectangle instead. */
.field {
  display: flex;
  align-items: center;
  gap: 8px;
  max-width: 100%;
  padding: 6px 6px 6px 18px;
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-radius: 16px;
  box-shadow: var(--lift);
  transition: box-shadow 0.2s var(--ease), border-color 0.2s var(--ease);
}
.field:focus-within { border-color: rgb(var(--g) var(--g) var(--g) / 0.3); }

.field input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0;
  font: inherit;
  font-size: 15px;
  color: var(--ink);
  background: none;
  border: 0;
  outline: none;
}
.field input::placeholder { color: var(--ink-subtle); }

.button {
  flex: none;
  padding: 11px 20px;
  font: inherit;
  font-size: 14px;
  font-weight: 620;
  letter-spacing: 0.2px;
  color: var(--canvas);
  background: var(--ink);
  border: 0;
  border-radius: 12px;
  cursor: pointer;
  transition: opacity 0.2s var(--ease);
}
.button:hover { opacity: 0.88; }
.button:disabled { opacity: 0.5; cursor: default; }

/* Reserves no space until it has something to say — an empty line here would
   push the store row down on every load. */
.signup__status {
  margin: 0;
  font-size: 13px;
  line-height: 1.4;
  color: var(--ink-muted);
}
.signup__status:empty { display: none; }
.signup__status[data-state="ok"]    { color: #217d08; }
.signup__status[data-state="error"] { color: #cd2c04; }

.stores { display: flex; flex-wrap: wrap; gap: 10px; }

.store {
  display: inline-flex;
  align-items: center;
  gap: 11px;
  padding: 11px 20px 11px 16px;
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-radius: 14px;
  box-shadow: var(--lift);
}
.store svg { width: 22px; height: 22px; flex: none; }

.store__t {
  display: flex;
  flex-direction: column;
  font-size: 15px;
  font-weight: 620;
  line-height: 1.15;
  letter-spacing: -0.01em;
  color: var(--ink);
}
.store__t small {
  font-size: 11px;
  font-weight: 480;
  letter-spacing: 0.3px;
  text-transform: uppercase;
  color: var(--ink-subtle);
}

/* ---------------------------------------------------------------- right */
.show {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 22px;
}

/* A soft wash so the device is not floating on flat paper. */
.show::before {
  content: "";
  position: absolute;
  z-index: -1;
  left: 50%;
  top: 50%;
  width: 118%;
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  background: radial-gradient(circle, rgb(var(--g) var(--g) var(--g) / 0.075), transparent 62%);
}

/* ---------------------------------------------------------------- device
   Drawn, not photographed. One shared aspect ratio from the real capture
   (1206 x 2445 after the status bar is cropped off). */
.phone {
  /* The page is one frozen screen, so the device's limit is always vertical:
     size it from the viewport height and let width follow. 2.17 is the screen's
     own ratio, plus the bezel. Width still has a say on very wide, short
     windows, which is what the clamp is for. */
  --w: min(clamp(280px, 28vw, 392px), calc((88svh - 26px) / 2.17));
  width: var(--w);
  flex: none;
  /* Yaw and pitch, written by the driver from the pointer. The defaults are the
     resting pose, so it reads as an object before anyone moves the mouse. */
  --ty: -11deg;
  --tx: 3deg;
  /* A closer camera exaggerates the near edge, which is what makes the shape
     read as depth rather than a skew. */
  transform-style: preserve-3d;
  transform: perspective(900px) rotateY(var(--ty)) rotateX(var(--tx))
             translateZ(0);
  transition: transform 0.35s var(--ease);
  will-change: transform;
}

.phone__body {
  position: relative;
  padding: 11px;
  border-radius: calc(var(--w) * 0.155);
  background: linear-gradient(150deg, #3a3d40, #17191b 42%, #0d0e10 72%, #2c2f32);
  /* Three shadows, not one: contact, the body of the shadow, and a wide ambient
     wash. A single blur reads as a sticker. */
  box-shadow:
    0 2px 6px rgba(0, 0, 0, 0.6),
    0 30px 60px -12px rgba(0, 0, 0, 0.75),
    0 80px 140px -40px rgba(0, 0, 0, 0.9);
}

/* What the device is standing on. A mirrored, fading copy of its own silhouette
   — no content, just enough to say there is a surface. */
.phone::after {
  content: "";
  position: absolute;
  left: 8%;
  right: 8%;
  top: 100%;
  height: 22%;
  background: linear-gradient(rgba(255, 255, 255, 0.10), rgba(255, 255, 255, 0));
  filter: blur(14px);
  transform: scaleY(-1);
  opacity: 0.5;
  pointer-events: none;
}

/* The band of light along the rail — what makes a drawn phone read as metal. */
.phone__body::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: linear-gradient(150deg, rgba(255,255,255,0.55), rgba(255,255,255,0.04) 32%, rgba(255,255,255,0) 60%, rgba(255,255,255,0.28));
  -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 FULL device screen, status bar included: 1206 x 2622 is the real iPhone 17
   Pro capture. The shots below it were cropped at 177px, which is the 59pt
   status bar — so the bar's share of the height is 177/2622, and the window
   underneath is left with exactly the aspect the images already have. */
.phone__screen {
  position: relative;
  aspect-ratio: 1206 / 2622;
  display: flex;
  flex-direction: column;
  border-radius: calc(var(--w) * 0.125);
  background: #ffffff;
  overflow: hidden;
}

/* ---- the painted status bar (see scroll.html for why it is not captured).
   It occupies its own band rather than floating over the screens: these shots
   put content against the safe area, so an overlay washed out the first row. */
.status {
  flex: 0 0 6.75%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* iOS sets the bar's contents low in the band, not centred in it. */
  padding: 0.6% 8.5% 0 8.5%;
  color: #141414;
  background: #ffffff;
}

.window {
  position: relative;
  flex: 1 1 auto;
  overflow: hidden;
}

.status__time {
  font-size: calc(var(--w) * 0.052);
  font-weight: 640;
  letter-spacing: 0.1px;
}

.status__right { display: flex; align-items: center; gap: calc(var(--w) * 0.017); }
.status__ic { width: calc(var(--w) * 0.05); height: auto; }

.status__batt {
  position: relative;
  width: calc(var(--w) * 0.072);
  height: calc(var(--w) * 0.036);
  border-radius: calc(var(--w) * 0.011);
  border: 1.2px solid currentColor;
  opacity: 0.9;
}
.status__batt i {
  position: absolute;
  inset: 1.4px;
  border-radius: 1px;
  background: currentColor;
}
.status__batt::after {
  content: "";
  position: absolute;
  left: 100%;
  top: 50%;
  width: 1.3px;
  height: 38%;
  margin-left: 0.8px;
  transform: translateY(-50%);
  border-radius: 0 1px 1px 0;
  background: currentColor;
  opacity: 0.55;
}

/* 37pt tall in an 874pt screen, sitting 11pt down — the real proportions, so it
   lands inside the status band rather than on the screen below it. */
.island {
  position: absolute;
  z-index: 4;
  top: 1.26%;
  left: 50%;
  width: 30.5%;
  height: 4.2%;
  transform: translateX(-50%);
  background: #08090a;
  border-radius: var(--radius-pill);
}

/* ---- the filmstrip.
   Each screen is exactly the height of the window, so the strip's own height is
   one screen and a translateY of -100% advances by exactly one. --adv is
   continuous, which is what makes the movement a scrub rather than a slideshow. */
.strip {
  display: flex;
  flex-direction: column;
  height: 100%;
  transform: translate3d(0, calc(var(--adv) * -100%), 0);
  will-change: transform;
}

.screen { flex: 0 0 100%; }

.screen img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top;
}

/* A fixed sheen across the glass. It must not travel with the strip — the
   highlight belongs to the device, not to what is on it. */
.glare {
  position: absolute;
  z-index: 5;
  inset: 0;
  pointer-events: none;
  /* --sheen is the tilt, normalised: the band slides across the glass as the
     device turns, which is the whole trick. */
  background: linear-gradient(105deg,
    rgba(255, 255, 255, 0) calc(30% + var(--sheen, 0) * 22%),
    rgba(255, 255, 255, 0.17) calc(44% + var(--sheen, 0) * 22%),
    rgba(255, 255, 255, 0) calc(58% + var(--sheen, 0) * 22%));
  transition: background 0.35s var(--ease);
}

/* One link, in the corner, out of the way of a page that is otherwise a single
   frozen screen with nowhere to put a footer. */
.legal {
  position: absolute;
  z-index: 3;
  left: clamp(24px, 5vw, 76px);
  bottom: 26px;
  font-size: 13px;
  letter-spacing: 0.2px;
  color: var(--ink-subtle);
  text-decoration: none;
  transition: color 0.2s var(--ease);
}
.legal:hover { color: var(--ink); }

/* ---------------------------------------------------------------- cue */
.cue {
  position: absolute;
  z-index: 3;
  bottom: 30px;
  left: 50%;
  margin: 0;
  transform: translateX(-50%);
  font-size: 11px;
  font-weight: 560;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: var(--ink-subtle);
}
.cue span { display: block; animation: nudge 2.4s var(--ease) infinite; }
.cue::after {
  content: "";
  display: block;
  width: 1px;
  height: 26px;
  margin: 9px auto 0;
  background: linear-gradient(var(--ink-subtle), transparent);
}

@keyframes nudge {
  0%, 100% { transform: translateY(0); opacity: 0.75; }
  50%      { transform: translateY(3px); opacity: 1; }
}

/* ---------------------------------------------------------------- narrow
   Below 900px there is no room for two columns, so the device goes under the
   copy and the filmstrip still scrubs — the mechanic is the point, not the
   side-by-side. */
@media (max-width: 900px) {
  .hero {
    grid-template-columns: minmax(0, 1fr);
    gap: 34px;
    padding-top: 92px;
  }
  .pin { align-items: flex-start; height: auto; min-height: 0; position: static; }
  .stage { height: auto; }
  .say { max-width: none; }
  .phone { transform: none; --w: min(300px, 72vw); }
  .cue { display: none; }
  .steps { min-height: 0; }
  /* With no pinning there is nothing to scrub, so the captions all stand. */
  .step { position: static; opacity: 1 !important; transform: none !important; }
  .steps { gap: 14px; }
  .step { grid-area: auto; }
}

/* ---------------------------------------------------------------- a11y */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .cue span { animation: none; }
}

/* Capture mode: ?p= sets --adv directly. Nothing may be mid-transition when a
   frame is taken, and the scroll cue would put a looping animation into an
   otherwise deterministic render. */
body.capture .cue { display: none; }
body.capture * { animation: none !important; transition: none !important; }
