/* Sortie landing — design tokens mirrored from the app's Theme.swift
   ("Warm Editorial": a near-white canvas lit by a diffuse blush wash, ink
   instead of black, bright flat accents carrying dark labels, generous soft
   radii, and an editorial serif reserved for brand moments). */

/* Fraunces 72pt — the app's bundled display face (SIL OFL, self-hosted latin
   subset, licence served at /fonts/OFL.txt). The 72pt optical cut is the one
   the app ships: the smaller cuts are body faces and lose the thick-thin
   contrast that makes it a display serif.

   Two faces only, and the split is the type system: SemiBold roman for every
   heading (what the app sets its headings in) and Regular italic for the
   editorial layer — folios, city names, asides. Never synthesized, and no
   regular ROMAN face: nothing on the page sets the serif upright at 400, so
   shipping it just made every visitor download an unused 18KB. Anything
   added later that needs it will silently fall back to ui-serif — check the
   rendered face, not just the family name. */
@font-face {
  font-family: "Fraunces";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/fonts/fraunces-semibold.woff2") format("woff2");
}

@font-face {
  font-family: "Fraunces";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("/fonts/fraunces-italic.woff2") format("woff2");
}

:root {
  /* ---- Palette: the light values from Theme.swift ----
     Accents come in two tiers, and mixing them up is the one way to break
     this palette. The FLAT tier is for fills, strokes, glyphs and dots only —
     flat coral measures ~2.6:1 on the canvas and can never carry small text.
     The INK tier is for any text on a surface. `--on-accent` is the label on
     top of a flat fill: a fixed near-black, because the flat accents are
     bright and a label that inverted would fail on them. */
  --accent: #ff7a4d;          /* Palette.exhibition — the site's one accent */
  --accent-ink: #c0431f;      /* Palette.exhibitionInk — accent as text */
  --accent-press: #f2683a;
  --party-ink: #7a4bef;       /* Palette.partyInk */
  --free: #18c98d;            /* Palette.free — dots and marks */
  --free-ink: #0b7a57;        /* Palette.freeInk — prices */
  --brand-ink: #5341e0;       /* Palette.brandInk — focus rings, links */
  --on-accent: #1e1f33;       /* Palette.onAccent */

  --canvas: #fafafa;
  --surface: #ffffff;
  --fill: #f0eef3;
  --separator: #e7e5ec;
  --hairline: rgba(0, 0, 0, 0.07); /* Palette.cardHairline */
  --selection: #ede7fb;

  --ink: #1e1f33;
  --ink-muted: #6f6c86;
  --ink-faint: #93909f;

  /* The night world, used by the full-bleed band. */
  --canvas-dark: #121220;
  --ink-dark: #f2f0f5;
  --ink-muted-dark: #9c99b4;
  --accent-dark: #ff9269; /* Palette.exhibition, dark branch */

  /* Palette.heroStops — violet → pink → orange. One loud moment per page. */
  --hero-gradient: linear-gradient(115deg, #6a4fd6 0%, #d9498a 52%, #d9762e 100%);

  /* The ambient wash: blush from the top edge, peach from the upper-right
     corner, both keyed to the accent exactly as AmbientWash mixes them. */
  --wash-top: 248, 195, 195;
  --wash-edge: 252, 209, 181;

  /* Type. The serif carries brand moments; the system sans carries everything
     functional — labels, card titles, metadata, buttons. They never swap. */
  --serif: "Fraunces", ui-serif, Georgia, "Times New Roman", serif;
  --sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, sans-serif;

  /* 8-pt grid (Metrics.unit) */
  --s1: 8px;
  --s2: 16px;
  --s3: 24px;
  --s4: 32px;
  --s6: 48px;
  --s8: 64px;
  --section: clamp(64px, 10vw, 128px);

  /* Metrics: soft geometry — large outer radii, capsule controls. */
  --radius-card: 24px;
  --radius-button: 18px;
  --radius-control: 14px;
  --button-height: 54px;
  --measure: 62ch;
  --page-max: 1100px;

  /* Palette.shadowContact + Palette.shadowAmbient — a tight contact shadow
     under a wide diffuse one. On #FAFAFA a white card also needs the hairline
     to hold its edge; the two together are what "elevation" means here. */
  --elevation: 0 1px 2px rgba(0, 0, 0, 0.07), 0 8px 16px rgba(0, 0, 0, 0.1);
  --elevation-lift: 0 2px 4px rgba(0, 0, 0, 0.07), 0 18px 32px rgba(0, 0, 0, 0.14);

  interpolate-size: allow-keywords; /* animated <details> height (progressive) */
  color-scheme: light;
}

/* Cross-document transitions between / and /privacy (progressive). */
@view-transition {
  navigation: auto;
}

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

html {
  scroll-behavior: smooth;
  /* The ghost words bleed past their column on purpose (`right: -3%`), which
     opened a few px of horizontal scroll between 861px and ~1150px. `clip`
     rather than `hidden`: it stops the bleed without turning the root into a
     scroll container, so scroll(root) timelines keep working. */
  overflow-x: clip;
}

body {
  position: relative; /* anchors .wash to the document */
  margin: 0;
  background: var(--canvas);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* Two real faces are served; a synthesized third would be a smear. */
  font-synthesis: none;
}

::selection {
  background: var(--selection);
  color: var(--ink);
}

/* The ambient wash — the app's AmbientWash, one light source entering from the
   top of the page. Low-saturation and blurry by design: it should read as
   light in the room, not as decoration. Everything after it in the document is
   positioned, so it paints underneath without a z-index. */
.wash {
  position: absolute;
  inset: 0 0 auto;
  height: min(1200px, 135vh);
  pointer-events: none;
  /* The radial hasn't finished fading by the time the box ends, so clipping it
     leaves a visible seam. The mask guarantees the light dies out smoothly
     whatever the viewport does to the gradient's geometry. */
  mask-image: linear-gradient(180deg, #000 55%, transparent 100%);
  -webkit-mask-image: linear-gradient(180deg, #000 55%, transparent 100%);
  background:
    radial-gradient(
      110% 78% at 100% 0%,
      rgba(var(--wash-edge), 0.42) 0%,
      rgba(var(--wash-edge), 0.13) 45%,
      transparent 100%
    ),
    linear-gradient(
      180deg,
      rgba(var(--wash-top), 0.46) 0%,
      rgba(var(--wash-top), 0.15) 13%,
      transparent 34%
    );
}

header,
main,
footer,
.colophon {
  position: relative;
}

/* Film grain over everything — makes the "paper" literal. Pure SVG noise,
   no asset. Kept faint enough to read as texture, not dirt. */
.grain {
  position: fixed;
  inset: 0;
  z-index: 40;
  pointer-events: none;
  opacity: 0.04;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 180px;
}

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

/* ---------- Type ramp ---------- */

h1,
h2,
h3 {
  font-family: var(--serif);
  font-weight: 600;
  margin: 0;
  text-wrap: balance;
}

h1 {
  font-size: clamp(40px, 6.4vw, 66px);
  line-height: 1.04;
  letter-spacing: -0.015em;
}

h2 {
  font-size: clamp(29px, 4.1vw, 42px);
  line-height: 1.12;
  letter-spacing: -0.012em;
}

/* Optical alignment: trim the leading above cap height and below baseline
   so display type sits flush against its neighbours (progressive). */
@supports (text-box: trim-both cap alphabetic) {
  h1,
  h2 {
    text-box: trim-both cap alphabetic;
    margin-block: 0.12em; /* give back a touch of breathing room */
  }
}

/* Masked word-stagger reveal for the hero H1. Each word sits in a clipping
   box (.w) and rises into place (.wi). Static without motion preference. */
.w {
  display: inline-block;
  overflow: clip;
  /* Fraunces' italic descenders and the drawn underline both sit below the
     baseline; clipping tight to the line box would shave them. */
  padding-bottom: 0.12em;
  margin-bottom: -0.12em;
}

.wi {
  display: inline-block;
}

h3 {
  font-size: 21px;
  line-height: 1.3;
  letter-spacing: -0.01em;
}

p {
  margin: 0;
}

/* The italic accent word — the page's one loud moment, set in the app's hero
   gradient. Regular italic against the semibold roman around it is the
   contrast; the weight difference is deliberate, not a missing face. */
.accent {
  font-style: italic;
  font-weight: 400;
  background: var(--hero-gradient);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* Fallback for anything that can't clip a background to text. */
@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  .accent {
    background: none;
    color: var(--accent-ink);
  }
}

/* The clipping box around it carries a drawn-in underline in the same ink. */
.w-accent {
  background-image: linear-gradient(var(--accent), var(--accent));
  background-repeat: no-repeat;
  background-size: 100% 0.045em;
  background-position: 0 92%;
}

.kicker {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

/* Magazine folio: "№ 01 — The problem". Serif № keeps it bookish. */
.folio {
  display: flex;
  align-items: center;
  gap: var(--s2);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

.folio::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--separator);
}

.folio .no {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 400;
  font-size: 15px;
  text-transform: none;
  letter-spacing: 0;
  color: var(--accent-ink);
}

.lede {
  font-size: clamp(17px, 2vw, 19px);
  color: var(--ink-muted);
  max-width: var(--measure);
}

/* ---------- Nav ---------- */

.nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-block: var(--s2);
  border-bottom: 1px solid var(--separator);
}

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--serif);
  font-weight: 600;
  font-size: 25px;
  letter-spacing: -0.015em;
  color: var(--ink);
  text-decoration: none;
}

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

/* ---------- Buttons ---------- */

/* Bright flat fill, near-black label — the app's badge language at button
   scale. A deep fill carrying white text is the palette this one replaced. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  height: var(--button-height);
  padding-inline: var(--s4);
  border-radius: var(--radius-button);
  background: var(--accent);
  color: var(--on-accent);
  font-family: var(--sans);
  font-size: 17px;
  font-weight: 600;
  text-decoration: none;
  transition: background 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}

.btn .arrow {
  transition: transform 0.2s ease;
}

.btn:hover {
  background: var(--accent-press);
  transform: translateY(-1px);
  box-shadow: 0 10px 24px -12px rgba(192, 67, 31, 0.55);
}

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

.btn:active {
  transform: translateY(0) scale(0.98);
}

.btn:focus-visible {
  outline: 3px solid var(--brand-ink);
  outline-offset: 3px;
}

.btn--small {
  height: 40px;
  padding-inline: var(--s3);
  border-radius: 999px;
  font-size: 15px;
}

/* Outline capsule — the web reading of the app's quiet PrimaryButton ("Try
   again", "Maybe later"): same object as the filled CTA with the weight
   removed, ink label, no fill. The app draws no border because Liquid Glass
   supplies its own specular rim; without the material, a soft ink hairline is
   what stands in for that edge. Ink-based, not `--separator` — the grey
   hairline all but vanished over the blush wash. */
.btn--ghost {
  background: transparent;
  color: var(--ink);
  border: 1px solid rgba(30, 31, 51, 0.22);
}

.btn--ghost:hover {
  background: rgba(30, 31, 51, 0.05);
  border-color: rgba(30, 31, 51, 0.38);
  transform: none;
  box-shadow: none;
}

.cta-note {
  font-size: 15px;
  color: var(--ink-muted);
}

/* Official App Store badge (Apple artwork — never restyle the SVG itself).
   Height matches the button height; hover mirrors the .btn lift. */
.badge-link {
  display: inline-block;
  transition: transform 0.15s ease, filter 0.15s ease;
}

.badge-link:hover {
  transform: translateY(-1px);
  filter: drop-shadow(0 10px 20px rgba(30, 31, 51, 0.25));
}

.badge-link:active {
  transform: translateY(0) scale(0.98);
}

.badge-link:focus-visible {
  outline: 3px solid var(--brand-ink);
  outline-offset: 3px;
  border-radius: 8px;
}

.badge-link img {
  display: block;
  height: var(--button-height);
  width: auto;
}

/* QR popover: floats above a badge on hover/focus (desktop pointers only —
   touch users just tap the badge). White surface card with a bordered tail,
   easing up from the badge. Non-interactive, purely a scan target. */
.qr-wrap {
  position: relative;
  display: inline-block;
}

.qr-pop {
  position: absolute;
  bottom: calc(100% + 14px);
  left: 50%;
  translate: -50%;
  z-index: 30;
  display: grid;
  gap: 6px;
  justify-items: center;
  padding: 12px 12px 8px;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-control);
  box-shadow: var(--elevation-lift);
  opacity: 0;
  visibility: hidden;
  transform: translateY(10px) scale(0.94);
  transform-origin: bottom center;
  transition: opacity 0.25s ease, transform 0.25s cubic-bezier(0.2, 0.7, 0.3, 1.1), visibility 0.25s;
  pointer-events: none;
}

/* Bordered tail: a rotated square peeking from the card's bottom edge. */
.qr-pop::after {
  content: "";
  position: absolute;
  top: calc(100% - 7px);
  left: 50%;
  width: 14px;
  height: 14px;
  margin-left: -7px;
  background: var(--surface);
  border-right: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
  rotate: 45deg;
}

.qr-pop img {
  display: block;
  border-radius: 6px;
}

.qr-pop p {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-muted);
  white-space: nowrap;
}

@media (hover: hover) {
  .qr-wrap:hover .qr-pop,
  .qr-wrap:focus-within .qr-pop {
    opacity: 1;
    visibility: visible;
    transform: none;
    transition-delay: 0.1s;
  }
}

/* The closer's badge is the page's final statement — oversized. */
.badge-link--xl img {
  height: clamp(72px, 12vw, 104px);
}

.badge-link--xl:hover {
  transform: translateY(-2px);
  filter: drop-shadow(0 18px 36px rgba(30, 31, 51, 0.3));
}

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

.hero {
  display: grid;
  grid-template-columns: minmax(0, 1.12fr) minmax(0, 0.88fr);
  align-items: center;
  gap: var(--s8);
  padding-block: clamp(48px, 7vw, 96px) var(--section);
}

.hero-copy {
  display: grid;
  gap: var(--s3);
  justify-items: start;
}

.hero-cta {
  display: grid;
  gap: var(--s1);
  justify-items: start;
}

/* ---------- Phone ---------- */

.phone-slot {
  display: flex;
  justify-content: center;
  perspective: 1200px;
}

/* Real framed app screenshot. drop-shadow (not box-shadow) follows the
   device silhouette through the PNG's transparent corners. */
.phone-img {
  width: min(316px, 78vw);
  height: auto;
  display: block;
  filter:
    drop-shadow(0 32px 48px rgba(30, 31, 51, 0.3))
    drop-shadow(0 4px 12px rgba(30, 31, 51, 0.14));
  transform: rotate3d(0.6, -1, 0.15, 14deg);
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

/* ---------- City marquee — "any city", shown not claimed ---------- */

.marquee {
  overflow: hidden;
  border-block: 1px solid var(--separator);
  padding-block: var(--s2);
  mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
}

/* Spacing lives on the items (not track gap) so translateX(-50%) is an
   exact one-copy shift and the loop never jumps. */
.marquee-track {
  display: flex;
  width: max-content;
  animation: marquee 48s linear infinite;
}

.marquee-track > span {
  margin-right: var(--s4);
  font-family: var(--serif);
  font-style: italic;
  font-weight: 400;
  font-size: 22px;
  color: var(--ink-muted);
  white-space: nowrap;
  display: flex;
  align-items: baseline;
  gap: var(--s4);
}

.marquee-track > span::after {
  content: "✦";
  /* Sans, not the inherited serif: an upright glyph inside the italic marquee
     otherwise requests the serif's 400-roman face — a whole font download for
     one ornament Fraunces doesn't even have a glyph for. */
  font-family: var(--sans);
  font-style: normal;
  font-size: 11px;
  color: var(--accent);
  align-self: center;
}

@keyframes marquee {
  to {
    transform: translateX(-50%);
  }
}

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

.proof {
  padding-block: var(--s4);
  text-align: center;
  font-family: var(--serif);
  font-style: italic;
  font-weight: 400;
  font-size: clamp(19px, 2.6vw, 24px);
  color: var(--ink-muted);
}

.proof b {
  color: var(--accent-ink);
  font-weight: 400;
}

/* ---------- Sections ---------- */

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

.section-head {
  display: grid;
  gap: var(--s2);
  margin-bottom: var(--s6);
}

.live-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--free);
  margin-right: 6px;
  animation: pulse 2s ease-in-out infinite;
}

/* ---------- Live strip — cards as ticket stubs ---------- */

.strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 14px; /* Metrics.cardGap */
}

.event-card {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
  overflow: hidden; /* also clips the notch circles into semicircles */
  display: flex;
  flex-direction: column;
  box-shadow: var(--elevation);
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.event-card:hover {
  transform: translateY(-3px) rotate(-0.4deg);
  box-shadow: var(--elevation-lift);
}

/* Ticket notches: canvas-coloured circles punched into both edges, centred
   on the stub's dashed tear line (the stub is a fixed 44px tall). */
.event-card::before,
.event-card::after {
  content: "";
  position: absolute;
  bottom: 37px; /* 44px stub − half of 14px circle */
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--canvas);
  border: 1px solid var(--hairline);
  z-index: 1;
}

.event-card::before { left: -8px; }
.event-card::after { right: -8px; }

.event-cover {
  aspect-ratio: 3 / 2;
  background: var(--fill);
  object-fit: cover;
  width: 100%;
  display: block;
  transition: scale 0.35s ease;
}

.event-card:hover .event-cover {
  scale: 1.04;
}

.event-body {
  padding: var(--s2);
  display: grid;
  gap: 6px;
  align-content: start;
  flex: 1;
}

.event-tag {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.event-tag--exhibitions,
.event-tag--exhibition { color: var(--accent-ink); }
.event-tag--parties,
.event-tag--party { color: var(--party-ink); }

/* An event card's title is a product artifact, so it takes the app's card
   font — the system sans — not the editorial serif. */
.event-title {
  font-family: var(--sans);
  font-weight: 600;
  font-size: 17px;
  line-height: 1.3;
  letter-spacing: -0.01em;
}

.event-meta {
  font-size: 14px;
  line-height: 1.4;
  color: var(--ink-muted);
}

/* The tear-off stub: dashed line, freshness left, price right. */
.event-stub {
  height: 44px;
  border-top: 1px dashed var(--separator);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-inline: var(--s2);
  font-size: 13px;
  color: var(--ink-muted);
}

.event-found {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 400;
  font-size: 14px;
}

.event-price {
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.event-price--free {
  color: var(--free-ink);
}

/* ---------- Pain section (editorial block + ghost words) ---------- */

.editorial {
  position: relative;
  text-align: center;
  display: grid;
  gap: var(--s3);
  justify-items: center;
}

.editorial .lede {
  margin-inline: auto;
}

.ghosts {
  position: absolute;
  inset: 0;
  pointer-events: none;
  font-family: var(--serif);
  font-style: italic;
  font-weight: 400;
  color: var(--ink);
}

.ghosts span {
  position: absolute;
  opacity: 0.07;
  font-size: clamp(20px, 3vw, 34px);
  white-space: nowrap;
  animation: drift 14s ease-in-out infinite alternate;
}

.ghosts span:nth-child(1) { top: -8%; left: 4%; }
.ghosts span:nth-child(2) { top: 10%; right: 2%; animation-delay: -3s; }
.ghosts span:nth-child(3) { bottom: 12%; left: 0%; animation-delay: -6s; }
.ghosts span:nth-child(4) { bottom: -6%; right: 8%; animation-delay: -9s; }
.ghosts span:nth-child(5) { top: 42%; left: -4%; animation-delay: -11s; }
.ghosts span:nth-child(6) { top: 55%; right: -3%; animation-delay: -5s; }

@keyframes drift {
  from { transform: translateY(-10px); }
  to { transform: translateY(14px); }
}

/* ---------- Steps ---------- */

.steps {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s4);
}

.step {
  border-top: 1px solid var(--separator);
  padding-top: var(--s3);
  display: grid;
  gap: var(--s1);
  align-content: start;
}

/* Scoped past `.step p` on purpose — the plain descendant selector below is
   more specific than a lone class and was silently setting this to 16px. */
.step .step-number {
  font-family: var(--serif);
  font-style: italic;
  font-size: 60px;
  font-weight: 400;
  color: var(--accent-ink);
  line-height: 1;
}

.step p {
  color: var(--ink-muted);
  font-size: 16px;
}

/* ---------- Dark band ---------- */

.band {
  background: var(--canvas-dark);
  color: var(--ink-dark);
  padding-block: var(--section);
}

.band .folio {
  color: var(--ink-muted-dark);
}

.band .folio::after {
  background: rgba(242, 240, 245, 0.16);
}

.band .folio .no {
  color: var(--accent-dark);
}

.band h2 {
  max-width: 24ch;
}

.band-inner {
  display: grid;
  gap: var(--s3);
}

.band .lede {
  color: var(--ink-muted-dark);
}

/* The signature interaction: the "viral" line gets struck through in coral
   as it scrolls into view — drawn as a background bar rather than a pseudo
   element, because an absolutely-positioned ::after inside an INLINE parent
   resolves to zero width in Chrome and never painted at all. `clone` gives
   each wrapped line its own bar. Fully drawn by default, so browsers without
   scroll timelines still see the strike. */
.strike {
  color: rgba(242, 240, 245, 0.45);
  background-image: linear-gradient(var(--accent-dark), var(--accent-dark));
  background-repeat: no-repeat;
  background-size: 100% 0.055em;
  background-position: 0 60%;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}

/* ---------- Final CTA ---------- */

.closer {
  text-align: center;
  display: grid;
  gap: var(--s3);
  justify-items: center;
  padding-block: var(--section);
}

.closer-line {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 400;
  font-size: 22px;
  color: var(--accent-ink);
}

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

/* The list is centred in the page, so its heading has to share the measure —
   otherwise the h2 sits at the page gutter and the questions start elsewhere. */
.faq,
.section-head--faq {
  max-width: 760px;
  margin-inline: auto;
}

.faq details {
  border-top: 1px solid var(--separator);
  padding-block: var(--s2);
}

.faq details:last-child {
  border-bottom: 1px solid var(--separator);
}

.faq summary {
  font-family: var(--serif);
  font-weight: 600;
  font-size: 20px;
  line-height: 1.35;
  cursor: pointer;
  list-style: none;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s2);
  transition: color 0.15s ease;
}

.faq summary:hover {
  color: var(--accent-ink);
}

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

.faq summary::after {
  content: "+";
  font-family: var(--sans);
  font-size: 22px;
  font-weight: 400;
  color: var(--ink-muted);
  transition: rotate 0.2s ease;
}

.faq details[open] summary::after {
  rotate: 45deg;
}

/* Animated open/close where ::details-content + interpolate-size exist. */
.faq details::details-content {
  block-size: 0;
  overflow: clip;
  transition: block-size 0.3s ease, content-visibility 0.3s ease allow-discrete;
}

.faq details[open]::details-content {
  block-size: auto;
}

.faq details p {
  margin-top: var(--s1);
  color: var(--ink-muted);
  max-width: var(--measure);
}

/* ---------- Giant cropped wordmark + footer ---------- */

.colophon {
  overflow: hidden;
  line-height: 0.78;
  text-align: center;
  user-select: none;
}

.colophon span {
  font-family: var(--serif);
  font-weight: 400;
  font-style: italic;
  font-size: clamp(110px, 24vw, 300px);
  letter-spacing: -0.03em;
  color: var(--fill);
  display: inline-block;
  transform: translateY(0.1em); /* crop the baseline off the page edge */
}

.footer {
  border-top: 1px solid var(--separator);
  padding-block: var(--s6);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3);
  align-items: baseline;
  justify-content: space-between;
  font-size: 15px;
  color: var(--ink-muted);
}

.footer nav {
  display: flex;
  gap: var(--s3);
}

.footer a {
  color: var(--ink-muted);
  text-decoration: none;
  background-image: linear-gradient(currentColor, currentColor);
  background-size: 0% 1px;
  background-position: 0 100%;
  background-repeat: no-repeat;
  transition: background-size 0.25s ease, color 0.15s ease;
}

.footer a:hover {
  color: var(--ink);
  background-size: 100% 1px;
}

/* External link — ink text with the ↗ glyph, the app's convention for links
   that leave the product (never blue link text). */
.footer a.ext::after {
  content: "\2197"; /* ↗ */
  font-size: 0.8em;
  margin-left: 2px;
  display: inline-block;
}

.footer a:focus-visible {
  outline: 3px solid var(--brand-ink);
  outline-offset: 3px;
}

/* ---------- Legal / support subpages ---------- */

.prose {
  max-width: 720px;
  margin-inline: auto;
  padding-block: var(--s8);
  display: grid;
  gap: var(--s3);
}

.prose h1 {
  font-size: clamp(32px, 5vw, 44px);
}

.prose h2 {
  font-size: 24px;
  margin-top: var(--s3);
}

.prose p,
.prose li {
  color: var(--ink-muted);
}

.prose a {
  color: var(--brand-ink);
}

/* ---------- Motion (progressive enhancement) ---------- */

@media (prefers-reduced-motion: no-preference) {
  /* Hero H1: masked word-stagger rise. The H1 block itself is excluded from
     the group fade below — its words carry the entrance instead. */
  .hero-copy > h1 {
    transition: none;
  }

  .wi {
    transform: translateY(112%);
    animation: word-rise 0.65s cubic-bezier(0.2, 0.6, 0.2, 1) forwards;
    animation-delay: calc(0.15s + var(--i, 0) * 55ms);
  }

  @keyframes word-rise {
    to {
      transform: none;
    }
  }

  /* The underline draws in once its word has landed. */
  .w-accent {
    background-size: 0% 0.045em;
    animation: accent-draw 0.5s ease-out 1.15s forwards;
  }

  @keyframes accent-draw {
    to {
      background-size: 100% 0.045em;
    }
  }

  /* Load-in stagger for the hero copy (@starting-style entry transition). */
  .hero-copy > * {
    transition: opacity 0.7s ease, transform 0.7s ease;
  }

  .hero-copy > :nth-child(2) { transition-delay: 0.08s; }
  .hero-copy > :nth-child(3) { transition-delay: 0.16s; }
  .hero-copy > :nth-child(4) { transition-delay: 0.24s; }

  @starting-style {
    .hero-copy > * {
      opacity: 0;
      transform: translateY(16px);
    }
  }

  /* The phone floats gently (on the wrapper, so it composes with the
     scroll-driven straighten on the image itself). */
  .phone-slot {
    animation: float 7s ease-in-out infinite;
  }

  @keyframes float {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-8px); }
  }
}

/* Reading-progress hairline in coral along the top edge. Invisible (scaleX 0)
   wherever scroll timelines don't exist. */
.progress {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  z-index: 60;
  pointer-events: none;
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .progress {
      animation: progress-grow linear both;
      animation-timeline: scroll(root);
    }

    @keyframes progress-grow {
      from { transform: scaleX(0); }
      to { transform: scaleX(1); }
    }

    .reveal {
      animation: reveal-up linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 42%;
    }

    @keyframes reveal-up {
      from {
        opacity: 0;
        transform: translateY(24px);
      }
      to {
        opacity: 1;
        transform: none;
      }
    }

    /* Hero phone: starts tilted, settles flat over the first stretch of scroll. */
    .phone-img {
      animation: straighten linear both;
      animation-timeline: scroll(root);
      animation-range: 0px 480px;
    }

    @keyframes straighten {
      from { transform: rotate3d(0.6, -1, 0.15, 14deg); }
      to { transform: none; }
    }

    /* Strike through "what's viral" as the band scrolls into view. */
    .strike {
      animation: strike-draw linear both;
      animation-timeline: view();
      animation-range: entry 30% entry 90%;
    }

    @keyframes strike-draw {
      from { background-size: 0% 0.055em; }
      to { background-size: 100% 0.055em; }
    }
  }
}

@media (prefers-reduced-motion: reduce) {
  .live-dot,
  .ghosts span,
  .marquee-track {
    animation: none;
  }

  .qr-pop {
    transform: none;
    transition: opacity 0.15s ease, visibility 0.15s;
  }
}

/* ---------- Responsive ---------- */

@media (max-width: 860px) {
  .hero {
    grid-template-columns: 1fr;
    gap: var(--s6);
    padding-block: var(--s4) var(--s8);
    text-align: center;
  }

  .hero-copy,
  .hero-cta {
    justify-items: center;
  }

  .steps {
    grid-template-columns: 1fr;
    gap: var(--s3);
  }

  .ghosts {
    display: none;
  }

  .strip {
    display: flex;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    margin-inline: calc(-1 * var(--s3));
    padding-inline: var(--s3);
    padding-block: 4px var(--s1);
    -webkit-overflow-scrolling: touch;
  }

  .strip .event-card {
    flex: 0 0 78%;
    max-width: 300px;
    scroll-snap-align: start;
  }
}
