/* ===========================================================================
   Crest — section layer
   =========================================================================== */

/* --- The wave canvas ------------------------------------------------------
   Fixed behind everything. Content scrolls over it; the wave itself never
   scrolls, it *responds* to scroll. That inversion is the whole page. */

.wave-stage {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;

  /* Painted over by the canvas on every frame. It exists for the seconds
     before first paint, and for the case where the 2D context is unavailable
     (iOS Safari under memory pressure) — without it the page would fall back
     to flat navy rather than something that still reads as ocean. */
  background: linear-gradient(180deg,
    var(--deep-blue) 0%,
    var(--ocean) 46%,
    color-mix(in oklab, var(--teal) 62%, var(--ocean)) 100%);
}

.wave-stage canvas { width: 100%; height: 100%; }

/* Veil.

   The only thing standing between the wave and the text, now that the per-block
   scrims are gone. Because it is full-bleed and continuous it has no edge that
   can read as a box — the whole reason it does this job alone.

   Measured, not eyeballed. Against the brightest point of The Crest
   (rgb(253,219,66)) the raw canvas gives body copy 2.80:1 and muted copy
   1.88:1, both far below the 4.5:1 AA floor. At 0.76 through the middle band
   those become 5.62:1 and 4.70:1.

   It is deliberately weakest across the bottom fifth, which is where the wave's
   silhouette actually sits and where nothing is ever written — so the wave
   keeps its full colour exactly where you look at it. */
.wave-stage::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg,
      rgba(10, 22, 40, 0.84) 0%,
      rgba(10, 22, 40, 0.78) 26%,
      rgba(10, 22, 40, 0.76) 62%,
      rgba(10, 22, 40, 0.62) 84%,
      rgba(10, 22, 40, 0.44) 100%);
}

.page { position: relative; z-index: 1; }

/* A per-block text scrim used to live here and has been removed.

   However soft its falloff, it terminated at the element box, and that edge was
   visible wherever the wave behind it was flat — worst of all in the tools
   section, where the scrim's boundary landed exactly on the first and last
   cards' borders and read as though they had been sliced. Legibility is now
   handled entirely by the vertical veil above, which is full-bleed and
   therefore has no edge to see. */
.scrim, .scrim--center { position: relative; }

/* --- Phase rail -----------------------------------------------------------
   The app shows Rise / Crest / Fall / Calm with the active one lit. So does
   the page — driven by the same scroll value that drives the wave. */

.rail {
  position: fixed;
  right: clamp(0.75rem, 2.2vw, 2.25rem);
  top: 50%;
  transform: translateY(-50%);
  z-index: 20;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-2);
  /* Symmetric inline padding, so `left: 50%` on the track lands exactly on the
     dot column. The items are 44px square for touch, so the capsule takes its
     width from them and only needs a little breathing room either side. */
  padding: var(--s-3) var(--s-2);
  border-radius: var(--r-full);
  background: rgba(10, 22, 40, 0.42);
  border: 1px solid var(--hairline);
  backdrop-filter: blur(18px) saturate(130%);
  -webkit-backdrop-filter: blur(18px) saturate(130%);
  opacity: 0;
  transition: opacity var(--dur-mid) var(--ease-out-soft);
}
.rail.is-visible { opacity: 1; }

/* The track, and the fill that rides it.

   Four dots that snap between states is a weaker version of what the app
   itself shows — a phase bar that fills. This is continuous: --rail-progress
   is set from the same scroll value that drives the wave, so the line reports
   real position rather than which of four buckets you happen to be in.

   Both layers run down the centre of the capsule, which is exactly one dot
   wide — see the note on .rail__label for why that matters. They span dot
   centre to dot centre rather than the full padding box, so the line starts
   and ends on a dot instead of poking out past the first and last. */
.rail::before,
.rail::after {
  content: "";
  position: absolute;
  left: 50%;
  translate: -50% 0;
  width: 2px;
  border-radius: var(--r-full);
  pointer-events: none;
}
.rail::before {
  top: calc(var(--s-3) + 22px);
  bottom: calc(var(--s-3) + 22px);
  background: rgba(255, 255, 255, 0.14);
}
.rail::after {
  top: calc(var(--s-3) + 22px);
  height: calc(var(--rail-progress, 0) * (100% - (var(--s-3) + 22px) * 2));
  background: linear-gradient(180deg,
    color-mix(in oklab, var(--rail-color, var(--teal)) 55%, transparent),
    var(--rail-color, var(--teal)));
  transition: background var(--dur-slow) var(--ease-out-soft);
}

/* Just the dot in the flow — but the button is a full 44x44 with the dot
   centred in it. A 9px target is a 9px target however pretty it looks, and
   44 on one axis only still fails on the other. The capsule's own padding
   shrinks to absorb the extra width. */
.rail__item {
  position: relative;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

/* Absolutely positioned, and that is the whole point.

   Previously the labels sat in normal flow, so the capsule was always sized to
   fit "Calm Waters" whether any label was visible or not. The result was a
   wide, mostly-empty pill with the dots stranded against its right edge, and
   every dot at a different x depending on how long its own label was. Taking
   the labels out of flow lets the capsule collapse to exactly one dot wide,
   which is what makes a single centred track possible at all.

   They carry their own background because outside the capsule they sit
   directly over the wave. */
.rail__label {
  position: absolute;
  /* Anchored to the dot, not to the 44px hit box around it — otherwise the
     extra touch padding shows up as a gap between label and dot. */
  right: calc(50% + 4.5px + var(--s-3));
  top: 50%;
  translate: 0 -50%;
  white-space: nowrap;
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--ink-2);
  padding: 0.34em 0.7em;
  border-radius: var(--r-full);
  background: rgba(10, 22, 40, 0.72);
  border: 1px solid var(--hairline);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  opacity: 0;
  transition:
    opacity var(--dur-fast) var(--ease-out-soft),
    color var(--dur-fast) var(--ease-out-soft);
  pointer-events: none;
}

.rail__dot {
  /* Must be block: it is a <span>, and .rail__item is no longer a flex
     container, so as an inline box its width, height and border-radius were
     all being ignored — the dots rendered as small squares from the occluder
     shadow alone. */
  display: block;
  width: 9px;
  height: 9px;
  border-radius: var(--r-full);
  background: var(--ink-4);
  position: relative;      /* above the track and fill */
  z-index: 1;
  /* The track runs behind the dots, so each dot needs to occlude it rather
     than have the line appear to pass through. */
  box-shadow: 0 0 0 3px rgba(10, 22, 40, 0.92);
  transition:
    background var(--dur-mid) var(--ease-out-soft),
    box-shadow var(--dur-mid) var(--ease-out-soft),
    transform var(--dur-mid) var(--ease-out-soft);
}
/* A phase already scrolled past reads as complete, not inactive. */
.rail__item[data-state="done"] .rail__dot {
  background: color-mix(in oklab, var(--teal) 70%, white 10%);
}

.rail__item[aria-current="true"] .rail__dot {
  background: var(--rail-color, var(--teal));
  transform: scale(1.45);
  box-shadow:
    0 0 0 2.4px rgba(10, 22, 40, 0.92),
    0 0 0 5px color-mix(in oklab, var(--rail-color, var(--teal)) 26%, transparent);
}
.rail__item[aria-current="true"] .rail__label,
.rail__item:hover .rail__label,
.rail__item:focus-visible .rail__label {
  opacity: 1;
  color: var(--ink-0);
}

@media (max-width: 860px) { .rail { display: none; } }

/* --- Mobile phase bar -----------------------------------------------------
   The rail's job on small screens. Same two custom properties, set on :root by
   syncRail, so the colour tracks the phase exactly as the rail's fill does. */

.topbar {
  display: none;
  position: fixed;
  inset: 0 0 auto 0;
  height: 3px;
  z-index: 20;
  background: rgba(255, 255, 255, 0.08);
  pointer-events: none;
}
.topbar__fill {
  display: block;
  height: 100%;
  width: 100%;
  transform-origin: left center;
  transform: scaleX(var(--rail-progress, 0));
  background: var(--rail-color, var(--teal));
  transition: background var(--dur-slow) var(--ease-out-soft);
}
@media (max-width: 860px) { .topbar { display: block; } }

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

.hero {
  min-height: 100svh;
  display: grid;
  /* minmax(0,1fr), not the implicit `auto`: a grid column's default minimum is
     min-content, so the nowrap App Store button widened the column past the
     viewport at 320px and dragged .wrap out with it. */
  grid-template-columns: minmax(0, 1fr);
  align-content: center;
  padding-block: var(--s-10) var(--s-9);
  position: relative;
}

.hero__mark {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-bottom: var(--s-7);
  font-weight: 600;
  font-size: var(--t-md);
  letter-spacing: -0.01em;
  color: var(--ink-0);
}
/* Cropped to the arc, so the mark optically matches the wordmark's cap height
   rather than carrying the icon's generous internal padding. */
.hero__mark .mark { width: 48px; height: auto; }

.hero__title { margin-bottom: var(--s-5); }

/* Each clause of the headline fades in on its own beat, so the sentence
   arrives the way it reads: rises… peaks… passes. */
.hero__clause {
  display: inline-block;
  opacity: 0;
  transform: translate3d(0, 0.34em, 0);
  animation: clause-in 900ms var(--ease-out-soft) forwards;
}
.hero__clause--2 { animation-delay: 180ms; }
.hero__clause--3 { animation-delay: 380ms; }
.hero__clause--accent { color: var(--foam); }

@keyframes clause-in {
  to { opacity: 1; transform: none; }
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4);
  align-items: center;
  margin-top: var(--s-7);
}

.hero__rating {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  font-size: var(--t-sm);
  color: var(--ink-3);
}
.hero__stars { color: var(--gold); letter-spacing: 0.08em; }

/* The App Store thumbnail, at App Store proportions. iOS squircles are
   ~22.4% of the tile, which is why a plain 12px radius always looks wrong
   next to a real icon. */
.app-tile {
  width: 46px;
  height: 46px;
  flex: none;
  border-radius: 22.4%;
  background: var(--deep-blue);
  border: 1px solid var(--hairline);
  display: grid;
  place-items: center;
  overflow: hidden;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
}
.app-tile svg { width: 100%; height: 100%; }

.hero__ratingText {
  display: flex;
  flex-direction: column;
  line-height: 1.35;
}
.hero__ratingText strong {
  color: var(--ink-1);
  font-weight: 600;
  letter-spacing: -0.01em;
}

.hero__cue {
  position: absolute;
  bottom: var(--s-6);
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-2);
  font-size: var(--t-xs);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--ink-4);
  transition: opacity var(--dur-mid) var(--ease-out-soft);
}
.hero__cue.is-hidden { opacity: 0; }
.hero__cue svg { width: 15px; animation: cue 2.4s var(--ease-in-out) infinite; }

@keyframes cue {
  0%, 100% { transform: translateY(0); opacity: 0.5; }
  50%      { transform: translateY(5px); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .hero__clause { opacity: 1; transform: none; animation: none; }
  .hero__cue svg { animation: none; }
}

/* --- Phase section -------------------------------------------------------- */

.phase__num {
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: var(--track-eyebrow);
  color: var(--ink-4);
  font-variant-numeric: tabular-nums;
}

/* --- Proof ---------------------------------------------------------------- */

.proof { text-align: center; }

.proof__claim {
  font-size: var(--t-xxl);
  font-weight: 700;
  letter-spacing: var(--track-xxl);
  line-height: var(--leading-tight);
  color: var(--ink-0);
  max-width: 20ch;
  margin-inline: auto;
  text-wrap: balance;
}
.proof__claim em { font-style: normal; color: var(--teal); }

.proof__arrow {
  display: inline-flex;
  align-items: baseline;
  gap: var(--s-4);
  margin-top: var(--s-7);
  font-variant-numeric: tabular-nums;
}
.proof__n {
  font-size: clamp(3.5rem, 2rem + 7vw, 7rem);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.04em;
}
.proof__n--start { color: var(--phase-rise); }
.proof__n--end   { color: var(--teal); }
.proof__to { color: var(--ink-4); font-size: var(--t-xl); }

.proof__caption { color: var(--ink-3); margin-top: var(--s-4); font-size: var(--t-sm); }

/* auto-fit gave two columns at phone widths, which orphans the third stat on a
   row of its own — worse than either 1 or 3. Explicitly one column until
   there is room for all three. */
.stats {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-5) var(--s-4);
  margin-top: var(--s-8);
  max-width: 780px;
  margin-inline: auto;
  align-items: start;
}
@media (min-width: 620px) {
  .stats { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.stat {
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}
/* Deliberately a step below .h2. These support the claim above them; at --t-xl
   they read as three competing headlines and the eye has nowhere to land. */
.stat__v {
  font-size: var(--t-lg);
  font-weight: 650;
  color: var(--ink-0);
  letter-spacing: -0.018em;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
  text-wrap: balance;
}
.stat__l {
  font-size: var(--t-xs);
  color: var(--ink-3);
  letter-spacing: 0.02em;
  line-height: 1.4;
  text-wrap: pretty;
}

/* --- Tools grid ----------------------------------------------------------- */

.tools {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(128px, 1fr));
  gap: var(--s-3);
  margin-top: var(--s-6);
}
.tool {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-5) var(--s-3);
  border-radius: var(--r-md);
  background: var(--surface-1);
  border: 1px solid var(--hairline);
  font-size: var(--t-sm);
  font-weight: 550;
  color: var(--ink-1);
  text-align: center;
}
.tool svg { width: 22px; height: 22px; color: var(--teal); }

/* --- Gallery -------------------------------------------------------------- */

.gallery {
  display: flex;
  gap: var(--s-5);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding-block: var(--s-4) var(--s-6);
  padding-inline: var(--gutter);
  margin-inline: calc(var(--gutter) * -1);
  scrollbar-width: none;
}
.gallery::-webkit-scrollbar { display: none; }
.gallery > * { scroll-snap-align: center; flex: none; }

/* --- Live surf ------------------------------------------------------------
   A miniature of ActiveSurfView. Sizes are in cqw so the whole screen scales
   with the device frame rather than needing a breakpoint per width — the frame
   itself is the container. */

.device--live .device__screen {
  container-type: inline-size;
  background: var(--deep-blue);
}

.live__wave {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.live {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 7cqw 5cqw 6cqw;
  text-align: center;
  color: #fff;
}

.live__chrome { align-self: flex-start; }
.live__cat {
  font-size: 3.6cqw;
  font-weight: 600;
  padding: 1.4cqw 3.2cqw;
  border-radius: var(--r-full);
  background: rgba(255, 107, 107, 0.22);
  border: 1px solid rgba(255, 107, 107, 0.4);
  color: #ffd2d2;
}

.live__timer {
  font-size: 17cqw;
  font-weight: 300;
  line-height: 1;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  margin-top: 11cqw;
}
.live__until {
  font-size: 3.5cqw;
  color: rgba(255, 255, 255, 0.62);
  margin-top: 2cqw;
  min-height: 4.4cqw;
}
/* Reserved height: ActiveSurfView returns nil for most of the rise, and the
   layout must not jump when the line appears. */
.live__motivation {
  font-size: 4cqw;
  color: rgba(255, 255, 255, 0.72);
  margin-top: 3.5cqw;
  min-height: 10cqw;
  max-width: 88%;
  line-height: 1.35;
}

.live__phase {
  display: flex;
  align-items: center;
  gap: 2cqw;
  font-size: 6cqw;
  font-weight: 650;
  letter-spacing: -0.02em;
  margin-top: 4cqw;
}
.live__icon { width: 6cqw; height: 6cqw; flex: none; }

.live__message {
  font-size: 4.6cqw;
  font-weight: 600;
  line-height: 1.32;
  margin-top: 2.5cqw;
  max-width: 92%;
  min-height: 12cqw;
}

.live__pills {
  display: flex;
  justify-content: space-between;
  width: 100%;
  margin-top: auto;
  padding-inline: 1cqw;
  gap: 1cqw;
}
.live__pill {
  font-size: 2.9cqw;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.34);
  position: relative;
  padding-top: 6cqw;
  flex: 1;
  transition: color var(--dur-mid) var(--ease-out-soft);
}
.live__pill::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  translate: -50% 0;
  width: 4.4cqw;
  height: 4.4cqw;
  border-radius: var(--r-full);
  background: rgba(255, 255, 255, 0.16);
  transition:
    background var(--dur-mid) var(--ease-out-soft),
    box-shadow var(--dur-mid) var(--ease-out-soft);
}
.live__pill[data-state="done"] { color: rgba(255, 255, 255, 0.52); }
.live__pill[data-state="done"]::before { background: rgba(107, 203, 119, 0.85); }
.live__pill[data-state="active"] { color: #fff; }
.live__pill[data-state="active"]::before {
  background: var(--pill-color, #fff);
  box-shadow: 0 0 0 1.4cqw color-mix(in oklab, var(--pill-color, #fff) 26%, transparent);
}

.live__track {
  width: 100%;
  height: 1.4cqw;
  border-radius: var(--r-full);
  background: rgba(255, 255, 255, 0.16);
  margin-top: 4cqw;
  overflow: hidden;
}
.live__bar {
  height: 100%;
  width: 100%;
  border-radius: inherit;
  transform-origin: left center;
  transform: scaleX(0);
  background: #fff;
  transition: background var(--dur-mid) var(--ease-out-soft);
}

/* If the 2D context was unavailable the miniature would otherwise sit on flat
   navy with nothing behind it. */
.live--nowave .device__screen {
  background: linear-gradient(180deg, var(--ocean), var(--teal));
}

/* --- Privacy -------------------------------------------------------------- */

.privacy__list { display: grid; gap: var(--s-4); margin-top: var(--s-6); }
.privacy__item {
  display: flex;
  gap: var(--s-4);
  align-items: flex-start;
  font-size: var(--t-base);
  color: var(--ink-2);
}
.privacy__item svg {
  width: 19px; height: 19px;
  color: var(--teal);
  flex: none;
  margin-top: 0.28em;
}

/* --- Closing CTA ---------------------------------------------------------- */

.closing { text-align: center; padding-block: var(--section-y) var(--s-9); }
.closing .btn { margin-top: var(--s-7); }
.closing__note { margin-top: var(--s-5); font-size: var(--t-sm); color: var(--ink-4); }

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

.footer {
  border-top: 1px solid var(--hairline);
  padding-block: var(--s-6) var(--s-7);
  font-size: var(--t-sm);
  color: var(--ink-4);
}
.footer__inner {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-5);
  justify-content: space-between;
  align-items: center;
}

/* Icon only. It carries an aria-label rather than visible text, so the 44px
   box comes from the button itself instead of from the words beside it. */
.footer__author {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  margin-inline-start: calc(var(--s-3) * -1);
  text-decoration: none;
  color: var(--ink-3);
  border-radius: var(--r-md);
  transition: background var(--dur-fast) var(--ease-out-soft),
              color var(--dur-fast) var(--ease-out-soft);
}
.footer__author:hover {
  background: var(--surface-1);
  color: var(--ink-0);
}
.footer__author svg { width: 21px; height: 21px; }

.footer__links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin-inline-end: calc(var(--s-3) * -1);
}
.footer a { color: var(--ink-3); text-decoration: none; }
.footer a:hover { color: var(--ink-1); }

/* Footer links were 24px tall — well under the 44px touch minimum. Padding
   rather than a fixed height, so they still wrap sanely on a narrow screen. */
.footer__links a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding-inline: var(--s-3);
  border-radius: var(--r-md);
  transition: background var(--dur-fast) var(--ease-out-soft),
              color var(--dur-fast) var(--ease-out-soft);
}
.footer__links a:hover { background: var(--surface-1); }

.footer__legal {
  margin-top: var(--s-5);
  font-size: var(--t-xs);
  color: var(--ink-4);
  max-width: 60ch;
  line-height: 1.5;
}
