/* ═══════════════════════════════════════════════════════════════
   GridFM — product page
   Tokens follow the Bounce design system: paper + ink, purple acts,
   yellow highlights, pink underlines, teal = live. Hard edges only:
   no border-radius, no soft shadows. Elevation is the §4 numbered scale
   (3/6/10, ink); purple shadow promotes exactly one surface per view.
   ═══════════════════════════════════════════════════════════════ */

:root {
  /* surface */
  --ink:        oklch(14% 0.005 286);
  --paper:      oklch(98.9% 0.006 107);
  --canvas:     oklch(90% 0.01 107);
  --white:      #fff;

  /* accents */
  --purple:     #8C2BEE;   /* spec §3 --color-primary */
  --purple-ink: #6d28d9;
  --yellow:     oklch(96.5% 0.21 111);
  --pink:       oklch(72.5% 0.175 350);
  --teal:       oklch(74.2% 0.121 185);   /* §3: live / on-air ONLY */

  /* error, per §7 — red-500 border, red-600 message, 5% tint fill */
  --red:        #ef4444;
  --red-ink:    #dc2626;
  --red-tint:   rgba(239, 68, 68, 0.05);

  /* ink at alpha */
  --ink-95:     oklch(14% 0.005 286 / 0.95);
  --ink-75:     oklch(14% 0.005 286 / 0.75);
  --ink-70:     oklch(14% 0.005 286 / 0.72);
  --ink-60:     oklch(14% 0.005 286 / 0.6);
  --ink-50:     oklch(14% 0.005 286 / 0.5);
  --ink-38:     oklch(14% 0.005 286 / 0.38);
  --ink-08:     oklch(14% 0.005 286 / 0.08);
  --paper-80:   oklch(98.9% 0.006 107 / 0.8);
  --paper-50:   oklch(98.9% 0.006 107 / 0.5);
  --paper-40:   oklch(98.9% 0.006 107 / 0.4);
  --paper-30:   oklch(98.9% 0.006 107 / 0.3);
  --paper-20:   oklch(98.9% 0.006 107 / 0.2);
  --yellow-55:  oklch(96.5% 0.21 111 / 0.55);   /* tape: translucent, so the
                                                   surface reads through it */

  /* type */
  --display: Syne, 'Trebuchet MS', sans-serif;
  --body:    'Space Grotesk', 'Segoe UI', system-ui, sans-serif;
  --mono:    'Space Mono', ui-monospace, 'Cascadia Mono', monospace;

  /* structure */
  --bd:     3px;
  /* §4 elevation, always in ink. Colour is a promotion tool only: at most one
     shadow-3-purple surface per view. */
  --sh-1:   3px 3px 0;    /* chips, tags, small buttons */
  --sh-2:   6px 6px 0;    /* cards, panels, large buttons */
  --sh-3:   10px 10px 0;  /* modals, hero panels */
  --pad-x:  clamp(20px, 4.4vw, 56px);
  --wrap:   1320px;

  /* motion */
  --out:    cubic-bezier(0.22, 1, 0.36, 1);
}

/* ─────────────────────────── base ─────────────────────────── */

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

html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--body);
  font-size: 16px;
  font-weight: 500;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

body.is-loading { overflow: hidden; }

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

h1, h2, h3, h4 { margin: 0; }
p { margin: 0; }
ul, dl, dd, dt { margin: 0; padding: 0; }
li { list-style: none; }

a { color: var(--purple); text-decoration: none; }
a:hover { color: var(--purple-ink); }

button { font: inherit; color: inherit; }

:focus-visible {
  outline: 3px solid var(--purple);
  outline-offset: 3px;
}

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

/* ─────────────────────── type primitives ─────────────────────── */

.display {
  font-family: var(--display);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: -0.03em;
  line-height: 0.94;
  text-wrap: balance;
}
.display--lg  { font-size: clamp(2rem, 5vw, 3.5rem); max-width: 900px; }
.display--md  { font-size: clamp(1.9rem, 4.2vw, 2.875rem); }
.display--md2 { font-size: clamp(1.6rem, 3.4vw, 2.5rem); letter-spacing: -0.02em; }
.display--sm  { font-size: clamp(1.5rem, 2.4vw, 1.625rem); letter-spacing: -0.02em; }

.eyebrow {
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--ink-60);
}
.eyebrow--purple { color: var(--purple); letter-spacing: 0.14em; font-size: 11px; }

.lede {
  font-size: clamp(15px, 1.5vw, 18px);
  line-height: 1.55;
  color: var(--ink-70);
  max-width: 660px;
}

.struck { white-space: nowrap; color: var(--ink-38); }


.wordmark {
  font-family: var(--display);
  font-weight: 800;
  font-size: 28px;
  letter-spacing: -0.04em;
  color: var(--ink);
  line-height: 1;
}
.wordmark span { color: var(--purple); }
.wordmark:hover { color: var(--ink); }
.wordmark--sm { font-size: 25px; }
.wordmark--pink { color: var(--paper); font-size: 30px; }
.wordmark--pink span { color: var(--pink); }

/* the 3×3 mark, one cell lit */
.gridmark {
  display: grid;
  grid-template-columns: repeat(3, 5px);
  grid-template-rows: repeat(3, 5px);
  gap: 2px;
  background: var(--ink);
  border: 3px solid var(--ink);
  flex: none;
}
.gridmark i { background: var(--white); }
.gridmark i.on { background: var(--purple); }
.gridmark--shadow { box-shadow: var(--sh-1) var(--ink); border-width: 2.5px; grid-template-columns: repeat(3, 7px); grid-template-rows: repeat(3, 7px); }

.chip {
  display: inline-block;
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  padding: 6px 14px;
  border: 2px solid var(--ink);
  background: var(--white);
  box-shadow: var(--sh-1) var(--ink);
}
.chip--yellow { background: var(--yellow); }


.dot {
  width: 8px; height: 8px;
  background: var(--teal);
  border-radius: 50%;
  flex: none;
  animation: pulseDot 1.4s ease-in-out infinite;
}
@keyframes pulseDot { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }

/* ─────────────────────────── buttons ─────────────────────────── */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--body);
  font-weight: 700;
  font-size: 15px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 17px 34px;
  border: var(--bd) solid var(--ink);
  background: var(--white);
  color: var(--ink);
  cursor: pointer;
  box-shadow: var(--sh-2) var(--ink);
  transition: transform 150ms ease-out, box-shadow 150ms ease-out, background-color 150ms ease-out;
}
.btn:hover     { transform: translate(2px, 2px); box-shadow: 4px 4px 0 var(--ink); color: var(--ink); }
.btn:active    { transform: translate(6px, 6px); box-shadow: 0 0 0 var(--ink); }
.btn--purple   { background: var(--purple); color: var(--white); }
.btn--purple:hover { color: var(--white); }
.btn--paper:hover  { background: var(--yellow); }
.btn--md  { padding: 15px 32px; font-size: 14px; box-shadow: 5px 5px 0 var(--ink); }
.btn--md:active { transform: translate(5px, 5px); }
.btn--sm  { padding: 10px 22px; font-size: 13px; letter-spacing: 0.06em; box-shadow: var(--sh-1) var(--ink); }
.btn--sm:hover  { transform: translate(2px, 2px); box-shadow: 1px 1px 0 var(--ink); }
.btn--sm:active { transform: translate(3px, 3px); box-shadow: 0 0 0 var(--ink); }
.btn--block { width: 100%; padding: 17px; }
/* for buttons that sit on ink: the ink shadow would vanish there, so this one
   is thrown in paper and the border switches with it */
.btn--pink {
  background: var(--pink); color: var(--ink);
  border-color: var(--paper);
  box-shadow: 5px 5px 0 var(--paper-30);
}
.btn--pink:hover  { color: var(--ink); box-shadow: 3px 3px 0 var(--paper-30); }
.btn--pink:active { transform: translate(5px, 5px); box-shadow: 0 0 0 var(--paper-30); }

/* ─────────────────────────── loader ─────────────────────────── */

.loader {
  position: fixed;
  inset: 0;
  z-index: 900;
  background: var(--paper);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 22px;
  transition: opacity 500ms ease-out, visibility 500ms;
}

/* the object flies from here to the hero — JS measures this slot */
.loader__slot { width: clamp(160px, 36vw, 220px); aspect-ratio: 1; flex: none; }

/* The whole loader is now the object and this number, so the number gets to be
   a real size instead of a caption. Tabular figures so the width never jitters
   as it counts, and ink on paper because the yellow readout only worked
   against the dark panel that used to sit behind it. */
.loader__pct {
  font-family: var(--mono);
  font-size: clamp(30px, 7vw, 42px);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  transition: opacity 200ms ease-out, transform 200ms ease-in;
}

/* loaded: fade the number, then the sheet */
.is-revealing .loader__pct { opacity: 0; transform: translateY(8px); }
.is-loaded .loader { opacity: 0; visibility: hidden; }

/* Failsafe, declarative half. The head script's 8s timer is the primary
   release and the only one that can give scrolling back; this covers the case
   where the main thread is wedged and that timer never gets to run, by lifting
   the sheet off the page anyway. A visitor who waits this long should at least
   be reading GridFM instead of a blank rectangle. Both halves are no-ops on the
   normal path, which finishes inside app.js's own 5s ceiling plus the flight. */
@keyframes loaderBail { to { opacity: 0; visibility: hidden; pointer-events: none; } }
body.is-loading .loader { animation: loaderBail 0s linear 8.4s forwards; }

/* ─────────────────────────── ticker + nav ─────────────────────────── */

.ticker {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  height: 38px;
  padding: 0 var(--pad-x);
  background: var(--ink);
  color: var(--paper);
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.14em;
}
.ticker__right { display: flex; align-items: center; gap: 20px; opacity: 0.7; }

.nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  height: 76px;
  padding: 0 var(--pad-x);
  background: var(--paper);
  border-bottom: var(--bd) solid var(--ink);
  position: sticky;
  top: 0;
  z-index: 40;
}

/* the bar holds two targets, so both of them get a real one */
.nav .wordmark { display: inline-flex; align-items: center; min-height: 44px; }

/* ─────────────────────────── hero ─────────────────────────── */

.hero {
  display: grid;
  grid-template-columns: 1fr minmax(360px, 540px);
  align-items: center;
  gap: 32px;
  max-width: var(--wrap);
  margin: 0 auto;
  padding: clamp(36px, 5vw, 64px) var(--pad-x) clamp(44px, 5.5vw, 68px);
}

.hero__title {
  font-family: var(--display);
  font-weight: 800;
  font-size: clamp(2.5rem, 5.6vw, 5rem);
  line-height: 0.85;
  text-transform: uppercase;
  letter-spacing: -0.04em;
  margin: 26px 0 0;
}
.line { display: block; overflow: hidden; padding-bottom: 0.04em; }
.line__in { display: block; }

.hero__lede {
  font-size: clamp(16px, 1.6vw, 18px);
  line-height: 1.5;
  color: var(--ink-75);
  max-width: 480px;
  margin: 26px 0 34px;
}

.hero__ctas { display: flex; gap: 14px; align-items: center; flex-wrap: wrap; margin-bottom: 40px; }

.stats { display: flex; gap: clamp(20px, 3vw, 32px); font-family: var(--mono); flex-wrap: wrap; }
.stat { display: flex; flex-direction: column-reverse; }
.stat dd { font-size: clamp(24px, 3vw, 30px); font-weight: 700; line-height: 0.9; }
.stat dt {
  font-size: 10px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.12em; color: var(--ink-50); margin-top: 6px;
}

.hero__visual { display: flex; flex-direction: column; align-items: center; gap: 8px; }

.mark {
  position: relative;
  width: 100%;
  max-width: 520px;
  aspect-ratio: 1;
  -webkit-tap-highlight-color: transparent !important;
}
.mark__still {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: contain;
  transition: opacity 300ms ease-out;
}
.mark__live {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%; height: 100%;
  min-height: 0;
  opacity: 0;
  transition: opacity 300ms ease-out;
}
.mark.is-live .mark__still { opacity: 0; }
.mark.is-live .mark__live  { opacity: 1; }

/* while loading the still flies free of the layout */
.is-loading .mark__still {
  position: fixed;
  z-index: 950;
  transition: none;
}
.is-revealing .mark__still {
  transition: transform 900ms var(--out);
}

.mark__hint {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--ink-50);
  opacity: 0;
  transition: opacity 400ms ease-out;
}
.is-loaded .mark__hint { opacity: 1; transition-delay: 1.4s; }

/* ─────────────────────────── sections ─────────────────────────── */

.section {
  max-width: var(--wrap);
  margin: 0 auto;
  padding: clamp(44px, 5.5vw, 72px) var(--pad-x);
  border-top: var(--bd) solid var(--ink);
}

.why__head { max-width: 1000px; }
.why__head .eyebrow { display: block; margin-bottom: 22px; }
.why__head .lede { margin-top: 24px; }

.compare { display: grid; grid-template-columns: 1fr 1fr; gap: 28px; margin-top: 48px; }
.compare__col { display: flex; flex-direction: column; }
.compare__label { display: flex; align-items: center; gap: 12px; margin-bottom: 18px; }
/* the old way's marker — muted to match the card under it, while the GridFM
   column is marked by the gridmark with its lit purple cell */
.compare__icon {
  width: 30px; height: 30px; flex: none;
  border: var(--bd) solid var(--ink);
  background: var(--canvas);
  display: flex; align-items: center; justify-content: center;
  color: var(--ink-70);
}
.compare__icon svg { width: 16px; height: 16px; }

.card {
  border: var(--bd) solid var(--ink);
  background: var(--white);
  display: flex;
  flex-direction: column;
  flex: 1;
}
/* the one promoted surface on the page (§4 shadow-3-purple) */
.card--new { box-shadow: var(--sh-3) var(--purple); }
.card__body { padding: 32px; flex: 1; }
.card__body .display { margin-bottom: 26px; }

/* The old way is the dead half of the argument, so it holds no colour at all —
   every accent on this page now belongs to the GridFM card. It recedes onto
   --canvas, the muted surface token the system already had, and drops through
   the ink alpha steps. Nothing structural softens: same 3px ink border, same
   single container shadow, because the recession is carried by surface and
   alpha rather than by breaking the system's hard edges.
   Measured on --canvas — title 7.17:1, body 4.81:1, icon strokes 3.50:1. */
.card--old {
  background: var(--canvas);
  box-shadow: var(--sh-2) var(--ink);
}
.card--old .display,
.card--old .point__title { color: var(--ink-70); }
.card--old .point__text  { color: var(--ink-60); }
/* the plates flatten into the surface — an empty frame, nothing lit inside */
.card--old .point__icon {
  background: var(--canvas);
  color: var(--ink-50);
}

.points { display: flex; flex-direction: column; gap: 22px; }
.point { display: flex; gap: 16px; align-items: flex-start; }
/* flat: no offset shadow. Four of these in a column read as a list of facts,
   not a stack of raised chips — the card's own shadow is the only depth here. */
.point__icon {
  width: 44px; height: 44px; flex: none;
  border: var(--bd) solid var(--ink);
  background: var(--white);
  display: flex; align-items: center; justify-content: center;
  color: var(--ink-75);
}
.point__icon svg { width: 21px; height: 21px; }
.point__icon--purple { background: var(--purple); color: var(--white); }
/* Weight carries the split, not a second face: 700 against 400, two steps
   either side of the page's 500 body default. The card__body h3 keeps
   uppercase Syne, so the card still reads claim → point → reasoning. */
.point__title {
  font-family: var(--body); font-weight: 700; font-size: 18px;
  letter-spacing: -0.01em; line-height: 1.3; margin-bottom: 5px;
}
.point__text {
  font-family: var(--body); font-weight: 400; font-size: 14px;
  line-height: 1.45; color: var(--ink-70);
}


/* ─────────────────────────── two ways in ─────────────────────────── */

.ways__head { text-align: center; max-width: 720px; margin: 0 auto 32px; }
.ways__head .chip { margin-bottom: 22px; }
.ways__head .lede { margin: 18px auto 0; }

.tabs {
  position: relative;
  display: flex;
  width: fit-content;
  margin: 0 auto 34px;
  border: var(--bd) solid var(--ink);
  box-shadow: var(--sh-2) var(--ink);
  background: var(--white);
}
.tabs__pill {
  position: absolute;
  top: 0; bottom: 0; left: 0;
  width: 50%;
  background: var(--purple);
  transition: transform 200ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.tabs__btn {
  position: relative;
  z-index: 1;
  flex: 1;
  background: transparent;
  border: 0;
  cursor: pointer;
  padding: 15px clamp(20px, 5vw, 48px);
  font-family: var(--body);
  font-weight: 700;
  font-size: clamp(14px, 2vw, 16px);
  text-transform: uppercase;
  letter-spacing: -0.01em;
  color: var(--ink);
  transition: color 200ms ease-out;
}
.tabs__btn[aria-selected='true'] { color: var(--white); }

.panel {
  border: var(--bd) solid var(--ink);
  background: var(--white);
  box-shadow: var(--sh-2) var(--ink);
  display: grid;
  grid-template-columns: 1fr 1fr;
  overflow: hidden;
}
.panel[hidden] { display: none; }

.panel__copy { padding: clamp(28px, 3.4vw, 48px); display: flex; flex-direction: column; align-items: flex-start; }
.panel__copy .eyebrow { margin-bottom: 14px; }
.panel__copy .display { margin-bottom: 14px; }
.panel__blurb { font-size: 16px; line-height: 1.55; color: var(--ink-70); max-width: 440px; margin-bottom: 26px; }

.checks { display: flex; flex-direction: column; gap: 14px; margin-bottom: 32px; }
.checks li {
  display: flex; gap: 14px; align-items: flex-start;
  font-size: 15.5px; font-weight: 700; line-height: 1.4;
}
.checks li::before {
  content: '';
  width: 26px; height: 26px; flex: none; margin-top: 1px;
  background-color: var(--ink);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6L9 17l-5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 15px 15px;
}

/* Two frames, one ink rule between them. The stage used to hold mock product
   cards with invented ratings; it holds photographs now, so nothing here
   competes with them — no rotation, no chrome, no fabricated data. The dot
   grid stays underneath as the fallback if an image never arrives. */
/* A canvas with pictures laid on it. The stage drops to --canvas, the surface
   token, so it reads as a board rather than another white panel; the dot grid
   sits on top of it and the photographs are simply placed, tilted, with no
   elevation of their own. Everything is fluid — width is a percentage capped
   in px, so the pair scales with the panel instead of relying on
   breakpoint-by-breakpoint pixel overrides. */
.panel__stage {
  position: relative;
  border-left: var(--bd) solid var(--ink);
  background-color: var(--canvas);
  background-image: radial-gradient(var(--ink-08) 1.5px, transparent 1.5px);
  background-size: 22px 22px;
  min-height: 460px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: clamp(20px, 2.6vw, 28px);   /* room for the tape to sit above the print */
  padding: clamp(22px, 4vw, 40px);
}

/* A print, not a card: white margin around the photo, no elevation, small
   enough that the board reads as the larger thing. */
.stage__frame {
  position: relative;
  width: min(70%, 260px);   /* the cap governs on desktop; the % keeps small
                               screens from shrinking the photo to a thumbnail */
  aspect-ratio: 3 / 2;
  flex: none;
  border: var(--bd) solid var(--ink);
  background: var(--white);
  padding: 6px;
}

/* Placed by hand, not mounted: opposite tilts inside §1.3's ±2°, and only one
   panel is ever on screen, so the frames themselves spend exactly the two
   rotated elements the budget allows. No shadow — a print on a board has none. */
.stage__frame:nth-child(odd)  { transform: rotate(-1.5deg); }
.stage__frame:nth-child(even) { transform: rotate(2deg); }

/* the tape holding it up: translucent, overlapping the top edge, and set at its
   own angle so it reads as applied rather than drawn */
.stage__frame::before {
  content: '';
  position: absolute;
  z-index: 2;
  top: -9px;
  left: 50%;
  width: clamp(54px, 32%, 86px);
  height: 17px;
  background: var(--yellow-55);
}
.stage__frame:nth-child(odd)::before  { transform: translateX(-50%) rotate(3deg); }
.stage__frame:nth-child(even)::before { transform: translateX(-50%) rotate(-2.5deg); }

/* The pair stays centred as a group and steps apart by a bounded amount, so a
   wide single-column stage does not fling the two frames to opposite edges.
   One offset value drives both directions. */
.stage__frame:nth-child(odd)  { margin-right: clamp(20px, 14%, 96px); }
.stage__frame:nth-child(even) { margin-left:  clamp(20px, 14%, 96px); }

.stage__img { width: 100%; height: 100%; object-fit: cover; }


/* crowd score + live-now cards */


/* ─────────────────────────── film ─────────────────────────── */
.film {
 padding-top: 5rem;
 padding-bottom: 5rem;

}
.film__head { margin-bottom: 28px; }
.film__head .lede { margin-top: 14px; }

/* Two rows: the dock takes exactly the height its copy needs, and the film
   keeps everything left over. The dock used to sit on top of the film, which
   read fine on a wide frame but ate the middle of a portrait one — the mark
   ended up half behind it. Nothing overlaps now, at any width. */
.film__frame {
  margin: 0;
  aspect-ratio: 16 / 9;
  display: grid;
  grid-template-rows: 1fr auto;
  border: var(--bd) solid var(--ink);
  box-shadow: var(--sh-2) var(--ink);
  overflow: hidden;
  background: var(--ink);
}

.film__stage { position: relative; overflow: hidden; }
.film__video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}

/* the film is backdrop, not subject: the dim knocks it back far enough that
   paper type holds against it on any frame */
.film__dim {
  position: absolute; inset: 0;
  background: linear-gradient(180deg, var(--ink-50), var(--ink-60));
}

.film__dock {
  background: var(--ink);
  padding: 24px 32px;
  display: flex; align-items: center; justify-content: space-between;
  gap: 28px;
}

.film__note {
  font-size: 15px; font-weight: 600; line-height: 1.45;
  color: var(--paper-80);
  max-width: 430px;
}
.film__dock .btn { white-space: nowrap; }

/* ─────────────────────────── faq ─────────────────────────── */

.faq__list { display: flex; flex-direction: column; gap: 14px; max-width: 860px; margin-top: 24px; }
.faq__item { border: var(--bd) solid var(--ink); background: var(--white); box-shadow: var(--sh-2) var(--ink); }
.faq__q {
  width: 100%;
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  text-align: left;
  background: transparent;
  border: 0;
  cursor: pointer;
  padding: 20px 24px;
  font-family: var(--body);
  font-weight: 700;
  font-size: clamp(16px, 2.2vw, 20px);
  text-transform: uppercase;
  letter-spacing: -0.01em;
}
.faq__sign { font-family: var(--mono); font-size: 26px; font-weight: 700; color: var(--purple); flex: none; line-height: 1; }
.faq__a {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  transition: grid-template-rows 260ms ease-out, opacity 260ms ease-out;
}
.faq__a > p {
  overflow: hidden;
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink-70);
  max-width: 680px;
  padding: 0 24px;
}
.faq__item.is-open .faq__a { grid-template-rows: 1fr; opacity: 1; }
.faq__item.is-open .faq__a > p { padding-bottom: 22px; }

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

/* Two rows and four targets. The nav already carries the page's structure, so
   the footer stops repeating it: the mark, where to find us, the year, and the
   two documents anyone is entitled to read. */
.footer { background: var(--ink); color: var(--paper); padding: clamp(40px, 5vw, 56px) var(--pad-x) 30px; }
.footer__top {
  display: flex; align-items: center; justify-content: space-between;
  gap: 24px 40px; flex-wrap: wrap;
  max-width: var(--wrap); margin: 0 auto;
}
.footer__social { display: flex; gap: 10px; }
.social {
  width: 44px; height: 44px;
  display: flex; align-items: center; justify-content: center;
  box-shadow: var(--sh-1) var(--paper-20);
  color: var(--ink);
  font-weight: 700; font-size: 18px;
}
.social svg { width: 20px; height: 20px; }
.social--yellow { background: var(--yellow); }
.social--white  { background: var(--white); }
.social:hover   { color: var(--ink); transform: translate(2px, 2px); box-shadow: 1px 1px 0 var(--paper-20); }

.footer__base {
  display: flex; align-items: center; justify-content: space-between;
  gap: 6px 32px; flex-wrap: wrap;
  max-width: var(--wrap); margin: 30px auto 0;
  border-top: 2px solid var(--paper-20);
  padding-top: 4px;
  font-family: var(--mono); font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.1em; color: var(--paper-50);
}
.footer__legal { display: flex; gap: 24px; flex-wrap: wrap; }
/* the row's height comes from these two: a legal link is the last thing anyone
   should have to aim at, so each one is a full 44px target */
.footer__legal a {
  display: inline-flex; align-items: center; min-height: 44px;
  color: var(--paper-80);
}
.footer__legal a:hover { color: var(--yellow); }

/* ─────────────────────────── legal documents ───────────────────────────
   Read mode: one column, one reading path, nothing to decide. Ported from
   the app's LegalDocument shell — see the two spec corrections noted inline. */

.legal {
  max-width: 880px;
  margin: 0 auto;
  padding: clamp(28px, 5vw, 48px) var(--pad-x) clamp(64px, 9vw, 112px);
}

.legal__doc {
  border: var(--bd) solid var(--ink);
  background: var(--white);
  box-shadow: var(--sh-2) var(--ink);   /* §4: a document panel is a panel */
  padding: clamp(20px, 4.5vw, 44px);
}

.legal__doc .chip { margin-bottom: 20px; }

.legal__title {
  font-family: var(--display);
  font-weight: 800;
  text-transform: uppercase;
  font-size: clamp(1.85rem, 7vw, 2.75rem);   /* floor 29.6px — Syne stays >24 */
  line-height: 0.95;
  letter-spacing: -0.03em;
  overflow-wrap: break-word;
  hyphens: auto;
  margin-bottom: 8px;
}

.legal__stamp {
  font-family: var(--mono);                  /* §2.3: a date is data */
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--ink-60);
  margin-bottom: clamp(28px, 4vw, 36px);
}

/* 68ch keeps the line length readable inside a card wider than the prose */
.legal__intro {
  font-size: 16px;
  line-height: 1.65;
  color: var(--ink-75);
  max-width: 68ch;
  margin-bottom: 32px;
}

.legal__section { padding-top: 24px; margin-top: 28px; }
.legal__section:first-of-type { border-top: var(--bd) solid var(--ink); margin-top: 0; }

/* §2 HARD 1 — Syne is >24px only, so section headings are Space Grotesk */
.legal__h {
  font-family: var(--body);
  font-weight: 700;
  text-transform: uppercase;
  font-size: clamp(17px, 2.4vw, 20px);
  letter-spacing: -0.01em;
  line-height: 1.2;
  margin-bottom: 12px;
  overflow-wrap: break-word;
}

.legal__section p {
  font-size: 15px;
  line-height: 1.65;
  color: var(--ink-75);
  max-width: 68ch;
}

/* purple acts, pink underlines, yellow highlights — §3 roles, exactly */
.legal a[href^="mailto:"] {
  color: var(--purple);
  font-weight: 700;
  text-decoration: underline;
  text-decoration-color: var(--pink);
  text-decoration-thickness: 2px;
  text-underline-offset: 2px;
  transition: background-color 150ms ease-out;
}
.legal a[href^="mailto:"]:hover { background: var(--yellow); color: var(--purple-ink); }

/* ─────────────────────────── waitlist ─────────────────────────── */

.wl {
  width: min(560px, calc(100vw - 32px));
  max-height: calc(100dvh - 32px);
  padding: 0;
  border: var(--bd) solid var(--ink);
  background: var(--paper);
  box-shadow: var(--sh-3) var(--ink);
  color: var(--ink);
  overflow: auto;
  margin: auto;
}
.wl::backdrop { background: oklch(14% 0.005 286 / 0.6); }

.wl__close {
  position: absolute;
  top: 12px; right: 12px;
  width: 44px; height: 44px;
  display: flex; align-items: center; justify-content: center;
  background: var(--white);
  border: 2px solid var(--ink);
  box-shadow: 2px 2px 0 var(--ink);
  cursor: pointer;
  font-size: 15px;
  line-height: 1;
}
.wl__close:active { transform: translate(2px, 2px); box-shadow: none; }

.wl__panel {
  background-color: var(--paper);
  background-image: radial-gradient(oklch(14% 0.005 286 / 0.07) 1.5px, transparent 1.5px);
  background-size: 24px 24px;
  padding: clamp(28px, 6vw, 44px);
}
.wl__panel[hidden] { display: none; }
.wl__lockup { display: flex; align-items: center; gap: 11px; margin-bottom: 26px; }
.wl__panel .chip { margin-bottom: 20px; }
.wl__panel .display { margin-bottom: 12px; }
/* the success line carries a name the visitor typed, so it has to survive one
   80-character word without handing the panel a horizontal scrollbar */
.wl__blurb { font-size: 16px; line-height: 1.5; color: var(--ink-70); margin-bottom: 30px; overflow-wrap: break-word; }

.field { margin-bottom: 20px; }
.field label {
  display: block;
  font-family: var(--mono); font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.1em;
  margin-bottom: 8px;
}
/* says the field can be skipped before it is answered, not after */
.field__opt { font-weight: 500; color: var(--ink-50); margin-left: 6px; }
.field input,
.field select {
  width: 100%;
  font-family: var(--body); font-size: 16px; font-weight: 500;
  padding: 14px 16px;
  border: var(--bd) solid var(--ink);
  background: var(--white);
  color: var(--ink);
  outline: none;
  transition: box-shadow 150ms ease-out;
}
.field input:focus,
.field select:focus { box-shadow: var(--sh-1) var(--purple); }
.field input:focus-visible,
.field select:focus-visible { outline: none; }

/* the native control keeps the keyboard and the platform picker; only the
   arrow is ours, so it can be drawn in the same ink as the border */
.field__pick { position: relative; }
.field__pick select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 48px;
  border-radius: 0;
  cursor: pointer;
  /* a Windows/Android select clips its text to the line box otherwise */
  line-height: 1.2;
  text-overflow: ellipsis;
}
/* nothing is chosen yet, so the control reads as a prompt rather than an
   answer. Where :has() is missing the blank simply shows in full ink — the
   field still works, it just states itself more loudly */
.field__pick select:has(option[value='']:checked) { color: var(--ink-50); }
.field__pick option { color: var(--ink); }
.field__chev {
  position: absolute;
  top: 50%; right: 16px;
  width: 18px; height: 18px;
  transform: translateY(-50%);
  pointer-events: none;
  color: var(--ink);
}
.field__err {
  font-family: var(--mono); font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--red-ink); margin-top: 8px;
}
.field.is-bad input { border-color: var(--red); background: var(--red-tint); box-shadow: none; }

/* whatever the server or the network says, said once, in the system's own
   warning surface. Sentence case at 14px because this is the one message a
   visitor has to read to know what to do next. */
.wl__alert {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-bottom: 20px;
  padding: 13px 15px;
  background: var(--red-tint);
  border: var(--bd) solid var(--red);
  box-shadow: var(--sh-1) var(--ink);
  color: var(--red-ink);
  font-size: 14px;
  font-weight: 500;
  line-height: 1.45;
  overflow-wrap: break-word;
}
.wl__alert[hidden] { display: none; }
.wl__alert-icon { width: 19px; height: 19px; flex: none; margin-top: 1px; }

/* sending: the button stops pretending it can be pressed again */
.btn[disabled] { cursor: progress; background: var(--purple-ink); }
.btn[disabled]:hover,
.btn[disabled]:active { transform: none; box-shadow: var(--sh-2) var(--ink); }

.wl__fine {
  font-family: var(--mono); font-size: 10px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.06em;
  text-align: center; margin-top: 16px; color: var(--ink-50);
}


.wl__panel--done { background: var(--white); background-image: none; text-align: center; }
/* the heading is focused after submit so it is announced; it is not a control,
   so it should not draw a focus ring */
.wl__panel--done .display:focus { outline: none; }
.wl__check {
  width: 60px; height: 60px;
  margin: 0 auto 22px;
  display: flex; align-items: center; justify-content: center;
  background: var(--purple);
  color: var(--white);
  border: var(--bd) solid var(--ink);
  box-shadow: 4px 4px 0 var(--ink);
  color: var(--ink);
}
.wl__check svg { width: 30px; height: 30px; }
/* ── the waitlist without the modal enhancement ──
   `wl-modal` lands only once app.js has wired the dialog without throwing.
   Until then the waitlist is simply the last section of the page and the CTAs
   above are ordinary links that scroll to it — one form, one action, no
   duplicate markup, and nothing that needs JS to become reachable. */
html:not(.wl-modal) .wl {
  display: block;
  position: static;
  width: min(560px, calc(100% - 40px));
  max-height: none;
  margin: 0 auto clamp(48px, 9vw, 96px);
  box-shadow: var(--sh-2) var(--ink);
  scroll-margin-top: 96px;
}
html:not(.wl-modal) .wl__close { display: none; }
.wl__panel--done .wl__blurb { margin-bottom: 24px; }
.wl__panel--done .btn { display: inline-flex; }

/* ─────────────────────── analytics consent ─────────────────────── */

/* Built and inserted by analytics.js — there is no markup for it in any page.
   Without JS nothing is ever tracked, so a consent prompt would be asking
   permission for something that cannot happen.

   It is the first element in the body and the last one on the screen: a
   consent prompt should be the first thing a keyboard or screen-reader
   visitor reaches, and the bottom of the viewport is where it belongs to
   everyone else. z-index sits under the loader's 900 on purpose — if the two
   ever overlap, the loader wins. The waitlist is a <dialog>, which renders
   above both regardless. */
.consent {
  position: fixed;
  z-index: 800;
  left: var(--pad-x);
  right: var(--pad-x);
  bottom: clamp(16px, 3vw, 28px);
  margin: 0 auto;
  max-width: 760px;
  display: flex;
  align-items: center;
  gap: 22px;
  padding: 16px 20px;
  background: var(--paper);
  border: var(--bd) solid var(--ink);
  box-shadow: var(--sh-2) var(--ink);
  /* far enough to clear its own shadow, so nothing peeks above the fold
     while it is closed */
  transform: translateY(calc(100% + 40px));
  transition: transform 460ms var(--out);
}
.consent.is-in { transform: none; }

.consent__text {
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink-70);
}
.consent__text a { text-decoration: underline; text-underline-offset: 2px; }

.consent__acts {
  display: flex;
  gap: 10px;
  flex: none;
  margin-left: auto;
}

/* ─────────────────────── reveal choreography ─────────────────────── */

[data-reveal] {
  --d: 0s;
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 550ms ease-out var(--d), transform 550ms var(--out) var(--d);
}
.is-loaded [data-reveal] { opacity: 1; transform: none; }

[data-reveal='1'] { --d: 0.20s; }
[data-reveal='2'] { --d: 0.26s; }
[data-reveal='3'] { --d: 0.32s; }
[data-reveal='4'] { --d: 0.38s; }
[data-reveal='5'] { --d: 0.44s; }
[data-reveal='6'] { --d: 0.58s; }
[data-reveal='7'] { --d: 0.66s; }
[data-reveal='8'] { --d: 0.74s; }

/* headline lines rise out of their own mask */
.hero__title .line__in {
  opacity: 1;
  transform: translateY(105%);
  transition: transform 750ms var(--out) var(--d);
}
.is-loaded .hero__title .line__in { transform: none; }

/* nav + ticker drop in from above */
.ticker, .nav {
  transform: translateY(-101%);
  transition: transform 600ms var(--out) 0.14s;
}
.is-loaded .ticker, .is-loaded .nav { transform: none; }

/* no JS, or already revealed: everything is simply present */
body:not(.is-loading):not(.is-revealing):not(.is-loaded) [data-reveal],
body:not(.is-loading):not(.is-revealing):not(.is-loaded) .hero__title .line__in { opacity: 1; transform: none; }
body:not(.is-loading):not(.is-revealing):not(.is-loaded) .ticker,
body:not(.is-loading):not(.is-revealing):not(.is-loaded) .nav { transform: none; }
body:not(.is-loading):not(.is-revealing):not(.is-loaded) .loader { display: none; }
body:not(.is-loading):not(.is-revealing):not(.is-loaded) .mark__hint { opacity: 1; }

/* ═══════════════════════════ responsive ═══════════════════════════ */

@media (max-width: 1080px) {
  /* stacked: the mark leads, the copy follows it */
  .hero { grid-template-columns: 1fr; gap: 18px; }
  .hero__visual { order: 1; }
  .hero__copy { order: 2; }
  .mark { max-width: 460px; }
  .panel__stage { min-height: 420px; }


}

@media (max-width: 860px) {
  .compare { grid-template-columns: 1fr; gap: 36px; }
  .panel { grid-template-columns: 1fr; }
  /* stacked, the stage leads: it shows the thing being described, so it earns
     the top of the panel and the copy reads as the caption underneath */
  .panel__stage {
    order: -1;
    border-left: 0;
    border-bottom: var(--bd) solid var(--ink);
    min-height: 400px;
  }
  .tabs { width: 100%; }

  /* still a row here, but a narrower one — the dock has to hold the note and
     the button side by side without crushing either */
  .film__frame { aspect-ratio: 4 / 3; }
  .film__dock { padding: 18px 24px; gap: 20px; }

  .footer__top { gap: 22px 28px; }
}

@media (max-width: 620px) {
  .hero { gap: 22px; }
  .mark { max-width: 290px; }
  .hero__title { margin-top: 0; }
  .hero__lede { margin: 18px 0 26px; }
  .hero__ctas { margin-bottom: 30px; }
  .hero__ctas .btn { width: 100%; }

  .ticker { font-size: 9px; letter-spacing: 0.1em; gap: 10px; }
  .ticker__right { gap: 10px; }

  .card__body { padding: 24px 22px; }
  .point__icon { width: 38px; height: 38px; }

  .panel__stage { min-height: 340px; }


  /* The dock stacks here, so it claims roughly a third of the frame and the
     film only gets what is left. 2/3 rather than the 4/5 the design draws:
     that ratio assumed the dock sat over the film, and once it sits under it
     the frame has to grow by the dock's own height or the stage is a letterbox
     slot with the mark jammed into it. */
  .film__frame { aspect-ratio: 2 / 3; }
  .film__dock { flex-direction: column; align-items: stretch; gap: 14px; padding: 18px 20px; }
  .film__note { font-size: 13.5px; max-width: none; }
  .film__dock .btn { width: 100%; }
  .faq__q { padding: 16px 18px; }
  .faq__a > p { padding: 0 18px; }
  .faq__item.is-open .faq__a > p { padding-bottom: 18px; }


  /* The waitlist becomes a bottom sheet — a modal presentation, so it belongs
     to the modal mode and not to the static section the page falls back to.
     `100%` rather than `100vw`, which would add the scrollbar gutter. */
  /* The sheet itself does not scroll — the panel inside it does. That keeps the
     close button out of the scrolling box entirely, so it cannot drift, and it
     stays a plain absolutely-positioned corner control that hit-tests where it
     is drawn. */
  /* `[open]` is not decoration. A closed <dialog> is hidden by the UA rule
     `dialog:not([open]) { display: none }`, and any author `display` beats the
     UA sheet whatever its specificity — so declaring `display: flex` on the
     element itself keeps the sheet on screen after close() removes the
     attribute. Scoping the layout to the open state hands that job back. */
  html.wl-modal .wl[open] {
    display: flex;
    flex-direction: column;
    width: 100%;
    max-width: 100%;
    max-height: 92dvh;
    margin: auto 0 0;
    border-width: var(--bd) 0 0;
    box-shadow: none;
    overflow: hidden;
  }

  html.wl-modal .wl__close {
    top: 10px;
    right: 10px;
    z-index: 3;   /* above the panel it sits over */
  }

  html.wl-modal .wl__panel {
    /* min-height:0 is load-bearing: without it a flex item refuses to shrink
       below its content, and nothing inside can scroll */
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;   /* scrolling the sheet must not drag the page */
    padding-top: 64px;              /* clears the close button */
    padding-bottom: max(20px, env(safe-area-inset-bottom));
  }

  /* On a phone the keyboard eats the bottom of the sheet, which is exactly
     where the only submit lives. Pinning it to the scrollport floor means the
     ask is reachable whether the keyboard is up or down. */
  html.wl-modal .wl .btn--block {
    position: sticky;
    bottom: 0;
  }

  /* the two answers stop fitting beside the copy long before this, and a
     consent choice is not a place to make anyone aim at a small target */
  .consent {
    left: 12px;
    right: 12px;
    bottom: max(12px, env(safe-area-inset-bottom));
    flex-direction: column;
    align-items: stretch;
    gap: 14px;
    padding: 18px;
  }
  .consent__acts { margin-left: 0; }
  .consent__acts .btn { flex: 1; }
}

/* ─────────────────────── reduced motion ─────────────────────── */

@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;
    transition-delay: 0ms !important;
  }
  .is-loading .mark__still { position: absolute; z-index: auto; transform: none !important; }
  /* the mark never leaves the hero here, so the slot would only reserve empty
     space above the number — the loader is simply the count */
  .loader__slot { display: none; }
}
