/* =============================================================================
   SFSocial site styles

   Two surfaces share this stylesheet:

     - the landing page, which is a DECK: full-viewport panels, roughly one
       gesture per panel (see the DECK section below, and site.js)
     - the legal documents, which are ordinary long-form pages

   Everything above the DECK heading is shared. Nothing in the deck section
   applies unless <html data-page="landing"> is set, so a legal page can never
   inherit full-screen panels by accident.

   Design tokens are lifted directly from the Flutter app so the site and the
   product read as one thing:

     app/sfdating/lib/app/theme/app_colors.dart      colour tokens
     app/sfdating/lib/app/theme/app_gradients.dart   brand / hero / glow
     app/sfdating/lib/app/theme/app_text_styles.dart Plus Jakarta Sans + scale
     app/sfdating/lib/app/theme/app_theme.dart       radius 18 / 16, elevation 0

   Two deliberate deviations from the app, both for contrast. Documented at the
   token so nobody "fixes" them back:
     - CTA gradient is deepened in light mode. The app's #F03E3E carries white
       text at 3.81:1, under the 4.5:1 floor for the button's label size.
     - CTA ink flips to near-black in dark mode, where the light red gradient
       cannot carry white text at all (2.80:1).
============================================================================= */

/* ---------------------------------------------------------------- tokens -- */

:root {
  /* Brand, straight from AppColors */
  --brand-red: #f03e3e;
  --brand-red-light: #ff6b6b;
  --brand-red-dark: #c92a2a;
  --brand-coral: #ff8787;
  --brand-peach: #ffc4a3;
  --brand-blush: #ffa8b4;

  /* Light neutrals */
  --bg: #faf7f6;
  --surface: #ffffff;
  --surface-alt: #f5eeed;
  --outline: #eee4e2;
  --text: #211a1a;
  --text-2: #756b6b;
  --text-3: #a39897;

  /* Semantic */
  --success: #36b37e;
  --warning: #f5a623;
  --danger: #e5484d;

  /* Brand-as-text. NOT --brand-red: that is 3.81:1 on white and fails AA for
     body text. This is 5.37:1. */
  --accent-ink: #c92a2a;

  /* Semantic-as-text, for the same reason and by the same rule. --danger is
     3.67:1 on --bg and --success is 2.49:1, so neither may carry a form error
     or a success line; these are 5.67:1 and 5.02:1. The raw pair above stays
     for borders, dots and fills, which have a 3:1 floor, not 4.5:1. */
  --danger-ink: #c02026;
  --success-ink: #0f7a51;

  /* CTA. Deepened so white passes AA across the whole sweep: 5.37:1 -> 4.50:1 */
  --cta-from: #c92a2a;
  --cta-to: #dd3535;
  --cta-ink: #ffffff;

  /* Decorative brand gradient, matches AppGradients.brand exactly. Only ever
     used behind large display type or as a non-text surface. */
  --grad-brand: linear-gradient(135deg, #f03e3e, #ff6b6b);

  /* Same brand read, but safe AS TEXT. The real brand gradient tops out at
     #ff6b6b, which is 2.64:1 on the hero background and fails even the 3:1
     large-text floor. This one is 5.37:1 -> 4.23:1. */
  --grad-headline: linear-gradient(120deg, #c92a2a, #e8443f);
  --grad-hero: linear-gradient(180deg, #fff6f5, #fff0ee, #fff7f3);
  --grad-glow: linear-gradient(135deg, #ff8787, #ffc4a3);

  /* Tints are derived, never hardcoded, so a future theme carries them. */
  --tint-1: color-mix(in srgb, var(--brand-red) 6%, transparent);
  --tint-2: color-mix(in srgb, var(--brand-red) 12%, transparent);
  --tint-line: color-mix(in srgb, var(--brand-red) 28%, var(--outline));

  --shadow-sm: 0 1px 2px rgba(33, 26, 26, 0.04), 0 2px 8px rgba(33, 26, 26, 0.04);
  --shadow-md: 0 2px 4px rgba(33, 26, 26, 0.04), 0 12px 32px rgba(33, 26, 26, 0.07);
  --shadow-lg: 0 4px 8px rgba(33, 26, 26, 0.05), 0 24px 64px rgba(33, 26, 26, 0.1);

  /* Shape, from app_theme.dart. Every corner on the page comes from these
     four values; a one-off radius reads as a mistake next to the app. */
  --r-lg: 18px;
  --r-md: 16px;
  --r-sm: 12px;
  --r-pill: 999px;

  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 24px;
  --s-6: 32px;
  --s-7: 48px;
  --s-8: 64px;
  --s-9: 96px;
  --s-10: 128px;

  --maxw: 1180px;
  --maxw-prose: 74ch;

  --nav-h: 60px;

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --ease-emph: cubic-bezier(0.16, 0.84, 0.28, 1);
  --dur: 220ms;

  /* Motion budget for the deck. Entrances are unhurried, exits are quick: the
     leaving panel is already sliding away, and matching its exit to the
     entrance makes the next panel feel late. */
  --dur-enter: 560ms;
  --dur-exit: 200ms;
  --stagger: 62ms;
}

@media (min-width: 900px) {
  :root {
    --nav-h: 72px;
  }
}

:root[data-theme="dark"],
html:not([data-theme="light"]) {
  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;

    --bg: #121013;
    --surface: #1c181a;
    --surface-alt: #272123;
    --outline: #38302f;
    --text: #f7f2f1;
    --text-2: #b8aead;
    --text-3: #7c7170;

    --accent-ink: #ff8787;
    --danger-ink: #ff9b9b;
    --success-ink: #4fd6a0;

    /* Dark mode inverts the CTA: the app's light-red gradient carries dark ink,
       not white. White on #ff6b6b is 2.80:1. */
    --cta-from: #ff6b6b;
    --cta-to: #ff8787;
    --cta-ink: #1a1214;

    --grad-brand: linear-gradient(135deg, #ff6b6b, #ff8787);
    --grad-headline: linear-gradient(120deg, #ff8787, #ffc4a3);
    --grad-hero: linear-gradient(180deg, #131012, #181315, #1c1618);
    --grad-glow: linear-gradient(135deg, #f03e3e, #ffa8b4);

    --tint-1: color-mix(in srgb, var(--brand-red) 10%, transparent);
    --tint-2: color-mix(in srgb, var(--brand-red) 18%, transparent);

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3), 0 2px 8px rgba(0, 0, 0, 0.24);
    --shadow-md: 0 2px 4px rgba(0, 0, 0, 0.3), 0 12px 32px rgba(0, 0, 0, 0.36);
    --shadow-lg: 0 4px 8px rgba(0, 0, 0, 0.32), 0 24px 64px rgba(0, 0, 0, 0.46);
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;

  --bg: #121013;
  --surface: #1c181a;
  --surface-alt: #272123;
  --outline: #38302f;
  --text: #f7f2f1;
  --text-2: #b8aead;
  --text-3: #7c7170;

  --accent-ink: #ff8787;
  --danger-ink: #ff9b9b;
  --success-ink: #4fd6a0;
  --cta-from: #ff6b6b;
  --cta-to: #ff8787;
  --cta-ink: #1a1214;

  --grad-brand: linear-gradient(135deg, #ff6b6b, #ff8787);
  --grad-headline: linear-gradient(120deg, #ff8787, #ffc4a3);
  --grad-hero: linear-gradient(180deg, #131012, #181315, #1c1618);
  --grad-glow: linear-gradient(135deg, #f03e3e, #ffa8b4);

  --tint-1: color-mix(in srgb, var(--brand-red) 10%, transparent);
  --tint-2: color-mix(in srgb, var(--brand-red) 18%, transparent);

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3), 0 2px 8px rgba(0, 0, 0, 0.24);
  --shadow-md: 0 2px 4px rgba(0, 0, 0, 0.3), 0 12px 32px rgba(0, 0, 0, 0.36);
  --shadow-lg: 0 4px 8px rgba(0, 0, 0, 0.32), 0 24px 64px rgba(0, 0, 0, 0.46);
}

/* ------------------------------------------------------------------ base -- */

*,
*::before,
*::after {
  box-sizing: border-box;
}

* {
  margin: 0;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--nav-h) + 16px);
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: "Plus Jakarta Sans", ui-sans-serif, system-ui, -apple-system,
    "Segoe UI", Roboto, sans-serif;
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

img,
svg {
  display: block;
  max-width: 100%;
}

a {
  color: var(--accent-ink);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

a:hover {
  text-decoration-thickness: 2px;
}

:focus-visible {
  outline: 2px solid var(--accent-ink);
  outline-offset: 3px;
  border-radius: 4px;
}

::selection {
  background: var(--brand-coral);
  color: #211a1a;
}

.skip-link {
  position: fixed;
  left: -9999px;
  top: 0;
  z-index: 200;
  padding: var(--s-3) var(--s-5);
  background: var(--surface);
  color: var(--text);
  border-radius: 0 0 var(--r-md) 0;
  box-shadow: var(--shadow-md);
  font-weight: 600;
}

.skip-link:focus {
  left: 0;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* `hidden` has to beat any author `display` rule. The browser's own
   [hidden] { display: none } sits in the UA stylesheet, which every author
   declaration outranks - so a component that sets `display: flex` on a class
   silently un-hides itself, and an empty box (or in the case of .field__error,
   a bare warning dot with no text) is painted on a page where nothing is
   wrong. Cheaper to state once here than to remember at every component. */
[hidden] {
  display: none !important;
}

/* --------------------------------------------------------------- type ----- */

h1,
h2,
h3,
h4 {
  color: var(--text);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.15;
  text-wrap: balance;
}

p {
  text-wrap: pretty;
}

/* Fluid type, anchored to the app's own scale at the endpoints (displayLarge
   44 / displayMedium 36 / displaySmall 30 / headlineSmall 22).

   Every size is clamped against BOTH axes with min(vw, vh). A deck panel has
   to fit the viewport's height as much as its width, and 1280x620 is a
   completely different constraint from 1280x1024. Sizing on width alone is
   what makes full-screen sections overflow on short laptops. */
h1 {
  font-size: clamp(2.05rem, min(5.2vw, 7.2vh), 3.9rem);
  font-weight: 800;
  letter-spacing: -0.035em;
  line-height: 1.05;
}

h2 {
  font-size: clamp(1.55rem, min(3.5vw, 5vh), 2.55rem);
  letter-spacing: -0.03em;
  line-height: 1.1;
}

h3 {
  font-size: clamp(1.02rem, min(1.5vw, 2.3vh), 1.25rem);
  line-height: 1.28;
}

.lede {
  font-size: clamp(0.98rem, min(1.35vw, 2.05vh), 1.19rem);
  line-height: 1.58;
  color: var(--text-2);
  max-width: 56ch;
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  font-size: clamp(0.7rem, 1.4vh, 0.8125rem);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent-ink);
}

/* The section counter. An editorial device that also does real work in a deck:
   it says where you are without needing a second navigation element. */
.eyebrow__num {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  color: var(--text-3);
  font-variant-numeric: tabular-nums;
}

.eyebrow__num::after {
  content: "";
  width: clamp(14px, 2.2vw, 26px);
  height: 1px;
  background: currentColor;
  opacity: 0.55;
}

/* Gradient headline text. Uses --grad-headline (contrast-safe), never
   --grad-brand. Falls back to a solid accent where background-clip is
   unsupported, so the text is never invisible. */
.grad-text {
  color: var(--accent-ink);
}

@supports (-webkit-background-clip: text) or (background-clip: text) {
  .grad-text {
    background-image: var(--grad-headline);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }
}

/* ------------------------------------------------------------- layout ----- */

.wrap {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--s-5);
}

@media (min-width: 768px) {
  .wrap {
    padding-inline: var(--s-6);
  }
}

/* ------------------------------------------------------------ buttons ----- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: 48px;
  padding: var(--s-3) var(--s-6);
  border: 1px solid transparent;
  border-radius: var(--r-lg);
  font: inherit;
  font-size: 0.96875rem;
  font-weight: 600;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
    background-color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

.btn:active {
  transform: translateY(1px);
}

.btn--primary {
  background-image: linear-gradient(135deg, var(--cta-from), var(--cta-to));
  color: var(--cta-ink);
  box-shadow: var(--shadow-sm);
}

.btn--primary:hover {
  box-shadow: var(--shadow-md);
  transform: translateY(-1px);
}

.btn--primary:active {
  transform: translateY(0);
}

.btn--secondary {
  background: var(--surface);
  color: var(--text);
  border-color: var(--outline);
  box-shadow: var(--shadow-sm);
}

.btn--secondary:hover {
  border-color: var(--text-3);
  box-shadow: var(--shadow-md);
  transform: translateY(-1px);
}

.btn--lg {
  min-height: 54px;
  font-size: 1.03125rem;
  padding-inline: var(--s-7);
}

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
}

.icon-btn {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: 1px solid var(--outline);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}

.icon-btn:hover {
  background: var(--surface-alt);
  border-color: var(--text-3);
}

.icon-btn svg {
  width: 19px;
  height: 19px;
}

/* --------------------------------------------------------------- badge ---- */

.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: 6px var(--s-3);
  border-radius: var(--r-pill);
  border: 1px solid var(--outline);
  background: var(--surface);
  color: var(--text-2);
  font-size: clamp(0.75rem, 1.5vh, 0.8125rem);
  font-weight: 600;
}

.badge svg {
  width: 14px;
  height: 14px;
}

.badge--dev {
  border-color: color-mix(in srgb, var(--warning) 42%, transparent);
  background: color-mix(in srgb, var(--warning) 12%, var(--surface));
  color: var(--text);
}

.badge--dot::before {
  content: "";
  width: 7px;
  height: 7px;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--warning);
}

/* ---------------------------------------------------------------- nav ----- */

.nav {
  position: sticky;
  top: 0;
  z-index: 100;
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: blur(14px) saturate(1.4);
  -webkit-backdrop-filter: blur(14px) saturate(1.4);
  border-bottom: 1px solid transparent;
  transition: border-color var(--dur) var(--ease),
    background-color var(--dur) var(--ease);
}

.nav[data-scrolled="true"] {
  border-bottom-color: var(--outline);
  background: color-mix(in srgb, var(--bg) 94%, transparent);
}

.nav__inner {
  display: flex;
  align-items: center;
  gap: var(--s-5);
  min-height: var(--nav-h);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  color: var(--text);
  text-decoration: none;
  font-weight: 800;
  font-size: 1.0625rem;
  letter-spacing: -0.02em;
  flex-shrink: 0;
}

/* The real app icon, not a stand-in glyph: the site and the launcher icon are
   the same mark. */
.brand__mark {
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  object-fit: contain;
}

.nav__links {
  display: none;
  align-items: center;
  gap: 2px;
  margin-inline-start: auto;
}

.nav__links a {
  position: relative;
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-sm);
  color: var(--text-2);
  text-decoration: none;
  font-size: 0.9375rem;
  font-weight: 600;
  transition: color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}

.nav__links a:hover {
  color: var(--text);
  background: var(--surface-alt);
}

.nav__links a[aria-current="true"] {
  color: var(--text);
}

/* The active-section marker. A 2px underline rather than a filled pill: it
   marks position without competing with the CTA next to it. */
.nav__links a[aria-current="true"]::after {
  content: "";
  position: absolute;
  left: var(--s-3);
  right: var(--s-3);
  bottom: 2px;
  height: 2px;
  border-radius: 2px;
  background: var(--accent-ink);
}

.nav__actions {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin-inline-start: auto;
}

@media (min-width: 960px) {
  .nav__links {
    display: flex;
  }
  .nav__actions {
    margin-inline-start: 0;
  }
}

.nav__cta {
  display: none;
}

@media (min-width: 560px) {
  .nav__cta {
    display: inline-flex;
  }
}

.nav__burger {
  display: grid;
}

@media (min-width: 960px) {
  .nav__burger {
    display: none;
  }
}

.theme-toggle [data-theme-icon="dark"] {
  display: none;
}

:root[data-theme="dark"] .theme-toggle [data-theme-icon="dark"] {
  display: block;
}

:root[data-theme="dark"] .theme-toggle [data-theme-icon="light"] {
  display: none;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-toggle [data-theme-icon="dark"] {
    display: block;
  }
  :root:not([data-theme="light"]) .theme-toggle [data-theme-icon="light"] {
    display: none;
  }
}

/* Mobile drawer */
.mobile-menu {
  display: none;
  border-top: 1px solid var(--outline);
  background: var(--bg);
  padding: var(--s-4) 0 var(--s-6);
  max-height: calc(100dvh - var(--nav-h));
  overflow-y: auto;
}

.mobile-menu[data-open="true"] {
  display: block;
  box-shadow: var(--shadow-md);
}

@media (min-width: 960px) {
  .mobile-menu[data-open="true"] {
    display: none;
  }
}

.mobile-menu nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.mobile-menu a {
  padding: var(--s-3);
  border-radius: var(--r-sm);
  color: var(--text);
  text-decoration: none;
  font-weight: 600;
}

.mobile-menu a:hover {
  background: var(--surface-alt);
}

.mobile-menu hr {
  margin: var(--s-3) var(--s-3) var(--s-2);
  border: 0;
  border-top: 1px solid var(--outline);
}

.mobile-menu .mobile-menu__legal {
  color: var(--text-2);
  font-weight: 500;
  font-size: 0.9375rem;
}

.mobile-menu .btn {
  margin-top: var(--s-4);
}

/* ============================== DECK ====================================== */
/* Everything below is scoped to the landing page. The legal documents share
   this stylesheet and must keep ordinary document scrolling. */

html[data-page="landing"],
html[data-page="landing"] body {
  height: 100%;
  overflow: hidden;
}

/* The header overlays the deck rather than sitting above it. Sticky is the
   right answer on a document page, but here the body does not scroll, so a
   sticky header stays in flow, pushes the deck down by its own height, and the
   bottom of every panel is then clipped by the body's hidden overflow. Each
   panel already reserves --nav-h at the top for it. */
html[data-page="landing"] .nav {
  position: fixed;
  inset-inline: 0;
  top: 0;
}

/* The scroller is this element, not the document.

   Why not snap the document itself: on mobile the URL bar collapses while the
   document scrolls, the viewport height changes mid-gesture, and every snap
   point below shifts underneath the user. A nested scroller never triggers
   that collapse, so 100dvh stays constant for the whole session and panels
   cannot drift. The cost is the ~60px the URL bar keeps; the benefit is that
   "one gesture, one panel" is actually true on a phone. */
.deck {
  height: 100dvh;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior-y: contain;
  scroll-snap-type: y mandatory;
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.deck::-webkit-scrollbar {
  display: none;
}

/* Snapping is suspended while site.js animates between panels, then restored,
   at which point the browser snaps to the position just landed on. Without
   this, mandatory snapping competes with the animation frame by frame. */
.deck[data-animating] {
  scroll-snap-type: none;
}

/* Fallback for browsers without dvh (pre-2022 Safari): svh/dvh resolve to the
   same thing on desktop, and vh is close enough as a last resort. */
@supports not (height: 100dvh) {
  .deck {
    height: 100vh;
  }
}

.panel {
  --panel-bg: var(--bg);
  --panel-pad: clamp(18px, 3.2vh, 44px);

  position: relative;
  min-height: 100%;
  display: grid;
  align-content: center;
  background: var(--panel-bg);
  scroll-snap-align: start;

  /* Stops a fast flick from crossing several panels at once. */
  scroll-snap-stop: always;

  padding-block: calc(var(--nav-h) + var(--panel-pad)) var(--panel-pad);
}

.panel--alt {
  --panel-bg: var(--surface-alt);
}

.panel__inner {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--s-5);
}

@media (min-width: 768px) {
  .panel__inner {
    padding-inline: var(--s-6);
  }
}

/* One ambient wash per panel, sized in vmax so it scales with the viewport
   rather than sitting as a fixed blob on a large monitor. Deliberately at
   most one per panel: the old design had two per section and they read as
   decoration for its own sake. */
.panel__ambient {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
}

/* A radial gradient rather than a blurred circle. Same look, but a 90px blur
   on an 800px element is one of the most expensive things a phone GPU can be
   asked to composite, and clipping it against the panel leaves a visible
   straight edge where the blur is cut off. This has neither problem. */
.panel__ambient::before {
  content: "";
  position: absolute;
  top: -34%;
  right: -18%;
  width: min(70vmax, 900px);
  height: min(70vmax, 900px);
  background: radial-gradient(
    closest-side,
    color-mix(in srgb, var(--brand-coral) 62%, transparent),
    color-mix(in srgb, var(--brand-peach) 26%, transparent) 58%,
    transparent 100%
  );
  opacity: 0.5;
  transform: scale(0.92);
  transition: opacity 900ms var(--ease), transform 1200ms var(--ease);
}

.panel__ambient--left::before {
  right: auto;
  left: -24%;
  top: auto;
  bottom: -38%;
}

:root[data-theme="dark"] .panel__ambient::before {
  opacity: 0.24;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .panel__ambient::before {
    opacity: 0.24;
  }
}

.panel__body {
  position: relative;
  z-index: 1;
}

/* Panel heading block, shared by every panel that has one. */
.panel-head {
  max-width: 60ch;
  margin-bottom: clamp(var(--s-5), 3.4vh, var(--s-8));
}

.panel-head h2 {
  margin-top: clamp(var(--s-2), 1.4vh, var(--s-4));
}

.panel-head .lede {
  margin-top: clamp(var(--s-3), 1.6vh, var(--s-4));
}

/* ------------------------------------------------------------ rail -------- */

/* Section rail. Hidden below 1100px wide or 600px tall, where it would either
   collide with content or be too small to hit; the header progress bar covers
   those cases instead. */
.rail {
  display: none;
}

@media (min-width: 1100px) and (min-height: 600px) {
  .rail {
    position: fixed;
    z-index: 90;
    right: clamp(14px, 1.8vw, 30px);
    top: 50%;
    transform: translateY(-50%);
    display: grid;
    gap: 2px;
  }
}

.rail__item {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s-3);
  height: 26px;
  padding: 0 var(--s-2) 0 var(--s-4);
  border: 0;
  background: none;
  color: var(--text-3);
  font: inherit;
  font-size: 0.75rem;
  font-weight: 600;
  cursor: pointer;
  border-radius: var(--r-pill);
}

.rail__label {
  opacity: 0;
  transform: translateX(6px);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
  white-space: nowrap;
  pointer-events: none;
}

.rail__dot {
  position: relative;
  width: 8px;
  height: 8px;
  flex-shrink: 0;
  border-radius: var(--r-pill);
  background: var(--text-3);
  opacity: 0.5;
  transition: height var(--dur) var(--ease-emph), opacity var(--dur) var(--ease),
    background-color var(--dur) var(--ease);
}

.rail__item:hover .rail__label,
.rail__item:focus-visible .rail__label {
  opacity: 1;
  transform: none;
  color: var(--text);
}

.rail__item:hover .rail__dot {
  opacity: 1;
}

.rail__item[aria-current="true"] .rail__dot {
  height: 22px;
  opacity: 1;
  background: var(--accent-ink);
}

/* Header progress. The rail's counterpart wherever the rail cannot fit. */
.nav__progress {
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 2px;
  overflow: hidden;
}

@media (min-width: 1100px) and (min-height: 600px) {
  .nav__progress {
    display: none;
  }
}

.nav__progress span {
  display: block;
  height: 100%;
  background-image: var(--grad-brand);
  transform: scaleX(var(--progress, 0));
  transform-origin: left center;
  transition: transform 420ms var(--ease-emph);
}

/* -------------------------------------------------------- panel: hero ----- */

.hero {
  display: grid;
  gap: clamp(var(--s-5), 4vh, var(--s-8));
  align-items: center;
}

@media (min-width: 1024px) {
  .hero {
    grid-template-columns: minmax(0, 1.04fr) minmax(0, 0.96fr);
    gap: clamp(var(--s-6), 4vw, var(--s-9));
  }
}

.hero h1 {
  margin-top: clamp(var(--s-3), 2vh, var(--s-5));
}

.hero .lede {
  margin-top: clamp(var(--s-3), 2vh, var(--s-5));
  max-width: 46ch;
}

.hero__cta {
  margin-top: clamp(var(--s-4), 3vh, var(--s-7));
}

.hero__note {
  margin-top: clamp(var(--s-3), 1.8vh, var(--s-4));
  font-size: 0.84375rem;
  color: var(--text-3);
}

.hero__points {
  display: none;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-5);
  margin-top: clamp(var(--s-5), 3.4vh, var(--s-7));
  padding-top: clamp(var(--s-4), 2.6vh, var(--s-6));
  border-top: 1px solid var(--outline);
  list-style: none;
  padding-inline: 0;
}

/* The three proof points repeat the note above them, so they only appear where
   there is genuine room. On a phone they are the first thing to go. */
@media (min-width: 1024px) and (min-height: 700px) {
  .hero__points {
    display: flex;
  }
}

.hero__points li {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  font-size: 0.90625rem;
  font-weight: 600;
  color: var(--text-2);
}

.hero__points svg {
  width: 17px;
  height: 17px;
  color: var(--success);
  flex-shrink: 0;
}

/* Hero visual: a slice of the real product, not a generic dashboard. It shows
   exactly the thing the copy claims (a public channel, and a private message
   that has to be accepted first). */
.preview {
  position: relative;
  border-radius: var(--r-lg);
  border: 1px solid var(--outline);
  background: var(--surface);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
  container-type: inline-size;
}

@media (max-height: 620px) and (max-width: 1023px) {
  /* Landscape phones: the copy and the CTA matter more than the picture. */
  .preview {
    display: none;
  }
}

.preview__head {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-4) var(--s-5);
  border-bottom: 1px solid var(--outline);
  background: var(--panel-bg);
}

.preview__avatar {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 0.8125rem;
  font-weight: 700;
  color: #fff;
  background-image: var(--grad-brand);
  flex-shrink: 0;
}

.preview__avatar--alt {
  background-image: linear-gradient(135deg, #6b8ff0, #87b6ff);
}

.preview__avatar--hash {
  background: none;
  border: 1px solid var(--outline);
  color: var(--text-2);
}

.preview__title {
  font-weight: 700;
  font-size: 0.9375rem;
  letter-spacing: -0.01em;
}

.preview__sub {
  font-size: 0.8125rem;
  color: var(--text-3);
  display: flex;
  align-items: center;
  gap: 6px;
}

.preview__sub::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--success);
}

.preview__body {
  padding: var(--s-5);
}

.msg {
  display: flex;
  gap: var(--s-3);
}

.msg + .msg {
  margin-top: var(--s-4);
}

.msg__body {
  min-width: 0;
}

.msg__name {
  font-size: 0.8125rem;
  font-weight: 700;
  margin-bottom: 3px;
}

.msg__text {
  display: inline-block;
  padding: var(--s-3) var(--s-4);
  border-radius: var(--r-md);
  background: var(--surface-alt);
  border: 1px solid var(--outline);
  font-size: 0.90625rem;
  color: var(--text-2);
  line-height: 1.5;
}

/* Third message is the first thing dropped on a short viewport. */
@media (max-height: 860px) {
  .msg--extra {
    display: none;
  }
}

.preview__request {
  margin-top: var(--s-5);
  padding: var(--s-4);
  border-radius: var(--r-md);
  border: 1px dashed var(--tint-line);
  background: var(--tint-1);
}

.preview__request p {
  font-size: 0.84375rem;
  color: var(--text-2);
  margin-bottom: var(--s-3);
}

.preview__request strong {
  color: var(--text);
}

.preview__request-actions {
  display: flex;
  gap: var(--s-2);
}

.pill-btn {
  flex: 1;
  display: grid;
  place-items: center;
  min-height: 36px;
  padding: 0 var(--s-4);
  border-radius: var(--r-sm);
  border: 1px solid var(--outline);
  background: var(--surface);
  color: var(--text);
  font-size: 0.84375rem;
  font-weight: 600;
}

.pill-btn--accept {
  background-image: linear-gradient(135deg, var(--cta-from), var(--cta-to));
  color: var(--cta-ink);
  border-color: transparent;
}

/* Scroll hint. Only useful until the visitor has moved once, so site.js drops
   it after the first advance. */
.scroll-hint {
  position: absolute;
  left: 50%;
  bottom: clamp(10px, 2.2vh, 24px);
  transform: translateX(-50%);
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-3);
  border: 0;
  background: none;
  color: var(--text-3);
  font: inherit;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
  opacity: 1;
  transition: opacity 400ms var(--ease), color var(--dur) var(--ease);
  z-index: 2;
}

.scroll-hint:hover {
  color: var(--text-2);
}

.scroll-hint svg {
  width: 16px;
  height: 16px;
  animation: hint 2.4s var(--ease) infinite;
}

.deck[data-moved="true"] .scroll-hint {
  opacity: 0;
  pointer-events: none;
}

/* Touch users already know the gesture, and on a phone the hint costs the
   40px that decides whether the panel fits. */
@media (max-height: 640px), (max-width: 640px) {
  .scroll-hint {
    display: none;
  }
}

@keyframes hint {
  0%,
  60%,
  100% {
    transform: translateY(0);
  }
  30% {
    transform: translateY(3px);
  }
}

/* ------------------------------------------------------- panel: steps ----- */

.steps {
  display: grid;
  gap: 1px;
  background: var(--outline);
  border-block: 1px solid var(--outline);
}

@media (min-width: 860px) {
  .steps {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* A hairline grid rather than three floating cards: fewer boxes, and the rule
   between them does the separating work a border and a shadow used to. */
.step {
  background: var(--panel-bg);
  padding: clamp(var(--s-4), 3vh, var(--s-6)) clamp(var(--s-4), 2vw, var(--s-6));
  display: grid;
  gap: var(--s-3);
  align-content: start;
}

.step__num {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  font-size: 0.8125rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  color: var(--accent-ink);
  font-variant-numeric: tabular-nums;
}

.step__num::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--outline);
}

.step p {
  color: var(--text-2);
  font-size: clamp(0.875rem, 1.75vh, 0.9375rem);
}

@media (max-height: 700px) {
  .step {
    gap: var(--s-2);
  }
  .step p {
    font-size: 0.875rem;
  }
}

/* --------------------------------------------------- panel: comparison ---- */

.compare {
  display: grid;
  gap: var(--s-4);
}

@media (min-width: 820px) {
  .compare {
    grid-template-columns: repeat(2, 1fr);
    gap: var(--s-5);
  }
}

.compare__col {
  padding: clamp(var(--s-4), 3vh, var(--s-6));
  border-radius: var(--r-lg);
  border: 1px solid var(--outline);
}

.compare__col--them {
  background: color-mix(in srgb, var(--text-3) 7%, var(--panel-bg));
}

.compare__col--us {
  background: var(--surface);
  border-color: var(--tint-line);
}

.compare__col h3 {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-bottom: clamp(var(--s-3), 2.2vh, var(--s-5));
  font-size: clamp(0.95rem, 2vh, 1.0625rem);
}

.compare__col ul {
  display: grid;
  gap: clamp(var(--s-3), 1.8vh, var(--s-4));
  list-style: none;
  padding: 0;
}

.compare__col li {
  display: flex;
  gap: var(--s-3);
  font-size: clamp(0.86rem, 1.8vh, 0.9375rem);
  color: var(--text-2);
  line-height: 1.45;
}

.compare__col li svg {
  width: 17px;
  height: 17px;
  flex-shrink: 0;
  margin-top: 2px;
}

.compare__col--them li svg {
  color: var(--text-3);
}

.compare__col--us li svg {
  color: var(--success);
}

/* ---------------------------------------------------- panel: features ----- */

/* Same hairline-grid idea as the steps. Six items, no six shadows. */
.spec {
  display: grid;
  gap: 1px;
  background: var(--outline);
  border: 1px solid var(--outline);
  border-radius: var(--r-lg);
  overflow: hidden;
}

@media (min-width: 620px) {
  .spec {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 980px) {
  .spec {
    grid-template-columns: repeat(3, 1fr);
  }
}

.spec__item {
  background: var(--panel-bg);
  padding: clamp(var(--s-4), 2.6vh, var(--s-5));
  display: grid;
  gap: var(--s-2);
  align-content: start;
}

.spec__item h3 {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  font-size: clamp(0.94rem, 1.9vh, 1rem);
}

.spec__item h3 svg {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  color: var(--accent-ink);
}

.spec__item p {
  font-size: clamp(0.84rem, 1.7vh, 0.90625rem);
  color: var(--text-2);
  line-height: 1.5;
}

/* Below this height the six descriptions stop fitting whole, so the
   elaboration goes and the first sentence stays.

   This rule used to drop `.spec__item p` outright, and that was the bug a
   reviewer hit: a browser window under 680px tall is an ordinary desktop
   window, not an edge case, and it rendered a panel headed "How dating works"
   as six bare titles. "Titles carry the meaning on their own" was never true
   of a title like "You both have to agree", which is the CONSEQUENCE of a
   mechanic and says nothing about the mechanic. */
@media (max-height: 680px) {
  .u-drop-tight {
    display: none;
  }
}

/* Any phone with room for the detail gets it back, which is every phone the
   rule above has not already ruled out: 681px meets `max-height: 680px`
   exactly, so no viewport falls between the two and renders the full-size
   desktop paragraph on a 360px column. This is the whole reason these rules
   are keyed on height and not on "mobile". */
@media (max-width: 619px) {
  .spec__item p {
    display: block;
    font-size: 0.8125rem;
    line-height: 1.45;
  }
}

/* ------------------------------------------------------ panel: safety ----- */

/* Two-column panel: the argument on the left, the evidence on the right.
   Shared by the safety and FAQ panels, which is also what gives the deck a
   rhythm instead of eight variations of the same stack. */
.split {
  display: grid;
  gap: clamp(var(--s-5), 4vh, var(--s-8));
  align-items: center;
}

@media (min-width: 1024px) {
  .split {
    grid-template-columns:
      minmax(0, var(--split-a, 0.85fr))
      minmax(0, var(--split-b, 1.15fr));
    gap: clamp(var(--s-6), 5vw, var(--s-9));
  }

  /* The head is a column here, not a block sitting above one. */
  .split > .panel-head {
    margin-bottom: 0;
  }
}

.safety {
  --split-a: 0.82fr;
  --split-b: 1.18fr;
}

.safety-list {
  display: grid;
  gap: 0;
  list-style: none;
  padding: 0;
  border-top: 1px solid var(--outline);
}

.safety-list li {
  display: flex;
  gap: var(--s-4);
  padding-block: clamp(var(--s-3), 2.2vh, var(--s-5));
  border-bottom: 1px solid var(--outline);
}

.safety-list svg {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  color: var(--accent-ink);
  margin-top: 2px;
}

.safety-list h3 {
  font-size: clamp(0.94rem, 1.9vh, 1rem);
  margin-bottom: 3px;
}

.safety-list p {
  font-size: clamp(0.84rem, 1.7vh, 0.90625rem);
  color: var(--text-2);
  line-height: 1.5;
}

@media (max-height: 720px) {
  .safety-list p {
    font-size: 0.84rem;
    line-height: 1.4;
  }
}

/* ------------------------------------------------------ panel: status ----- */

.status {
  border-radius: var(--r-lg);
  padding: clamp(var(--s-5), 4vh, var(--s-8));
  border: 1px solid var(--outline);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}

.status h2 {
  margin-top: clamp(var(--s-3), 2vh, var(--s-5));
}

.status .lede {
  margin-top: clamp(var(--s-3), 1.8vh, var(--s-4));
}

.status__grid {
  display: grid;
  gap: clamp(var(--s-3), 2.4vh, var(--s-5));
  margin-top: clamp(var(--s-5), 3.4vh, var(--s-7));
  padding-top: clamp(var(--s-4), 3vh, var(--s-6));
  border-top: 1px solid var(--outline);
}

@media (min-width: 780px) {
  .status__grid {
    grid-template-columns: repeat(2, 1fr);
    column-gap: var(--s-7);
  }
}

.status__item {
  display: flex;
  gap: var(--s-3);
  font-size: clamp(0.84rem, 1.7vh, 0.9375rem);
  color: var(--text-2);
  line-height: 1.5;
}

.status__item svg {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  margin-top: 2px;
  color: var(--accent-ink);
}

.status__item strong {
  color: var(--text);
  font-weight: 700;
}

/* --------------------------------------------------------- panel: faq ----- */

/* The question list is much taller than its heading, so the two columns are
   aligned at the top rather than centred on each other: a heading floating at
   the midpoint of a list it introduces reads as a mistake. */
.faq-split {
  --split-a: 0.78fr;
  --split-b: 1.22fr;
}

@media (min-width: 1024px) {
  .faq-split {
    align-items: start;
  }
}

.faq-aside {
  margin-top: clamp(var(--s-4), 3vh, var(--s-6));
  font-size: 0.875rem;
  color: var(--text-3);
}

.faq-aside a {
  font-weight: 600;
}

.faq {
  max-width: 78ch;
  border-top: 1px solid var(--outline);

  /* Lets `block-size: 0 -> auto` below actually interpolate. Scoped here
     rather than on :root because it changes how EVERY intrinsic size
     animates, and this is the only place on the page that wants it. */
  interpolate-size: allow-keywords;
}

.faq details {
  border-bottom: 1px solid var(--outline);
}

.faq summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  padding-block: clamp(var(--s-3), 2.1vh, var(--s-4));
  cursor: pointer;
  font-weight: 600;
  font-size: clamp(0.92rem, 1.9vh, 1rem);
  list-style: none;
}

.faq summary::-webkit-details-marker {
  display: none;
}

.faq summary:hover {
  color: var(--accent-ink);
}

.faq summary::after {
  content: "";
  width: 9px;
  height: 9px;
  flex-shrink: 0;
  border-right: 2px solid var(--text-3);
  border-bottom: 2px solid var(--text-3);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform var(--dur) var(--ease);
}

.faq details[open] summary::after {
  transform: rotate(225deg) translate(-2px, -2px);
}

.faq p {
  padding-bottom: clamp(var(--s-3), 2.1vh, var(--s-4));
  color: var(--text-2);
  font-size: clamp(0.86rem, 1.75vh, 0.9375rem);
  max-width: 68ch;
}

/* The answer slides open and closed instead of appearing and vanishing.
   Same duration and curve as the chevron above it, so one gesture reads as
   one movement rather than two.

   `::details-content` rather than a wrapper element animated by JavaScript,
   and that is the whole reason this is three rules instead of forty.
   `name="faq"` makes these an exclusive accordion: opening one closes its
   sibling, and the browser does that itself. A script would have to fight it
   - re-open the sibling it just closed, animate it shut, then close it again
   - while the browser animates BOTH for free here.

   `content-visibility` needs `allow-discrete`, or the content is hidden the
   instant `open` is removed and the closing animation plays on nothing.

   Where `::details-content` is not supported the selector matches nothing and
   the panel behaves exactly as it did before: no fallback to maintain, and no
   layout that depends on the transition having run. */
.faq details::details-content {
  block-size: 0;
  opacity: 0;
  overflow: hidden;
  transition: block-size var(--dur) var(--ease), opacity var(--dur) var(--ease),
    content-visibility var(--dur) var(--ease);
  transition-behavior: allow-discrete;
}

.faq details[open]::details-content {
  block-size: auto;
  opacity: 1;
}

/* ------------------------------------------------------- panel: close ----- */

/* Closing block centred in the space the footer leaves, rather than resting on
   top of it: a call to action pinned a third of the way up reads as an
   afterthought. */
.panel--close {
  grid-template-rows: minmax(0, 1fr) auto;
  align-content: stretch;
  gap: var(--s-5);
}

.closing {
  text-align: center;
  display: grid;
  align-content: center;
  justify-items: center;
  gap: clamp(var(--s-3), 2vh, var(--s-5));
  padding-block: var(--s-4);
}

.closing__mark {
  width: clamp(48px, 8vh, 68px);
  height: clamp(48px, 8vh, 68px);
}

.closing p {
  color: var(--text-2);
  max-width: 48ch;
  font-size: clamp(0.95rem, 1.9vh, 1.0625rem);
}

.closing .btn-row {
  justify-content: center;
  margin-top: var(--s-2);
}

.closing__note {
  font-size: 0.84375rem;
  color: var(--text-3);
}

.site-foot {
  border-top: 1px solid var(--outline);
  padding-top: clamp(var(--s-4), 2.6vh, var(--s-5));
  display: grid;
  gap: var(--s-4);
}

.site-foot__links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3) var(--s-5);
  align-items: center;
  list-style: none;
  padding: 0;
}

.site-foot a {
  color: var(--text-2);
  text-decoration: none;
  font-size: 0.875rem;
  font-weight: 600;
}

.site-foot a:hover {
  color: var(--text);
  text-decoration: underline;
}

.site-foot__legal {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-5);
  justify-content: space-between;
  font-size: 0.8125rem;
  color: var(--text-3);
}

/* ------------------------------------------------- small viewports -------- */

/* A phone is not a narrow desktop. Every rule below changes the composition
   rather than scaling it down, because the deck's promise is that a panel is
   one complete screen: if the layout only shrinks, the smallest devices end up
   scrolling inside a panel that was supposed to BE the viewport.

   Both axes matter. 390x844 and 375x667 are the same width and completely
   different problems, so these are keyed on height wherever height is the
   constraint. */

@media (max-width: 640px) {
  /* Large buttons exist to be a comfortable desktop target. On a phone they
     only push the fold, and 48px is already above the 44px minimum. */
  .btn--lg {
    min-height: 48px;
    font-size: 0.96875rem;
    padding-inline: var(--s-4);
  }

  .lede {
    font-size: 0.9375rem;
    line-height: 1.5;
  }
}

/* Short phones (SE-class) get a smaller display size than tall ones, which is
   what keeps the headline at four lines instead of six. */
@media (max-width: 520px) {
  h1 {
    font-size: clamp(1.6rem, min(8.4vw, 4.3vh), 2.4rem);
  }

  h2 {
    font-size: clamp(1.35rem, min(6.4vw, 3.6vh), 1.9rem);
  }
}

/* ---- hero ---- */

@media (max-width: 1023px) {
  .preview__head {
    padding: var(--s-3) var(--s-4);
  }

  .preview__body {
    padding: var(--s-4);
  }

  .hero .lede {
    max-width: none;
  }
}

/* One message and the request is the whole point of the picture; the rest of
   the thread is texture only a large screen can afford. Phones never show it;
   tablets keep it while they have the height for it. */
@media (max-width: 640px) {
  .msg--second,
  .msg--extra {
    display: none;
  }

  .preview__request {
    margin-top: var(--s-4);
    padding: var(--s-3);
  }

  .hero__cta {
    margin-top: var(--s-4);
  }

  .hero h1,
  .hero .lede {
    margin-top: var(--s-3);
  }

  .hero__note {
    margin-top: var(--s-3);
  }

  .hero {
    gap: var(--s-4);
  }
}

@media (min-width: 641px) and (max-width: 1023px) and (max-height: 900px) {
  .msg--second,
  .msg--extra {
    display: none;
  }
}

/* Below this there is no honest way to show a product shot AND the offer on
   one screen, so the offer wins. The threshold is height because that is what
   actually runs out: 390x844 keeps the picture, 360x740 does not. */
@media (max-width: 1023px) and (max-height: 780px) {
  .preview {
    display: none;
  }
}

/* ---- steps ---- */

@media (max-width: 859px) {
  .steps {
    border-top: 0;
  }

  /* Number beside the title rather than above it: one line saved per step,
     three steps, and the numbering still reads as a sequence. */
  .step {
    grid-template-columns: 22px minmax(0, 1fr);
    gap: var(--s-1) var(--s-3);
    padding: var(--s-4) 0;
  }

  .step__num {
    grid-area: 1 / 1;
  }

  .step__num::after {
    display: none;
  }

  .step h3 {
    grid-area: 1 / 2;
  }

  .step p {
    grid-area: 2 / 2;
    font-size: 0.875rem;
    line-height: 1.45;
  }
}

/* ---- comparison ---- */

@media (max-width: 819px) {
  .compare__col {
    padding: var(--s-4);
  }

  .compare__col h3 {
    margin-bottom: var(--s-3);
    font-size: 0.9375rem;
  }

  .compare__col ul {
    gap: var(--s-2);
  }

  .compare__col li {
    font-size: 0.86rem;
    line-height: 1.4;
  }

  .compare__col li svg {
    width: 15px;
    height: 15px;
  }
}

/* ---- features ---- */

/* Two compact tiles per row, with the icon stacked above a smaller title.
   Whether the description survives is decided by the two height-keyed rules
   next to `.spec__item p`, NOT here: a `display: none` in this block sits
   later in the file at equal specificity, so it silently beat them and no
   phone ever kept a description. */
@media (max-width: 619px) {
  .spec {
    grid-template-columns: repeat(2, 1fr);
  }

  .spec__item {
    padding: var(--s-4) var(--s-3);
  }

  .spec__item h3 {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--s-2);
    font-size: 0.875rem;
    line-height: 1.3;
  }
}

/* A small phone (SE-class: narrow AND short) is the one place six titles and
   six descriptions genuinely compete for the same pixels. What gives is the
   CHROME - padding, gaps, leading - and not the type size or the sentence,
   because a description a reader cannot make out is no better than the bare
   titles this replaced. Measured: this is what brings the chat and dating
   panels back inside a 375x667 viewport. */
@media (max-width: 619px) and (max-height: 700px) {
  .spec__item {
    padding: var(--s-3);
    gap: 6px;
  }

  .spec__item h3 {
    gap: 6px;
  }

  .spec__item p {
    line-height: 1.4;
  }
}

/* ---- safety ---- */

/* Same trade as the tiles, for the same reason: what goes is the elaboration
   after the first sentence, not the description. "Your dating side stays
   private" is a heading, and a heading is not an explanation. Scoped to the
   list, because at these sizes a `.spec` tile still has room for both. */
@media (max-width: 1023px) and (max-height: 950px) {
  .safety-list li {
    align-items: center;
    padding-block: var(--s-3);
  }

  .safety-list .u-drop-tight {
    display: none;
  }
}

/* ---- status ---- */

@media (max-width: 780px) {
  .status {
    padding: var(--s-5) var(--s-4);
  }

  .status__item {
    font-size: 0.84rem;
    line-height: 1.45;
  }

  .status__grid {
    gap: var(--s-3);
  }
}

/* ---- short viewports of any width ---- */

/* Small laptops in a half-height window, and phones in landscape. Trimming the
   rhythm here is what keeps a panel one screen instead of one and a bit. */
@media (max-height: 720px) {
  .panel-head {
    margin-bottom: var(--s-4);
  }

  .status {
    padding: var(--s-5) var(--s-4);
  }

  .status__grid {
    margin-top: var(--s-4);
    padding-top: var(--s-4);
  }

  .safety-list li {
    padding-block: var(--s-3);
  }
}

/* Marks copy that is an elaboration of the panel below it. On a screen this
   short the panel IS the argument, and the paragraph introducing it is what
   pushes the panel past one screen. */
@media (max-height: 760px) and (max-width: 819px) {
  .u-drop-short {
    display: none;
  }
}

/* The same trade one level down, inside a tile description. Every one of
   those leads with a sentence that says what the feature IS, and this marks
   the sentences after it that only elaborate.

   It exists because neither end of the choice works on its own. The whole
   description six times over does not fit: measured at 360x740, the chat
   panel runs 204px past the viewport and the dating panel 179px. And dropping
   the description entirely, which is what both of these rules used to do,
   left a reader with six titles and no way to tell what any of them meant.
   One sentence fits everywhere this page is measured at, so one sentence is
   what always survives.

   Write the first sentence so it can stand alone, because in a narrow window
   or a short one it is the only sentence there is. */
@media (max-width: 619px) {
  .u-drop-tight {
    display: none;
  }
}

/* Small phones (SE-class) and half-height desktop windows. */
@media (max-height: 700px) {
  .panel {
    --panel-pad: clamp(14px, 2.4vh, 32px);
  }

  .step {
    padding-block: var(--s-2);
  }

  .status__item {
    font-size: 0.8125rem;
  }

  .compare__col {
    padding: var(--s-3);
  }
}

/* The status card carries the most words of any panel, so on the smallest
   phones it is the one that needs the tightest setting. */
@media (max-width: 400px) {
  .step p {
    font-size: 0.84rem;
  }

  .status {
    padding: var(--s-4) var(--s-3);
  }

  .status .lede {
    font-size: 0.875rem;
  }

  .status__item {
    font-size: 0.8125rem;
    line-height: 1.4;
  }

  .status__grid {
    gap: var(--s-3);
    margin-top: var(--s-4);
    padding-top: var(--s-4);
  }
}

/* Landscape phones. Nothing composes well at 390px of height, so the goal is
   only that the layout stays orderly while it scrolls a little. */
@media (max-height: 480px) {
  .panel {
    --panel-pad: 12px;
  }

  .panel-head {
    margin-bottom: var(--s-3);
  }
}

/* ---- close ---- */

@media (max-height: 720px) {
  .closing {
    padding-block: var(--s-4);
  }

  .closing__mark {
    display: none;
  }
}

@media (max-width: 640px) {
  .site-foot__links {
    gap: var(--s-2) var(--s-4);
  }

  .site-foot__legal {
    gap: var(--s-1);
  }
}

/* --------------------------------------------------------------- motion --- */

/* Entrance choreography.

   Scoped to .js (set by the boot script before first paint) so a visitor with
   scripting blocked gets the fully visible page instead of eight blank
   screens. The stagger index comes from --i in the markup, so the whole
   sequence costs no per-element JavaScript. */
.js [data-anim] {
  opacity: 0;
  transform: translate3d(0, 14px, 0);
  transition: opacity var(--dur-exit) var(--ease),
    transform var(--dur-exit) var(--ease);
}

.js .panel[data-state="active"] [data-anim] {
  opacity: 1;
  transform: none;
  transition: opacity var(--dur-enter) var(--ease-emph),
    transform var(--dur-enter) var(--ease-emph);
  transition-delay: calc(var(--i, 0) * var(--stagger));
}

/* The panel's own environment settles slightly after its content, which is
   what makes the arrival read as one movement rather than several. */
.js .panel[data-state="active"] .panel__ambient::before {
  transform: scale(1);
}

@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;
    scroll-behavior: auto !important;
  }

  /* Not covered by the three selectors above. */
  .faq details::details-content {
    transition-duration: 0.01ms !important;
  }

  /* No entrance at all, rather than a fast one: the calm version of a deck is
     a page whose content is simply there when you arrive. */
  .js [data-anim] {
    opacity: 1;
    transform: none;
  }

  /* Snapping stays, but gently, and site.js stops intercepting the wheel. */
  .deck {
    scroll-snap-type: y proximity;
  }

  .scroll-hint svg {
    animation: none;
  }
}

/* ================================ DOCS ==================================== */

.doc-layout {
  display: grid;
  gap: var(--s-7);
  padding-block: var(--s-7) var(--s-9);
  align-items: start;
}

@media (min-width: 1024px) {
  .doc-layout {
    grid-template-columns: 268px minmax(0, 1fr);
    gap: var(--s-9);
  }
}

/* Table of contents */
.toc {
  display: none;
}

@media (min-width: 1024px) {
  .toc {
    display: block;
    position: sticky;
    top: calc(var(--nav-h) + 24px);
    max-height: calc(100vh - var(--nav-h) - 56px);
    overflow-y: auto;
    padding-right: var(--s-3);
  }
}

.toc h2 {
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-3);
  margin-bottom: var(--s-4);
}

.toc ol {
  list-style: none;
  padding: 0;
  display: grid;
  gap: 2px;
  counter-reset: toc;
}

.toc a {
  display: block;
  padding: 7px var(--s-3);
  border-radius: var(--r-sm);
  border-left: 2px solid transparent;
  color: var(--text-2);
  text-decoration: none;
  font-size: 0.875rem;
  line-height: 1.4;
  transition: color var(--dur) var(--ease), background-color var(--dur) var(--ease),
    border-color var(--dur) var(--ease);
}

.toc a:hover {
  background: var(--surface-alt);
  color: var(--text);
}

.toc a[aria-current="true"] {
  color: var(--accent-ink);
  border-left-color: var(--accent-ink);
  background: var(--tint-1);
  font-weight: 600;
}

/* Mobile TOC */
.toc-mobile {
  margin-bottom: var(--s-6);
  border: 1px solid var(--outline);
  border-radius: var(--r-md);
  background: var(--surface);
  overflow: hidden;
}

@media (min-width: 1024px) {
  .toc-mobile {
    display: none;
  }
}

.toc-mobile summary {
  padding: var(--s-4) var(--s-5);
  font-weight: 600;
  cursor: pointer;
  list-style: none;
}

.toc-mobile summary::-webkit-details-marker {
  display: none;
}

.toc-mobile ol {
  list-style: none;
  margin: 0;
  padding: 0 var(--s-3) var(--s-3);
  display: grid;
  gap: 2px;
}

.toc-mobile a {
  display: block;
  padding: var(--s-3);
  border-radius: var(--r-sm);
  color: var(--text-2);
  text-decoration: none;
  font-size: 0.9375rem;
}

.toc-mobile a:hover {
  background: var(--surface-alt);
  color: var(--text);
}

/* Document header */
.doc-head {
  padding-bottom: var(--s-6);
  margin-bottom: var(--s-7);
  border-bottom: 1px solid var(--outline);
}

.doc-head h1 {
  font-size: clamp(2rem, 1.5rem + 2vw, 2.75rem);
  margin-bottom: var(--s-4);
}

.doc-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  align-items: center;
  font-size: 0.875rem;
  color: var(--text-3);
}

.doc-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin-bottom: var(--s-7);
}

.doc-tabs a {
  padding: var(--s-2) var(--s-4);
  border-radius: var(--r-pill);
  border: 1px solid var(--outline);
  background: var(--surface);
  color: var(--text-2);
  text-decoration: none;
  font-size: 0.875rem;
  font-weight: 600;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

.doc-tabs a:hover {
  color: var(--text);
  border-color: var(--text-3);
}

.doc-tabs a[aria-current="page"] {
  background-image: linear-gradient(135deg, var(--cta-from), var(--cta-to));
  color: var(--cta-ink);
  border-color: transparent;
}

/* Prose: the rendered legal markdown */
.prose {
  max-width: var(--maxw-prose);
  font-size: 1rem;
  line-height: 1.7;
  color: var(--text-2);
}

.prose > * + * {
  margin-top: var(--s-5);
}

.prose h2 {
  margin-top: var(--s-8);
  padding-top: var(--s-6);
  border-top: 1px solid var(--outline);
  font-size: clamp(1.375rem, 1.2rem + 0.8vw, 1.625rem);
  color: var(--text);
  scroll-margin-top: calc(var(--nav-h) + 24px);
}

.prose > h2:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

.prose h3 {
  margin-top: var(--s-7);
  font-size: 1.125rem;
  color: var(--text);
  scroll-margin-top: calc(var(--nav-h) + 24px);
}

.prose h4 {
  margin-top: var(--s-6);
  font-size: 1rem;
  color: var(--text);
}

.prose strong {
  color: var(--text);
  font-weight: 700;
}

.prose a {
  color: var(--accent-ink);
  font-weight: 500;
}

.prose ul,
.prose ol {
  padding-left: var(--s-5);
  display: grid;
  gap: var(--s-3);
}

.prose li {
  padding-left: var(--s-1);
}

.prose li::marker {
  color: var(--text-3);
}

.prose hr {
  border: 0;
  border-top: 1px solid var(--outline);
  margin-block: var(--s-7);
}

.prose code {
  padding: 2px 6px;
  border-radius: 6px;
  background: var(--surface-alt);
  border: 1px solid var(--outline);
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-size: 0.875em;
  color: var(--text);
}

.prose pre {
  padding: var(--s-5);
  border-radius: var(--r-md);
  background: var(--surface-alt);
  border: 1px solid var(--outline);
  overflow-x: auto;
}

.prose pre code {
  padding: 0;
  border: 0;
  background: none;
}

.prose blockquote {
  padding: var(--s-4) var(--s-5);
  border-left: 3px solid var(--accent-ink);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  background: var(--surface-alt);
}

.prose blockquote > * + * {
  margin-top: var(--s-3);
}

/* Tables scroll inside their own container, never the page */
.table-scroll {
  overflow-x: auto;
  border: 1px solid var(--outline);
  border-radius: var(--r-md);
  background: var(--surface);
  -webkit-overflow-scrolling: touch;
}

.prose table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9375rem;
  min-width: 460px;
}

.prose th,
.prose td {
  padding: var(--s-4);
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--outline);
}

.prose th {
  background: var(--surface-alt);
  color: var(--text);
  font-weight: 700;
  font-size: 0.875rem;
  white-space: nowrap;
}

.prose tbody tr:last-child td {
  border-bottom: 0;
}

.prose td strong {
  color: var(--text);
}

/* A `| | |` markdown table is a key/value list, not tabular data. It gets no
   column headers, so the first cell becomes the row header instead. */
.prose th[scope="row"] {
  width: 1%;
  white-space: nowrap;
  background: var(--surface-alt);
  border-right: 1px solid var(--outline);
}

.table-scroll--plain table {
  min-width: 0;
}

.doc-foot {
  max-width: var(--maxw-prose);
  margin-top: var(--s-8);
  padding-top: var(--s-6);
  border-top: 1px solid var(--outline);
  font-size: 0.9375rem;
  color: var(--text-3);
}

/* The legal pages keep a plain footer; the landing page's lives in its last
   panel instead. */
.footer {
  border-top: 1px solid var(--outline);
  background: var(--surface-alt);
  padding-block: var(--s-6);
}

.footer__bottom {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4);
  align-items: center;
  justify-content: space-between;
  font-size: 0.875rem;
  color: var(--text-3);
}

.footer a {
  color: var(--text-2);
  font-weight: 600;
  text-decoration: none;
}

.footer a:hover {
  color: var(--text);
  text-decoration: underline;
}

/* ==========================================================================
   CONTENT PAGES  (/pre-register, /appeal, /delete-account)

   The third surface in this stylesheet, after the deck and the legal
   documents. Hand-written HTML in site/pages/, dropped into
   templates/page.html.

   It is deliberately narrower than either: one column, ~760px, no sidebar, no
   full-screen panels. Both pages are tasks rather than reading - file an
   appeal, delete an account - and both are reached by somebody who is already
   annoyed or already leaving. Everything here is in service of "the next thing
   to do is obvious and there is only one of it".

   Nothing below depends on JavaScript. The delete-account form needs it to
   compose the request, and says so in its own <noscript>; the pages themselves
   read fine without it.
   ========================================================================== */

.page {
  /* Narrower than .wrap's 1180px, which this overrides. A task page reads as a
     single column; the measure is the point. */
  max-width: 760px;
  padding-block: clamp(var(--s-7), 8vw, var(--s-9)) var(--s-9);
}

/* /pre-register starts closer to the header: the form is the reason for the
   page, so it should be on screen without a scroll on an ordinary laptop.
   Where :has() is unsupported the page keeps the spacing above. */
.page:has(.prereg) {
  padding-top: clamp(var(--s-5), 4vw, var(--s-7));
}

/* ------------------------------------------------------------- page head -- */

.page-head {
  display: grid;
  gap: var(--s-4);
  padding-bottom: var(--s-7);
}

/* The global h1 is sized against BOTH axes for the deck, where a panel has to
   fit the viewport's height. These pages scroll, so height is not a constraint
   and the vh term only makes the heading jump between devices. */
.page-head h1 {
  font-size: clamp(1.95rem, 5.2vw, 2.75rem);
}

.page-head .eyebrow {
  font-size: 0.8125rem;
}

.page-head .lede {
  font-size: 1.0625rem;
  max-width: 60ch;
}

/* ---------------------------------------------------------------- cards --- */

.card {
  display: grid;
  gap: var(--s-4);
  padding: clamp(var(--s-5), 5vw, var(--s-6));
  border: 1px solid var(--outline);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}

.card h2 {
  font-size: 1.3125rem;
  letter-spacing: -0.02em;
}

.card p {
  color: var(--text-2);
  font-size: 0.96875rem;
}

/* The one thing on the page we want pressed. Tinted rather than outlined: an
   accent border alone reads as a warning next to the callouts below it. */
.card--cta {
  border-color: var(--tint-line);
  background: linear-gradient(180deg, var(--tint-1), transparent), var(--surface);
}

.card__note {
  font-size: 0.875rem;
  color: var(--text-3);
}

.card .btn-row {
  padding-top: var(--s-1);
}

/* The in-app route, as it is actually worded in the app's own settings screen:
   Settings > Account > Delete account. Chips rather than prose because people
   scan for the path and then go looking for it on their own screen. */
.path {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
  font-size: 0.9375rem;
}

.path span {
  padding: 7px var(--s-3);
  border: 1px solid var(--outline);
  border-radius: var(--r-sm);
  background: var(--surface-alt);
  color: var(--text);
  font-weight: 600;
}

.path .path__end {
  border-color: color-mix(in srgb, var(--danger) 40%, transparent);
  background: color-mix(in srgb, var(--danger) 10%, var(--surface));
  color: var(--danger-ink);
}

.path svg {
  width: 15px;
  height: 15px;
  color: var(--text-3);
  flex: none;
}

/* -------------------------------------------------------------- sections -- */

.page-section {
  display: grid;
  gap: var(--s-5);
  padding-top: var(--s-8);
}

.page-section h2 {
  font-size: clamp(1.375rem, 3.4vw, 1.75rem);
}

.page-section__lede {
  color: var(--text-2);
  max-width: 62ch;
  margin-top: calc(var(--s-5) * -1 + var(--s-3));
}

/* Long-form asides inside a section reuse the legal pages' prose rules rather
   than restating them. */
.page-section .prose {
  font-size: 0.96875rem;
}

/* ------------------------------------------------------------- ordered ---- */

/* Numbered steps. A hairline rule down the left instead of a box per step: the
   sequence is the message, and three cards make it look like three choices. */
.numbered {
  display: grid;
  gap: var(--s-5);
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: step;
}

.numbered li {
  position: relative;
  counter-increment: step;
  padding-left: calc(var(--s-7) + var(--s-2));
}

.numbered li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: -2px;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 1px solid var(--tint-line);
  background: var(--tint-1);
  color: var(--accent-ink);
  font-size: 0.875rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

/* The rule joining one step to the next. Decorative, and dropped on the last
   item so the sequence visibly ends. */
.numbered li::after {
  content: "";
  position: absolute;
  left: 17px;
  top: 40px;
  bottom: calc(var(--s-5) * -1);
  width: 1px;
  background: var(--outline);
}

.numbered li:last-child::after {
  display: none;
}

.numbered h3 {
  margin-bottom: var(--s-2);
  font-size: 1.0625rem;
}

.numbered p {
  color: var(--text-2);
  font-size: 0.96875rem;
}

/* ------------------------------------------------------------ checklist --- */

.checklist {
  display: grid;
  gap: var(--s-5);
  margin: 0;
  padding: 0;
  list-style: none;
}

.checklist li {
  display: grid;
  grid-template-columns: 26px 1fr;
  gap: var(--s-4);
  align-items: start;
}

.checklist li > svg {
  width: 26px;
  height: 26px;
  padding: 5px;
  border-radius: 50%;
  background: var(--tint-1);
  color: var(--accent-ink);
}

.checklist h3 {
  margin-bottom: var(--s-2);
  font-size: 1.0625rem;
}

.checklist p {
  color: var(--text-2);
  font-size: 0.96875rem;
}

/* -------------------------------------------------------------- callout --- */

/* The things people miss and then write in about. Icon plus rule, not a
   coloured box: a page of coloured boxes teaches people to skip them. */
.callout {
  display: grid;
  grid-template-columns: 22px 1fr;
  gap: var(--s-4);
  padding: var(--s-5);
  border: 1px solid var(--outline);
  border-left-width: 3px;
  border-radius: var(--r-md);
  background: var(--surface-alt);
}

.callout > svg {
  width: 22px;
  height: 22px;
  color: var(--text-2);
}

.callout p {
  color: var(--text-2);
  font-size: 0.9375rem;
}

.callout strong {
  color: var(--text);
}

.callout--warn {
  border-color: color-mix(in srgb, var(--warning) 38%, var(--outline));
  border-left-color: var(--warning);
  background: color-mix(in srgb, var(--warning) 9%, var(--surface));
}

.callout--warn > svg {
  color: color-mix(in srgb, var(--warning) 80%, var(--text));
}

/* ----------------------------------------------------------------- form --- */

.form {
  display: grid;
  gap: var(--s-6);
  padding: clamp(var(--s-5), 5vw, var(--s-6));
  border: 1px solid var(--outline);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}

.field {
  display: grid;
  gap: var(--s-2);
}

/* A real <label>, always visible. A placeholder disappears the moment somebody
   starts typing, which is exactly when they need to check what was asked. */
.field__label {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s-3);
  color: var(--text);
  font-weight: 700;
  font-size: 0.96875rem;
}

.field__req,
.field__opt {
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.field__req {
  color: var(--accent-ink);
}

.field__opt {
  color: var(--text-3);
}

.field__hint {
  color: var(--text-2);
  font-size: 0.875rem;
  max-width: 58ch;
}

.input {
  width: 100%;
  min-height: 50px;
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--outline);
  border-radius: var(--r-md);
  background: var(--bg);
  color: var(--text);
  font: inherit;
  /* 16px exactly. iOS Safari zooms the whole page in on focus below it, and the
     page never zooms back out. */
  font-size: 1rem;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}

.input:hover {
  border-color: var(--text-3);
}

/* An example of the answer, never the question: the label above the field
   stays visible either way. Lighter than typed text so the two are never
   confused, and opacity pinned because Firefox fades placeholders on its own. */
.input::placeholder {
  color: var(--text-3);
  opacity: 1;
}

.input:focus {
  outline: none;
  border-color: var(--accent-ink);
  box-shadow: 0 0 0 3px var(--tint-2);
}

/* Keyboard users still get the global focus ring on top of the border change;
   the two together are the only reliable signal on a high-contrast display. */
.input:focus-visible {
  outline: 2px solid var(--accent-ink);
  outline-offset: 2px;
}

.textarea {
  min-height: 120px;
  padding-block: var(--s-3);
  line-height: 1.5;
  resize: vertical;
}

.select {
  appearance: none;
  padding-right: var(--s-8);
  /* The chevron is drawn rather than shipped: an inline SVG in a data URI would
     have to carry its own colour and could not follow the theme. */
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 22px) calc(50% + 1px), calc(100% - 16px) calc(50% + 1px);
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
  cursor: pointer;
}

/* Set by site.js alongside aria-invalid, so the state is announced and drawn
   from the same place. */
.input[aria-invalid="true"] {
  border-color: var(--danger);
}

.input[aria-invalid="true"]:focus {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--danger) 22%, transparent);
}

/* Beside the field it belongs to, never collected at the top of the form: an
   error the visitor has to scroll back up to read is an error they retype
   blind. */
.field__error {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  color: var(--danger-ink);
  font-size: 0.875rem;
  font-weight: 600;
}

.field__error::before {
  content: "";
  flex: none;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--danger);
  /* The exclamation is punched out of the dot, so the error does not depend on
     colour alone to be recognisable. */
  -webkit-mask: radial-gradient(circle at 50% 78%, #0000 1.1px, #000 1.2px),
    linear-gradient(#0000 24%, #000 24% 62%, #0000 62%);
  mask: radial-gradient(circle at 50% 78%, #0000 1.1px, #000 1.2px),
    linear-gradient(#0000 24%, #000 24% 62%, #0000 62%);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
}

.check {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--s-3);
  align-items: start;
  /* The whole row is the target, not the 20px box. */
  padding: var(--s-3);
  margin: calc(var(--s-3) * -1);
  border-radius: var(--r-sm);
  color: var(--text-2);
  font-size: 0.9375rem;
  cursor: pointer;
}

.check:hover {
  background: var(--surface-alt);
}

.check__box {
  width: 22px;
  height: 22px;
  margin-top: 1px;
  accent-color: var(--brand-red);
  cursor: pointer;
}

.form__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  align-items: center;
}

.form__actions .btn {
  flex: 1 1 auto;
}

@media (min-width: 520px) {
  .form__actions .btn {
    flex: 0 0 auto;
  }
}

.btn[aria-busy="true"] {
  opacity: 0.72;
  pointer-events: none;
}

/* One line, under the button that caused it. It is a live region, so it is
   read out on change; the colour is the secondary signal, not the only one. */
.form__status {
  display: flex;
  gap: var(--s-3);
  align-items: flex-start;
  padding: var(--s-4);
  border: 1px solid var(--outline);
  border-radius: var(--r-md);
  background: var(--surface-alt);
  color: var(--text-2);
  font-size: 0.9375rem;
}

.form__status strong {
  color: var(--text);
}

.form__status--ok {
  border-color: color-mix(in srgb, var(--success) 40%, transparent);
  background: color-mix(in srgb, var(--success) 9%, var(--surface));
  color: var(--success-ink);
}

.form__status--ok strong {
  color: var(--success-ink);
}

.form__status--error {
  border-color: color-mix(in srgb, var(--danger) 40%, transparent);
  background: color-mix(in srgb, var(--danger) 9%, var(--surface));
  color: var(--danger-ink);
}

.form__status--error strong {
  color: var(--danger-ink);
}

.form__status--warn {
  border-color: color-mix(in srgb, var(--warning) 38%, transparent);
  background: color-mix(in srgb, var(--warning) 9%, var(--surface));
  color: var(--text);
}

/* ------------------------------------------------------ pre-register --- */

/* The /pre-register card. Built from the form primitives above; what is new
   is the order (purpose between the label and the button), the inline
   field-and-button row, the result panel that replaces the form, and the
   example email further down.

   The container repeats .card--cta's look instead of using the class: `.card p`
   (0,1,1) sets colour and size for every paragraph in a card, and the form's
   error line and status line are paragraphs that must keep their own. */

.prereg {
  display: grid;
  padding: clamp(var(--s-5), 5vw, var(--s-6));
  border: 1px solid var(--tint-line);
  border-radius: var(--r-lg);
  background: linear-gradient(180deg, var(--tint-1), transparent), var(--surface);
  box-shadow: var(--shadow-sm);
}

.prereg-form {
  display: grid;
  gap: var(--s-5);
}

/* What the address is for, stated where it cannot be missed: between the
   label and the one button on the page. A tinted strip rather than hint text,
   because this sentence is the agreement, not a tip about formatting. */
.prereg .prereg__purpose {
  display: grid;
  grid-template-columns: 20px minmax(0, 1fr);
  gap: var(--s-3);
  align-items: start;
  margin-block: var(--s-1);
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--tint-line);
  border-radius: var(--r-sm);
  background: var(--tint-1);
  color: var(--text-2);
  font-size: 0.9375rem;
  line-height: 1.5;
}

.prereg__purpose svg {
  width: 20px;
  height: 20px;
  margin-top: 2px;
  color: var(--accent-ink);
}

.prereg__purpose strong {
  color: var(--text);
}

/* Field and button on one line wherever both fit at a readable width, so the
   action sits right beside the thing it acts on. Stacked, full width, on a
   phone. */
.prereg__row {
  display: grid;
  gap: var(--s-3);
}

.prereg__row .input {
  min-height: 54px;
}

@media (min-width: 620px) {
  .prereg__row {
    grid-template-columns: minmax(0, 1fr) auto;
  }
}

.prereg__submit {
  position: relative;
  white-space: nowrap;
}

/* Wide enough for "Notify me at launch" AND for "Adding you...", so the
   button does not change size under the pointer while it works. */
@media (min-width: 620px) {
  .prereg__submit {
    min-width: 14.5rem;
  }
}

.prereg__spinner {
  display: none;
  width: 18px;
  height: 18px;
  flex: none;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: prereg-spin 700ms linear infinite;
}

.prereg__submit[aria-busy="true"] .prereg__spinner {
  display: inline-block;
}

@keyframes prereg-spin {
  to {
    transform: rotate(1turn);
  }
}

.prereg .prereg__note {
  padding-top: var(--s-4);
  border-top: 1px solid var(--outline);
  color: var(--text-2);
  font-size: 0.875rem;
  line-height: 1.55;
}

.prereg__note strong {
  color: var(--text);
}

/* ------------------------------------------------ pre-register: result -- */

.prereg-done {
  display: grid;
  gap: var(--s-4);
  justify-items: start;
}

.js .prereg-done:not([hidden]) {
  animation: prereg-in 380ms var(--ease-emph) both;
}

.prereg-done__icon {
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  border: 1px solid color-mix(in srgb, var(--success) 42%, transparent);
  border-radius: 50%;
  background: color-mix(in srgb, var(--success) 12%, var(--surface));
  color: var(--success-ink);
}

/* Already on the list is good news too, but it is not new news: the brand
   tint instead of the success green, so the two outcomes read differently at
   a glance as well as in words. */
.prereg-done[data-outcome="already"] .prereg-done__icon {
  border-color: var(--tint-line);
  background: var(--tint-1);
  color: var(--accent-ink);
}

.prereg-done__icon svg {
  width: 26px;
  height: 26px;
}

/* The tick draws itself once. Under reduced motion the blanket rule at the
   deck section collapses the duration, and `both` leaves it drawn. */
.js .prereg-done__icon path {
  stroke-dasharray: 24;
  stroke-dashoffset: 24;
  animation: prereg-tick 440ms 140ms var(--ease) both;
}

.prereg-done__text {
  display: grid;
  gap: var(--s-2);
}

.prereg-done h2 {
  font-size: clamp(1.3125rem, 3vw, 1.5rem);
  letter-spacing: -0.02em;
}

/* Focused by the script so the outcome is announced. It is a heading, not a
   control, so it gets no focus ring. */
.prereg-done h2:focus {
  outline: none;
}

.prereg .prereg-done p {
  color: var(--text-2);
  font-size: 1rem;
  max-width: 52ch;
}

.prereg-done strong {
  color: var(--text);
  overflow-wrap: anywhere;
}

@keyframes prereg-in {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes prereg-tick {
  to {
    stroke-dashoffset: 0;
  }
}

/* ------------------------------------------ pre-register: example email -- */

/* The launch email, drawn as an email, addressed live to whatever is in the
   field. A second sheet peeks out behind it so it reads as a message in an
   inbox rather than as one more card on the page. */
.mail-figure {
  display: grid;
  gap: var(--s-4);
  margin: 0;
}

.mail {
  position: relative;
  isolation: isolate;
  max-width: 560px;
  margin-bottom: var(--s-3);
}

.mail::before {
  content: "";
  position: absolute;
  inset: var(--s-3) calc(var(--s-3) * -1) calc(var(--s-3) * -1) var(--s-3);
  z-index: -1;
  border: 1px solid var(--outline);
  border-radius: var(--r-lg);
  background: var(--surface-alt);
}

.mail__head,
.mail__body {
  background: var(--surface);
}

.mail__head {
  display: grid;
  gap: var(--s-2);
  margin: 0;
  padding: var(--s-4) var(--s-5);
  border: 1px solid var(--outline);
  border-bottom-style: dashed;
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  font-size: 0.875rem;
}

.mail__head div {
  display: grid;
  grid-template-columns: 4.5rem minmax(0, 1fr);
  gap: var(--s-3);
  align-items: baseline;
}

.mail__head dt {
  color: var(--text-2);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.mail__head dd {
  min-width: 0;
  margin: 0;
  color: var(--text);
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* The one live value on the page. Marked so the eye finds it when it changes. */
.mail__to {
  padding: 1px var(--s-2);
  margin-left: calc(var(--s-2) * -1);
  border-radius: var(--r-pill);
  background: var(--tint-1);
  color: var(--accent-ink);
}

.mail__body {
  display: grid;
  gap: var(--s-3);
  justify-items: start;
  padding: var(--s-6) var(--s-5);
  border: 1px solid var(--outline);
  border-top: 0;
  border-radius: 0 0 var(--r-lg) var(--r-lg);
  box-shadow: var(--shadow-md);
}

.mail__mark {
  width: 40px;
  height: 40px;
  border-radius: var(--r-sm);
}

/* Three classes, because `.mail-figure .mail__body p` below sets colour and
   size for every paragraph in the email, this one included. */
.mail-figure .mail__body .mail__title {
  margin-top: var(--s-1);
  color: var(--text);
  font-size: 1.25rem;
  font-weight: 800;
  letter-spacing: -0.02em;
}

.mail-figure .mail__body p {
  max-width: 46ch;
  color: var(--text-2);
  font-size: 0.96875rem;
}

.mail__btn {
  display: inline-flex;
  align-items: center;
  min-height: 42px;
  margin-top: var(--s-1);
  padding: 0 var(--s-5);
  border-radius: var(--r-md);
  background-image: linear-gradient(135deg, var(--cta-from), var(--cta-to));
  color: var(--cta-ink);
  font-size: 0.9375rem;
  font-weight: 600;
}

.mail-figure .mail__body .mail__foot {
  margin-top: var(--s-2);
  color: var(--text-2);
  font-size: 0.8125rem;
}

.mail-figure figcaption {
  max-width: 60ch;
  color: var(--text-2);
  font-size: 0.875rem;
}

@media (max-width: 420px) {
  .mail::before {
    inset: var(--s-2) calc(var(--s-2) * -1) calc(var(--s-2) * -1) var(--s-2);
  }

  .mail__head,
  .mail__body {
    padding-inline: var(--s-4);
  }

  .mail__head div {
    grid-template-columns: 3.75rem minmax(0, 1fr);
  }
}

/* ------------------------------------------------------------ page more --- */

/* Where to go instead. Every page here is one somebody landed on with a
   problem, and the wrong page is a likely outcome. */
.page-more {
  display: grid;
  gap: var(--s-3);
  margin-top: var(--s-9);
  padding-top: var(--s-6);
  border-top: 1px solid var(--outline);
}

@media (min-width: 720px) {
  .page-more {
    grid-template-columns: repeat(3, 1fr);
  }
}

.page-more a {
  display: grid;
  gap: 2px;
  align-content: start;
  min-height: 48px;
  padding: var(--s-4);
  border: 1px solid var(--outline);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--text);
  text-decoration: none;
  transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease),
    box-shadow var(--dur) var(--ease);
}

.page-more a:hover {
  border-color: var(--text-3);
  box-shadow: var(--shadow-sm);
  transform: translateY(-1px);
}

.page-more span {
  font-weight: 700;
  font-size: 0.96875rem;
}

.page-more small {
  color: var(--text-2);
  font-size: 0.8125rem;
  line-height: 1.45;
}

@media (prefers-reduced-motion: reduce) {
  .page-more a:hover {
    transform: none;
  }
}

@media print {
  .nav,
  .toc,
  .toc-mobile,
  .doc-tabs,
  .footer,
  .rail,
  .page-more,
  .form__actions,
  .skip-link {
    display: none !important;
  }

  body {
    background: #fff;
    color: #000;
  }

  .prose {
    max-width: none;
  }
}
