/*
 * Yaseen waitlist landing page.
 *
 * Everything here reads from `tokens.css` — no literal colours, radii or type sizes
 * outside of the token file, so the site re-skins with the app. Layout is the one thing
 * tokens don't own, and it lives here.
 *
 * The page is fluid rather than stepped: type, gutters and section rhythm are `clamp()`
 * expressions and the grids are `auto-fit`/`minmax`, so a 1024px tablet and a 1280px
 * laptop are not two designs but two points on the same one. Breakpoints are kept for the
 * three places where the layout genuinely has to change shape, not to patch sizes.
 */

/* ---------------------------------------------------------------- reset */

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

* {
  margin: 0;
}

html {
  scroll-behavior: smooth;
  /* The sticky nav would otherwise cover the top of whatever an anchor lands on. */
  scroll-padding-top: 96px;
  -webkit-text-size-adjust: 100%;
}

body {
  background: var(--surface-page);
  color: var(--text-primary);
  font: var(--type-body);
  -webkit-font-smoothing: antialiased;
}

/*
 * A belt to the braces below: `clip` refuses the sideways scroll without creating a scroll
 * container, which `hidden` would — and a scroll container here would break the sticky nav.
 * The overflow it guards against is a bug either way; this only stops the page being
 * draggable while one goes unnoticed.
 */
html,
body {
  overflow-x: clip;
}

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

a {
  color: var(--text-link);
}

a:hover {
  color: var(--text-link-hover);
}

button,
input,
textarea,
select {
  font: inherit;
  color: inherit;
}

:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}

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

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --------------------------------------------------------------- layout */

:root {
  --gutter: clamp(20px, 4vw, 32px);
  --section-y: clamp(56px, 8vw, 88px);
  --split-gap: clamp(36px, 5.5vw, 64px);
}

.wrap {
  max-width: var(--content-max);
  margin: 0 auto;
  padding-inline: var(--gutter);
}

.section {
  padding-block: var(--section-y);
}

.section--band {
  background: var(--surface-raised);
  border-block: var(--border-width) solid var(--border-hairline);
}

.split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--split-gap);
  align-items: center;
}

.split--hero {
  grid-template-columns: 1.05fr 0.9fr;
}

.split--phone {
  grid-template-columns: 1fr minmax(260px, 360px);
}

/* ------------------------------------------------------------ typography */

.overline {
  display: block;
  font: var(--type-overline);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-muted);
}

.overline--inverse {
  color: rgba(253, 250, 246, 0.7);
}

.h1 {
  font: 500 clamp(34px, 6vw, 56px) / 1.06 var(--font-display);
  letter-spacing: var(--tracking-tight);
  max-width: 18ch;
  text-wrap: balance;
}

.h2 {
  font: 500 clamp(26px, 3.4vw, 32px) / 1.18 var(--font-display);
  letter-spacing: var(--tracking-tight);
  max-width: 24ch;
  text-wrap: balance;
}

.h2--display {
  font-size: clamp(30px, 4.4vw, 42px);
  line-height: 1.1;
  max-width: 18ch;
}

.h3 {
  font: var(--type-heading);
}

.lede {
  font: var(--type-body);
  font-size: clamp(16px, 1.1vw, 18px);
  color: var(--text-secondary);
  max-width: 50ch;
  text-wrap: pretty;
}

.lede--tight {
  margin-top: var(--space-4);
  max-width: 48ch;
}

.muted {
  color: var(--text-secondary);
}

.hint {
  font: var(--type-caption);
  color: var(--text-muted);
}

.error {
  font: var(--type-caption);
  color: var(--text-danger);
  margin-top: var(--space-1);
}

/* ----------------------------------------------------------- primitives */

.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-15);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  font: var(--type-label);
  background: var(--surface-brand-soft);
  color: var(--text-brand);
}

.badge--neutral {
  background: var(--surface-sunken);
  color: var(--text-secondary);
}

.card {
  background: var(--surface-card);
  border: var(--border-width) solid var(--border-hairline);
  border-radius: var(--radius-card);
  padding: var(--card-pad-lg);
}

.card--raised {
  box-shadow: var(--shadow-2);
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--tap-min);
  padding: 0 var(--space-5);
  border: var(--border-width) solid transparent;
  border-radius: var(--radius-pill);
  background: var(--brand-primary);
  color: var(--on-brand);
  font: 500 var(--text-base) / 1 var(--font-ui);
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  transition: var(--transition-control);
}

.btn:hover {
  background: var(--brand-primary-hover);
  color: var(--on-brand);
}

/* The system's press: one step darker and a small physical give. */
.btn:active {
  background: var(--brand-primary-press);
  transform: scale(0.98);
}

.btn[disabled] {
  cursor: progress;
}

.btn--sm {
  min-height: 36px;
  font-size: var(--text-sm);
  padding: 0 var(--space-4);
}

.btn--lg {
  min-height: 52px;
  font-size: var(--text-md);
  padding: 0 var(--space-6);
}

.btn--accent {
  background: var(--brand-accent);
  color: var(--on-accent);
}

.btn--accent:hover {
  background: var(--brand-accent-hover);
  color: var(--on-accent);
}

/* The secondary of a pair: present, clearly not the primary. */
.btn--ghost {
  background: transparent;
  border-color: var(--border-subtle);
  color: var(--text-primary);
}

.btn--ghost:hover,
.btn--ghost:active {
  background: var(--surface-sunken);
  border-color: var(--border-strong);
  color: var(--text-primary);
}

.btn--block {
  width: 100%;
}

.btn .arrow {
  transition: transform var(--dur-fast) var(--ease-out);
  flex: 0 0 auto;
}

.btn:hover .arrow {
  transform: translateX(2px);
}

.btn:hover .arrow--down {
  transform: translateY(2px);
}

/*
 * Busy state. No spinner: the design system allows exactly one looping animation, "a
 * single 1.2s opacity breath on a syncing badge", and this is that one rather than a
 * second exception invented for a submit button.
 */
.btn[data-busy] .arrow {
  display: none;
}

.btn[data-busy] > span {
  animation: breath 1.2s var(--ease-in-out) infinite;
}

@keyframes breath {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.55;
  }
}

/* Every author `display` outranks the user agent's rule for this, so a component that
   sets one — .icon-btn does — stays on screen when it is marked hidden. */
[hidden] {
  display: none !important;
}

.icon-btn {
  width: var(--tap-min);
  height: var(--tap-min);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-pill);
  background: var(--surface-card);
  color: var(--text-primary);
  cursor: pointer;
  transition: var(--transition-control);
}

.icon-btn:hover:not([disabled]) {
  background: var(--surface-sunken);
  border-color: var(--border-strong);
}

/* The system's press for an icon button is a deeper give than a text button's. */
.icon-btn:active:not([disabled]) {
  transform: scale(0.94);
}

.icon-btn[disabled] {
  color: var(--clay-400);
  border-color: var(--border-hairline);
  background: transparent;
  cursor: not-allowed;
}

.icon-btn--ghost {
  background: transparent;
  border-color: transparent;
  color: var(--text-secondary);
  width: auto;
  min-width: var(--tap-min);
  padding-inline: var(--space-3);
}

.icon-btn--ghost:hover:not([disabled]) {
  background: var(--surface-sunken);
  border-color: transparent;
  color: var(--text-primary);
}

.icon-btn-label {
  font: 500 var(--text-sm) / 1 var(--font-mono);
  letter-spacing: var(--tracking-wide);
}

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.field > label {
  font: var(--type-label);
  color: var(--text-secondary);
}

.fieldset {
  border: none;
  padding: 0;
  min-width: 0;
}

.fieldset > legend {
  font: var(--type-label);
  color: var(--text-secondary);
  margin-bottom: var(--space-2);
  padding: 0;
}

.input {
  width: 100%;
  min-height: var(--tap-min);
  padding: var(--space-3) var(--space-4);
  background: var(--surface-sunken);
  border: var(--border-width) solid transparent;
  border-radius: var(--radius-control);
  transition: var(--transition-control);
}

.input::placeholder {
  color: var(--text-muted);
}

.input:focus {
  outline: none;
  background: var(--surface-card);
  border-color: var(--border-brand);
  box-shadow: var(--shadow-focus);
}

.input[aria-invalid='true'] {
  border-color: var(--danger-500);
}

textarea.input {
  resize: vertical;
  min-height: 76px;
}

/* Stat pairs: a mono figure over a caption. */
.stats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6) var(--space-8);
}

.stats--wl {
  margin-top: var(--space-8);
}

.stat {
  display: flex;
  flex-direction: column;
}

.stat b {
  font: 500 var(--text-lg) / 1.2 var(--font-mono);
  color: var(--text-brand);
}

.stat span {
  font: var(--type-caption);
  color: var(--text-muted);
}

.check-list {
  list-style: none;
  padding: 0;
  margin-top: var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.check-list li {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
}

.check-list svg {
  color: var(--text-brand);
  flex: 0 0 auto;
  margin-top: 2px;
}

.check-list p {
  color: var(--text-secondary);
}

.check-list b {
  font-weight: var(--weight-medium);
  color: var(--text-primary);
}

/* ----------------------------------------------------------- device frame */

/*
 * The phone body is part of the image, composited onto the capture at full resolution
 * rather than drawn in CSS around a bare screen — so the enclosure survives being saved,
 * shared or dropped into a deck, and the page does not have to keep a fake bezel in step
 * with a real screenshot.
 *
 * The files are RGBA with a transparent surround, which is why the shadow is a
 * `drop-shadow` filter: a box-shadow would trace the image's rectangle instead of the
 * phone's rounded silhouette.
 */
.device {
  width: min(100%, var(--dw, clamp(248px, 24vw, 320px)));
  margin-inline: auto;
}

/*
 * The shadow is a box-shadow on a shape that matches the phone, not a drop-shadow on the
 * image.
 *
 * drop-shadow traces the image's alpha, which sounds exactly right and was wrong here: the
 * composited image leaves only 7px of transparent margin around the body in a 660px file —
 * under 3px once rendered. A 22px blur had nowhere to fall, so most of it stayed behind
 * the opaque phone and the little that escaped was cut off square at the image's edge. A
 * square shadow under a phone-shaped picture.
 *
 * So the silhouette is rebuilt underneath instead. The body sits 7/660 of the width in
 * from each side with a corner radius of 106/660, and container query units turn those
 * ratios back into pixels at whatever width the device happens to render — the same
 * rounded rectangle at any size, casting a shadow with room to fall.
 *
 * It is also cheaper by a blur pass per phone: a box-shadow is not a filter.
 */
.device {
  position: relative;
  container-type: inline-size;
}

.device::before {
  content: '';
  position: absolute;
  inset: 0.5% 1.06%;
  /* Fallback first: without container query units this would compute to 0 and hand the
     square shadow straight back. 34px is right at the sizes the device actually renders. */
  border-radius: 34px;
  border-radius: 16.1cqw;
  box-shadow: 0 14px 24px rgba(28, 39, 35, 0.22);
}

.device img {
  display: block;
  position: relative;
  width: 100%;
  height: auto;
}

/*
 * Night mode adds a hairline rim. The enclosure is ink-900 and the band behind it is
 * #222B28 — ten values apart — so without an edge the phone dissolves into its own
 * background. A 1px spread on the same shape traces the body exactly.
 */
[data-theme='dark'] .device::before {
  box-shadow: 0 14px 28px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(231, 227, 218, 0.13);
}

/* In the carousel the slide sets the width, so the device takes all of it. */
.device--sm {
  --dw: 100%;
}

.device-note {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  margin-top: var(--space-5);
  font: var(--type-caption);
  color: var(--text-muted);
  text-align: center;
}

.device-note svg {
  flex: 0 0 auto;
}

/* --------------------------------------------------------------- the nav */

/*
 * Glass, in both themes.
 *
 * The bar is the page's own surface at partial opacity over a blur, so what scrolls under
 * it shows through rather than disappearing behind a slab. Because the colour comes from
 * `--surface-page`, night mode gets dark glass for free: the same rule resolves to sand at
 * 74% in day and #121816 at 74% at night, which is the whole point of putting chrome on
 * tokens instead of hex.
 *
 * A solid background is declared first as the fallback for anything without
 * `backdrop-filter` — translucency with no blur behind it is unreadable, not merely plain.
 */
.nav {
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--surface-page);
  border-bottom: var(--border-width) solid transparent;
  transition: background-color var(--dur-base) var(--ease-out),
    border-color var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out);
}

@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .nav {
    background: color-mix(in srgb, var(--surface-page) 74%, transparent);
    /*
     * Halved from 18px, and the saturate pass dropped. A backdrop filter re-runs on every
     * scroll frame, and on a phone two chained passes over a full-width bar is the single
     * most expensive thing on this page. At 10px the material still reads as glass.
     */
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
  }

  .nav[data-scrolled='true'] {
    background: color-mix(in srgb, var(--surface-page) 88%, transparent);
  }
}

.nav[data-scrolled='true'] {
  border-bottom-color: var(--border-hairline);
  box-shadow: var(--shadow-1);
}

/*
 * Night mode adds the one thing the day bar does not need: a lit top edge. Dark glass on a
 * dark page has no value difference to separate it, and the hairline that reads as a
 * divider in daylight disappears at night.
 */
[data-theme='dark'] .nav {
  box-shadow: inset 0 1px 0 rgba(231, 227, 218, 0.07);
}

[data-theme='dark'] .nav[data-scrolled='true'] {
  box-shadow: inset 0 1px 0 rgba(231, 227, 218, 0.1), var(--shadow-2);
}

.nav .wrap {
  min-height: 72px;
  display: flex;
  align-items: center;
  gap: clamp(var(--space-4), 2.5vw, var(--space-8));
}

.wordmark {
  font: 500 var(--text-xl) / 1 var(--font-display);
  letter-spacing: var(--tracking-tight);
  color: var(--text-brand);
  text-decoration: none;
  flex: 0 0 auto;
}

.nav nav {
  display: flex;
  gap: clamp(var(--space-4), 2vw, var(--space-6));
  flex: 1;
  min-width: 0;
}

.nav nav a {
  font: var(--type-body);
  color: var(--text-secondary);
  text-decoration: none;
  padding: var(--space-1) 0;
  white-space: nowrap;
  border-bottom: 2px solid transparent;
  transition: var(--transition-control);
}

.nav nav a:hover {
  color: var(--text-primary);
}

/* Where you are, not just where you could go. */
.nav nav a[aria-current='true'] {
  color: var(--text-brand);
  border-bottom-color: var(--border-brand);
}

.nav .privacy {
  display: inline-flex;
  align-items: center;
  gap: var(--space-15);
  font: var(--type-caption);
  color: var(--text-muted);
  white-space: nowrap;
}

.nav-controls {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  flex: 0 0 auto;
}

/*
 * The nav's call to action has two labels. French runs about 40% longer than English —
 * "Rejoindre la liste d'attente" is 222px against "Join the waitlist" at 159px — and with
 * `white-space: nowrap` the long one pushed the whole row past a phone's 393px, making the
 * page scroll sideways. The short label is swapped in below the breakpoint where that
 * starts to bite.
 */
.cta-short {
  display: none;
}

/* -------------------------------------------------------------- the hero */

.hero {
  padding-block: clamp(48px, 7vw, 80px) clamp(48px, 6vw, 72px);
}

.split--hero .h1 {
  margin-top: var(--space-5);
}

.split--hero .lede {
  margin-top: var(--space-4);
}

.cta-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-6);
}

.cta-hint {
  margin-top: var(--space-3);
}

.hero-proof {
  margin-top: var(--space-8);
  padding-top: var(--space-6);
  border-top: var(--border-width) solid var(--border-hairline);
}

/* --------------------------------------------------------------- reveal */

/*
 * Sections arrive as a cascade, not a slab.
 *
 * The container is marked; its children are what move, each one a beat behind the last.
 * A whole block fading in at once is the cheapest possible entrance and reads as exactly
 * that — the eye gets one event and nothing to follow. Staggering the overline, then the
 * heading, then the copy, then the cards gives the reader an order to read them in, which
 * is the actual job.
 *
 * Opacity and a small translate on a decelerating curve, nothing springy — the design
 * system allows that and forbids the rest. The hiding rule is scoped to
 * `:root[data-reveal="on"]`, which the script sets only when motion is welcome, so a
 * reduced-motion reader and one with no script never have content hidden from them.
 */
:root[data-reveal='on'] [data-reveal] > *:not([data-reveal]) {
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 700ms var(--ease-entrance), transform 700ms var(--ease-entrance);
  transition-delay: calc(var(--i, 0) * 70ms);
}

:root[data-reveal='on'] [data-reveal='in'] > *:not([data-reveal]) {
  opacity: 1;
  transform: none;
}

/*
 * A nested container is excluded from its parent's cascade and runs its own, so a grid of
 * cards inside a revealed column staggers within itself rather than arriving as one child
 * of the column.
 */
:root[data-reveal='on'] [data-reveal] > [data-reveal] {
  opacity: 1;
}

/* A phone gets a touch of scale as well. It is the largest thing in its column, and pure
   translate on something that size reads as a slide rather than an arrival. */
:root[data-reveal='on'] [data-reveal] > .device {
  transform: translateY(14px) scale(0.985);
}

:root[data-reveal='on'] [data-reveal='in'] > .device {
  transform: none;
}

/* --------------------------------------------------------- the 3am loop */

.loop-points {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
  gap: var(--space-5);
  margin-top: var(--space-8);
}

.loop-point {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
}

.loop-point .glyph {
  width: 34px;
  height: 34px;
  border-radius: var(--radius-sm);
  background: var(--surface-brand-soft);
  color: var(--text-brand);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
}

.loop-point b {
  font: 500 var(--text-base) / 1.3 var(--font-ui);
}

.loop-point p {
  font: var(--type-caption);
  color: var(--text-secondary);
  margin-top: 3px;
}

/* ------------------------------------------------------- what you track */

.track-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  gap: var(--space-4);
  margin-top: var(--space-8);
}

/*
 * The accent rule carries the category hue, so a card and its log row agree at a glance.
 * It sits on the top edge at 3px: a coloured left border is explicitly not a Yaseen
 * pattern, and the system calls that out by name.
 */
.track-card {
  position: relative;
  overflow: hidden;
}

.track-card::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 3px;
  background: var(--accent, var(--brand-primary));
}

.track-card .glyph {
  width: 40px;
  height: 40px;
  border-radius: var(--radius-sm);
  background: var(--accent-soft, var(--surface-brand-soft));
  color: var(--accent, var(--brand-primary));
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.track-card h3 {
  margin-top: var(--space-4);
}

.track-card p {
  font: var(--type-body);
  color: var(--text-secondary);
  margin-top: var(--space-2);
}

/* ------------------------------------------------------------ carousel */

/*
 * The app gallery. A horizontal scroller rather than a grid, because the screens are
 * portrait phones: a grid of five wastes the whole right half of the page on whitespace.
 *
 * Scrolling is the browser's — `scroll-snap-type` on the track, `scroll-snap-align` on the
 * slides. Touch, trackpad, shift-wheel, keyboard and the arrow buttons all drive the same
 * native scroll, so there is no transform to keep in sync and nothing to break if the
 * script fails to load. The script only marks the centred slide and lights the arrows and
 * the rail.
 */

.carousel-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-6);
}

.carousel-controls {
  display: flex;
  gap: var(--space-2);
  flex: 0 0 auto;
}

.carousel {
  /*
   * Exactly three phones, whatever the screen.
   *
   * The slides used to be a fixed width in a full-bleed scrollport, so a wide monitor put
   * ten of them on screen at once — the whole row visible, nothing to scroll to, and the
   * centre one no longer reading as the one being shown. Constraining the scrollport to
   * three slides keeps the composition the same from a laptop to a 4K display: one screen
   * being read, one arriving, one leaving.
   *
   * Below roughly 800px the trio is wider than the viewport and this stops applying, which
   * is the intent — three phones side by side on a phone would be three thumbnails. There
   * the centre slide holds its width and the neighbours become the peek that says the row
   * continues.
   */
  --slide-w: clamp(224px, 30vw, 340px);
  max-width: calc(3 * var(--slide-w) + 2 * var(--space-6));
  margin-top: var(--space-8);
  margin-inline: auto;
}

/*
 * Centre stage. The track's inline padding is half the scrollport less half a slide, which
 * is what lets the first and last slides reach the middle — without it the ends could
 * never become the focus. Snapping is centre-aligned to match.
 */
.carousel-track {
  /*
   * Positioned so that it, and not some ancestor, is what the slides measure against.
   * Every offset in the controller is in scroll coordinates; `offsetLeft` reports from the
   * nearest positioned ancestor, so without this the whole row is read as displaced by
   * however far the scrollport happens to sit from the left of the page — which centring
   * the scrollport made large enough to snap the carousel onto the wrong screen.
   */
  position: relative;
  --edge: max(var(--gutter), calc(50% - var(--slide-w) / 2));
  display: flex;
  gap: var(--space-6);
  margin: 0;
  padding: var(--space-4) var(--edge) var(--space-5);
  list-style: none;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  /*
   * Deliberately not `smooth`. Mandatory snapping and the smooth scroll animator both
   * want to own scrollLeft, and when a relayout makes the browser re-snap mid-animation
   * one of them loses — which is felt as a stutter. The two arrow buttons ask for
   * `behavior: 'smooth'` themselves, so nothing visible is given up here.
   */
  scroll-behavior: auto;
  /* The rail below is the scroll affordance; a second one under the phones is noise. */
  scrollbar-width: none;
}

.carousel-track::-webkit-scrollbar {
  display: none;
}

.carousel-track:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 4px;
  border-radius: var(--radius-card);
}

.carousel-slide {
  flex: 0 0 auto;
  width: var(--slide-w);
  scroll-snap-align: center;
}

.carousel-slide figure {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.carousel-slide figcaption {
  font: var(--type-caption);
  color: var(--text-secondary);
}

.carousel-slide figcaption b {
  display: block;
  font: 500 var(--text-base) / 1.3 var(--font-ui);
  color: var(--text-primary);
  margin-bottom: 2px;
}

/*
 * No transition on the frame. The script writes its scale from the scroll position on
 * every frame, so the size is a function of where the row is rather than an animation
 * racing it — a transition here would put the phones on a second clock, which is what the
 * roughness actually was.
 *
 * `will-change` is safe again. It was pulled once because pairing it with a filter can
 * drop content in WebKit; the drop-shadow is gone, so there is no filter left to pair
 * with, and the promotion is what keeps a per-frame transform on the compositor.
 */
[data-enhanced] .carousel-slide .device {
  will-change: transform;
}

/*
 * One screen at a time is the subject; the rest are context, and size alone says so. Every
 * phone carries the same shadow at the same strength — the others used to dim too, which
 * dimmed their shadows with them and left the row lit from five different distances.
 *
 * The size is set inline by the script. Scaling is a transform, so a slide occupies the
 * same box whatever its size and the captions underneath never move.
 */
@media (prefers-reduced-motion: reduce) {
  [data-enhanced] .carousel-slide .device {
    transform: none !important;
  }

  .carousel-track {
    scroll-behavior: auto;
  }
}

/*
 * Progress rail: how far along the row you are, and how much of it fits at once.
 *
 * The track is a tinted hairline rather than a solid bar — at full strength a 3px line the
 * width of the content column is a heavier mark than the thing it describes. The thumb
 * carries the colour instead.
 */
.carousel-rail {
  position: relative;
  height: 4px;
  margin-top: var(--space-6);
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--border-subtle) 55%, transparent);
  overflow: hidden;
}

.carousel-thumb {
  position: absolute;
  inset-block: 0;
  left: 0;
  width: var(--thumb-width, 25%);
  transform: translateX(var(--thumb-offset, 0));
  border-radius: var(--radius-pill);
  background: var(--brand-primary);
  /*
   * No transition. The thumb is set from the scroll position every frame, so a transition
   * would make it lag the row it reports on — which reads as the rail sliding around on
   * its own after you have stopped.
   */
}

/* ----------------------------------------------------------- quote band */

/*
 * `--text-inverse` flips with the theme, and on the brand surface that is right in day
 * mode and wrong at night: night's inverse is the near-black ground, which would land dark
 * text on a dark green band. The band names its own foreground so it holds in both.
 */
.quote-band {
  --on-band: var(--text-inverse);
  background: var(--surface-brand);
  color: var(--on-band);
}

[data-theme='dark'] .quote-band {
  --on-band: var(--text-primary);
}

.quote-band .wrap {
  padding-block: clamp(64px, 9vw, 96px);
  display: grid;
  grid-template-columns: 1.25fr 0.75fr;
  gap: var(--split-gap);
  align-items: center;
}

.quote-band blockquote {
  font: 300 clamp(28px, 4.4vw, 42px) / 1.2 var(--font-editorial);
  letter-spacing: var(--tracking-snug);
  max-width: 26ch;
  text-wrap: balance;
}

.quote-band .overline--inverse {
  margin-bottom: var(--space-4);
}

.quote-band p {
  font: var(--type-body);
  font-size: clamp(16px, 1.1vw, 18px);
  color: rgba(253, 250, 246, 0.86);
  margin-bottom: var(--space-4);
}

.quote-band .quote-body {
  margin-top: var(--space-6);
  max-width: 44ch;
}

/* ----------------------------------------------------------------- FAQ */

.faq {
  margin-top: var(--space-6);
  max-width: 780px;
}

.faq details {
  border-top: var(--border-width) solid var(--border-subtle);
}

.faq summary {
  min-height: 64px;
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding-block: var(--space-5);
  cursor: pointer;
  font: 500 var(--text-lg) / 1.35 var(--font-ui);
  list-style: none;
}

.faq summary::-webkit-details-marker {
  display: none;
}

.faq summary .q {
  flex: 1;
}

.faq summary .sign {
  color: var(--text-muted);
  flex: 0 0 auto;
  transition: transform var(--dur-base) var(--ease-out);
}

.faq details[open] summary .sign {
  transform: rotate(45deg);
}

.faq details p {
  color: var(--text-secondary);
  max-width: 68ch;
  padding-bottom: var(--space-5);
}

/* -------------------------------------------------------- waitlist panel */

.panel {
  max-width: 560px;
  width: 100%;
}

.panel form {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.roles {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.roles label,
.consent {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
  padding: var(--space-3);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-control);
  cursor: pointer;
  transition: var(--transition-control);
}

.roles label:hover,
.consent:hover {
  border-color: var(--border-brand);
}

.roles input,
.consent input {
  margin-top: 3px;
  accent-color: var(--brand-primary);
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
}

.roles label:has(input:checked) {
  border-color: var(--border-brand);
  background: var(--surface-brand-soft);
}

.roles b,
.consent b {
  display: block;
  font: 500 var(--text-base) / 1.3 var(--font-ui);
}

.roles span span,
.consent span span {
  font: var(--type-caption);
  color: var(--text-muted);
}

.assurances {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.success-card .icon-circle {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--surface-brand-soft);
  color: var(--text-brand);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.success-card h3 {
  font: 500 var(--text-2xl) / 1.2 var(--font-display);
  letter-spacing: var(--tracking-snug);
  margin-top: var(--space-4);
}

.success-card p {
  margin-top: var(--space-2);
}

.success-card .stats {
  margin-top: var(--space-5);
  padding-top: var(--space-5);
  border-top: var(--border-width) solid var(--border-hairline);
  gap: var(--space-4) var(--space-6);
}

/* -------------------------------------------------------------- footer */

.footer {
  background: var(--surface-page);
  border-top: var(--border-width) solid var(--border-hairline);
}

.footer .cols {
  padding-block: var(--space-16) var(--space-10);
  display: grid;
  grid-template-columns: 1.5fr repeat(3, 1fr);
  gap: var(--space-10);
}

.footer .cols p {
  font: var(--type-caption);
  color: var(--text-muted);
  margin-top: var(--space-3);
  max-width: 34ch;
}

.footer .residency {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-4);
  flex-wrap: wrap;
}

.footer .col-links {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

.footer .col-links a {
  font: var(--type-body);
  text-decoration: none;
}

.footer .col-links a:hover {
  text-decoration: underline;
}

.footer .baseline {
  padding-bottom: var(--space-10);
  display: flex;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  font: var(--type-caption);
  color: var(--text-muted);
}

/* --------------------------------------------------------- responsive */

/*
 * Three shape changes, not a ladder of size patches — sizes are fluid above.
 *
 *   1080  the nav runs out of room for its section links
 *    900  two columns beside a phone stop being two columns
 *    600  rows of stats and footer columns become stacks
 */

@media (max-width: 1080px) {
  .nav nav,
  .nav .privacy {
    display: none;
  }

  .nav .wrap {
    justify-content: space-between;
  }

  .nav-controls {
    margin-left: auto;
  }
}

/*
 * No backdrop filter on a phone.
 *
 * It re-runs on every scroll frame, and a phone is where the frames are scarcest and where
 * this page is most likely to be read. The bar goes solid instead: it loses the glass, and
 * gains the smooth scroll that the glass was costing. The desktop keeps both.
 */
@media (max-width: 900px) {
  .nav,
  .nav[data-scrolled='true'] {
    background: var(--surface-page);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }

  /* Shallower on a phone: at that size a deep shadow is only haze under the device. */
  .device::before {
    box-shadow: 0 8px 16px rgba(28, 39, 35, 0.18);
  }

  [data-theme='dark'] .device::before {
    box-shadow: 0 8px 18px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(231, 227, 218, 0.13);
  }

  .split,
  .split--hero,
  .split--phone,
  .quote-band .wrap,
  .footer .cols {
    grid-template-columns: 1fr;
  }

  /* The phone follows the copy it illustrates rather than sitting beside nothing. */
  .split > :last-child,
  .split--hero > :last-child,
  .split--phone > :last-child {
    max-width: 420px;
    margin-inline: auto;
    width: 100%;
  }

  .device {
    --dw: min(300px, 78vw);
  }

  .footer .cols {
    grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
    gap: var(--space-8);
  }
}

@media (max-width: 600px) {
  .cta-full {
    display: none;
  }

  .cta-short {
    display: inline;
  }

  .carousel-head {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-5);
  }

  .cta-row .btn {
    width: 100%;
  }

  .stats {
    gap: var(--space-5);
  }

  .footer .baseline {
    flex-direction: column;
    gap: var(--space-2);
  }
}

/*
 * Coarse pointers get the full 44px target everywhere, including the small nav button a
 * mouse can hit at 36.
 */
@media (pointer: coarse) {
  .btn--sm {
    min-height: var(--tap-min);
  }
}
