/* ============================================================================
   attractor — waitlist landing
   Every colour, size, radius and duration comes from tokens.css.

   One documented deviation: tokens.css exports --font-weight-* as Figma style
   names ("SemiBold", "Light"). Those are not valid CSS font-weight values, so
   weights below are numeric. Everything else is a token.
   ============================================================================ */

/* pink/400. Added to the Figma library as the default colour for brand TEXT
   and ICONS on dark — 6.75:1 on the canvas against pink/500's 5.81:1. It is not
   in tokens.css yet (that export is from 24 Aug), so it is declared here rather
   than hand-edited into a generated file. When the export lands, delete this
   block and point the name at the real token.

   Text and icons only. Fills, strokes, shadows and washes stay on
   --color-brand-on-dark (pink/500). */
:root { --color-brand-on-dark-text: #FF5C95; }

/* Ghost gained a 1px border in the updated Button component, and the icon
   button gained a Tertiary variant. The component tier for both is missing
   from the 24 Aug export, so the four names below alias the semantic tokens
   the Figma variables actually resolve to — same values, no new colours.
   Delete when the export lands and point at component/button/* instead. */
/* Gradient/Brand/Primary gained a third stop and a brighter start since the
   24 Aug export. Figma now binds from #ff3d7b, mid #d4025f, to #ae044b — the
   export still has from #d4025f with no mid, which is why the button rendered
   a shade darker than the component and lost its top-edge lift.

   --gradient-brand-mid is new; --gradient-brand-from is a CORRECTION to a token
   that exists with a stale value. Both go when the export catches up. */
:root {
  /* Button · Variant=Secondary was redefined too: the export has it as a dark
     outlined button (#121012 on a #302b30 border), Figma now binds a light
     filled one — near-white with near-black text and no border. Four more
     CORRECTIONS to tokens that exist with stale values. Same fate as the
     gradient: delete when the export catches up. */
  --component-button-secondary-bg:       #F5F2F3;
  --component-button-secondary-bg-hover: #FFFFFF;
  --component-button-secondary-border:   rgb(0 0 0 / 0);
  --component-button-secondary-fg:       #161315;

  --gradient-brand-from: #FF3D7B;   /* pink/500 — was #d4025f in the export */
  --gradient-brand-mid:  #D4025F;   /* pink/600 — new stop                  */
  /* --gradient-brand-to stays #ae044b, unchanged */

  /* Gradient/Brand/Primary itself, so the padded button and the square icon
     button cannot end up carrying different fills. */
  --fill-brand-primary: linear-gradient(90deg,
    var(--gradient-brand-from) 0%,
    var(--gradient-brand-mid) 50%,
    var(--gradient-brand-to) 100%);
}

/* Toast (Figma 270:1576) binds its geometry as well as its colours; the
   24 Aug export carries only the colours. Four of the five alias tokens that
   already exist, so they are written as aliases rather than as numbers; the
   width is the component's own and has no scale entry. Delete when the export
   lands. */
:root {
  --component-toast-width:     420px;
  --component-toast-radius:    var(--radius-lg);   /* 12 */
  --component-toast-padding-x: var(--space-md);    /* 16 */
  --component-toast-padding-y: var(--space-sm);    /* 12 */
  --component-toast-gap:       var(--space-sm);    /* 12 */
}

/* Feedback › Spinner (Figma 219:90) binds four variables; the 24 Aug export
   carries only the two colours. The stroke, the three sizes and the arc are
   the component's own values, read off the published symbol. Delete when the
   export lands. */
/* Badge · Variant=Brand moved to pink/400 for both its text and its border
   since the 24 Aug export, which still has them at pink/500 (#ff3d7b). Same
   correction as --color-brand-on-dark-text above and for the same reason: 500
   is the stroke pink and drops under contrast as small type. The fill is
   unchanged — Figma and the export agree on #ff3d7b at 20%.

   Scoped to dark rather than :root. Every page here ships data-theme="dark",
   and the light values were not read from Figma, so correcting them would be
   guessing. Delete when the export lands. */
[data-theme="dark"] {
  --component-badge-brand-fg:     #FF5C95;
  --component-badge-brand-border: #FF5C95;
}

:root {
  --component-spinner-stroke:  2px;
  --component-spinner-size-sm: 16px;
  --component-spinner-size-md: 24px;
  --component-spinner-size-lg: 40px;
  --component-spinner-arc:     150deg;
}

:root {
  --component-button-ghost-border:        var(--color-border-interactive);      /* #8a8187 */
  --component-button-ghost-border-hover:  var(--color-border-on-dark-strong);   /* #544d53 */
  --component-button-tertiary-fg:         var(--color-fg-on-dark);              /* #f4f0f2 */
  --component-button-tertiary-bg:         rgb(0 0 0 / 0);                       /* #00000000 */
  --component-button-tertiary-bg-hover:   var(--color-bg-sunken);               /* #191619 */
}

/* ===========================================================================
   The form frame.

   Every container that holds a form wears the same edge: a neutral hairline
   with the brand caught in its top-left corner, over the card fill. Defined
   here as three values rather than three rules, because the containers are
   built differently — the panel fakes its border with padding, the dock and
   the modal use a real transparent border — and only the values should be
   shared, not the construction.

   Fill is the card token at .82 so the field reads through it. Every colour is
   a token; only the geometry is written here. */

:root {
  --frame-fill: rgb(from var(--component-card-bg) r g b / .82);

  --frame-hairline: linear-gradient(150deg,
    var(--gradient-hairline-from) 0%,
    var(--gradient-hairline-to) 44%,
    var(--color-border-glass) 100%);

  --frame-corner: radial-gradient(120% 120% at 0% 0%,
    var(--color-brand-glow) 0%,        /* pink/alpha-30 */
    var(--color-brand-wash) 30%,       /* pink/alpha-20 */
    rgb(from var(--color-brand-on-dark) r g b / 0) 60%);
}

/* For a container with no inner wrapper to hide a padding trick behind: a
   transparent border, the fill clipped to the padding box, and the two edge
   layers painted out to the border box underneath it. */
.u-frame {
  border: var(--border-hairline) solid transparent;
  border-radius: var(--radius-xl);
  background:
    linear-gradient(var(--frame-fill), var(--frame-fill)) padding-box,
    var(--frame-corner) border-box,
    var(--frame-hairline) border-box;
}

@supports (backdrop-filter: blur(1px)) {
  .u-frame { backdrop-filter: blur(20px) saturate(1.3); }
}

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

html, body {
  margin: 0;
  padding: 0;
  background: var(--color-bg-canvas);
}

/* Gradient/Surface/Band top radial, across the whole viewport.

   Measured off the library swatch rather than eyeballed: the plum holds solid
   from the bottom edge up to 57% of the ellipse, then ramps to the surface
   colour at its edge, with the ellipse 60% of the width by 50% of the height
   and centred on the bottom edge.

   `fixed`, so it stays put while the page scrolls — the particle field is
   position: fixed too, and the two have to sit still together or the glow
   slides out from behind the shape it belongs to.

   The particle canvas is created with alpha: true and cleared to 0,0,0,0, so
   this reads through it rather than being hidden by it. */
body {
  /* Softened to a wash. At full strength the band's plum read as a coloured
     floor under the page; here it is the same shape and the same token, just
     carried at alpha so it tints rather than fills. The plateau is gone too —
     a solid centre is what made it a block instead of a glow. */
  background-image: radial-gradient(60% 50% at 50% 100%,
    rgb(from var(--gradient-surface-card-to) r g b / .55) 0%,
    rgb(from var(--gradient-surface-card-to) r g b / .22) 46%,
    rgb(from var(--gradient-surface-card-to) r g b / 0) 100%);
  background-repeat: no-repeat;
  background-attachment: fixed;
}

body {
  color: var(--color-fg-primary);
  font-family: var(--font-family-sans), ui-sans-serif, -apple-system, "Helvetica Neue", Arial, sans-serif;
  font-size: var(--font-size-body);
  line-height: var(--font-leading-body);
  -webkit-font-smoothing: antialiased;
  /* The landing is one screen when one screen is tall enough to hold it, and
     the grid below sizes it that way. When the window is shorter than that,
     the page scrolls rather than clipping — `overflow: hidden` here used to
     leave the footer cut off and unreachable on any window under ~900px tall,
     because hidden still clips even though scrollTo() can move past it.
     The field is position: fixed, so the background stays put either way. */
  overflow-x: hidden;
  overflow-y: auto;
}

.page {
  transition:
    opacity var(--motion-duration-base) var(--motion-ease),
    transform var(--motion-duration-base) var(--motion-ease);
}

::selection { background: var(--color-brand-primary); color: var(--color-brand-on-solid); }

a { color: inherit; text-decoration: none; }

:where(a, button, input, [tabindex]):focus-visible {
  outline: var(--border-focus) solid var(--color-focus-ring);
  outline-offset: var(--space-3xs);
  border-radius: var(--radius-sm);
}

/* ---------------------------------------------------------------- utilities */

.u-mono {
  font-family: var(--font-family-mono), ui-monospace, SFMono-Regular, Menlo, monospace;
  font-variant-numeric: tabular-nums;
}

.u-label {
  font-family: var(--font-family-label), ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--font-size-label);
  line-height: var(--font-leading-label);
  letter-spacing: var(--font-tracking-label);
  font-weight: 500;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}

.u-micro {
  font-family: var(--font-family-label), ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--font-size-micro);
  line-height: var(--font-leading-micro);
  letter-spacing: var(--font-tracking-micro);
  font-weight: 500;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}

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

/* ------------------------------------------------------------------- field */

.field {
  position: fixed;
  inset: 0;
  z-index: 0;
  transition: opacity var(--motion-duration-base) var(--motion-ease);
}


.field__canvas {
  display: block;
  width: 100%;
  height: 100%;
}

/* Two scrims, and they must stay different. They were briefly sharing one rule,
   which meant the same edge gradient was painted twice — roughly 95% black at
   the top of the frame instead of the intended 55%. */

/* The wash adds light, not dark: a warm brand haze across the top of the frame. */
.field__wash {
  background: radial-gradient(120% 74% at 50% -18%,
    var(--gradient-glow-brand-mid) 0%,
    var(--color-brand-wash) 38%,
    var(--gradient-glow-brand-edge) 70%);
  opacity: .34;
}

/* The sink grounds the frame edges so the masthead and footer always have
   something to sit on. The type's own scrim travels with the type —
   see .hero::before. */
.field__sink {
  background: linear-gradient(180deg,
    rgb(from var(--color-bg-canvas) r g b / .55) 0%,
    rgb(from var(--color-bg-canvas) r g b / 0) 20%,
    rgb(from var(--color-bg-canvas) r g b / 0) 76%,
    rgb(from var(--color-bg-canvas) r g b / .62) 100%);
}

/* -------------------------------------------------------------------- page */

.page {
  position: relative;
  z-index: 3;
  min-height: 100svh;
  display: grid;
  grid-template-rows: auto 1fr auto;
}

/* ---------------------------------------------------------------- masthead */

/* The bar owns a layer, at every width rather than only on a phone.

   It has descendants that hang below it — the language menu is an absolutely
   positioned dropdown — and on the portal those were landing underneath the
   steps bar, which claims z-index 20. The menu's own z-index:10 could not save
   it: .lang is position:relative with z-index:auto, so it creates no stacking
   context and the menu competes as a peer of the steps bar, not as part of the
   masthead. The visible symptom was a menu you could see through and click
   past: its first row was above the bar's top edge and worked, its second row
   was not and did nothing.

   25 is the number the sticky mobile rule below already used, and it means the
   same thing here: over the page, under the task modal (30), the toast (40)
   and the completion sheet (41). position:relative rather than a new stacking
   context of its own, so the dropdown still escapes the bar's box. */
.masthead {
  position: relative;
  z-index: 25;
  padding: clamp(18px, 2.2vw, 28px) clamp(20px, 3.4vw, 44px);
}

/* Edge to edge on large desktop — the 1220px content measure is right for
   running text, not for a two-item bar that already has its own side gutter. */
.masthead__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-lg);
}

/* ---------------------------------------------------------------- the logo */

/* The lockup is one shape at several sizes, so every part of it derives from a
   single scale unit. The ratios below are the artwork's own geometry as Figma
   renders it --- a wordmark 398.25 wide, a mark box 180 x 167 beside it and 32
   between them (Design System v1, 49:310) --- not layout decisions, so they
   stay as ratios rather than becoming spacing tokens. Set --logo-h and the
   mark, the gap and the wordmark all follow together. */
.logo {
  /* --logo-h is the wordmark's scale unit. It used to be the mark's height,
     which was the taller of the two; the mark is now shorter than the wordmark
     is wide by a long way, and anchoring the bar's rhythm to the smaller part
     would have moved every other number on it. The DNA card sets --logo-h too
     and draws the wordmark alone, so keeping this the unit that drives the
     wordmark is also what leaves that card exactly where it was.

     The floor is 18, and the 2 above the spacing step is bought rather than
     guessed. The lockup is 5.92 times this unit, so every pixel of it costs
     5.92 of width in a 375px bar. Sign-in is the binding page, not the
     portal: its member button reads "Join the waitlist" at 122.6px against
     "Sign in" and "Sign out" elsewhere, so its nav needs 207.2 --- 122.6 for
     the button, 12 for the gap, 72.6 for the language control.

     Measured there at 375: at 18 the lockup is 106.5, the nav gets 208.5
     against that 207.2, and the language control lands 1.3px inside the edge.
     At 20 the lockup is 118.3 and the bar overflows the page by 10. So 18 fits
     and 20 does not --- but the margin is 1.3px now, where it was 4.8 before
     the mark's box grew. Figma's export carries the symbol's own padding, and
     that padding costs 3.5px of bar at this unit. Trimming the asset to its
     curve would buy it back, at the price of the asset no longer being the
     file Figma exports.
     Measure sign-in, not the portal, before moving this again. */
  --logo-h: clamp(calc(var(--space-md) + var(--space-3xs)), 2.6vw, var(--space-2xl));
  /* The wordmark's width, and the lockup's real measure --- the mark's height
     and the gap are both fractions of it. Declared on the lockup rather than
     inside .wordmark so the two halves cannot end up scaled from different
     numbers. .dna__logo is a .logo as well, so it resolves this against its own
     --logo-h and gets the width it has always had. */
  --logo-wm-w: calc(var(--logo-h) * 399 / 103);
  display: inline-flex;
  align-items: center;
  /* 32 against a wordmark 398.25 wide, and the same 32 in both of Figma's
     variants. It is measured from the mark's box, not from the curve inside it
     --- the asset is the symbol as Figma exports it, padding and all, so the
     box is what sits beside the wordmark. Measured from the curve the two
     variants read 55 and 57, which is one gap seen from two sides of an
     off-centre ink, not two gaps. The box makes that question go away. */
  gap: calc(var(--logo-wm-w) * 32 / 398.25);
  /* The lockup mirrors. It did not before --- the row was pinned to
     direction: ltr so the mark stayed ahead of a wordmark that is Latin and
     reads left to right either way. Figma's AR variant (398:2888) puts the
     mark after the wordmark, so the row follows dir now. Nothing inside the
     wordmark reverses with it: it is a mask, not text. Which end of the bar
     the whole lockup sits at is still the parent's job. */
}

/* An asset rather than a mask, unlike the wordmark beside it. The mark is a
   crossing curve --- pink through teal, lit white where the two pass --- and a
   single-colour mask would throw away the only thing that makes it the mark.

   Vector rather than the raster it used to be. The artwork carries no blur, so
   an SVG is exact rather than approximate, and it takes the raster's baked-in
   black outer glow with it --- that glow was lit for a black tile the mark has
   not sat on for two revisions, and on the bar it lit the page, not the mark. */
.logo__mark {
  display: block;
  /* 167 against a wordmark 398.25 wide. */
  block-size: calc(var(--logo-wm-w) * 167 / 398.25);
  /* auto, and not by omission. The <img> carries width and height attributes
     so the box is reserved before the file arrives, and those attributes set
     the used width unless CSS says otherwise — leaving this out rendered the
     mark at its full intrinsic width with the aspect ratio ignored, because
     both dimensions were already determined. */
  inline-size: auto;
  /* Straight off the file's own viewBox. The box is the symbol's, not the
     curve's: the curve sits at x 25..155 inside it, so the visible mark is
     inset from the start of the lockup by 25 of these units --- about 4px on
     the bar. That inset is Figma's own, and it is why the gap above is 32
     rather than the 55 the eye measures between curve and wordmark.

     180, not 178: the refined symbol is one path where the old one was six and
     its box is two units wider. The curve still sits at x 25..155, so the inset
     and the 32 gap above are unchanged. */
  aspect-ratio: 180 / 167;
}

/* Masked rather than inlined, so the wordmark takes its colour from a token
   instead of carrying a hard-coded fill in the file. */
.wordmark {
  display: block;
  inline-size: var(--logo-wm-w);
  aspect-ratio: 399 / 71;
  background: currentColor;
  color: var(--color-fg-on-dark);
  -webkit-mask: url(../assets/brand/wordmark.svg) no-repeat center / contain;
  mask: url(../assets/brand/wordmark.svg) no-repeat center / contain;
}

.masthead__nav {
  display: flex;
  align-items: center;
  gap: clamp(16px, 2vw, 28px);
}

.masthead__nav { gap: var(--space-sm); }

/* The masthead is always dark, and --component-button-ghost-fg/-bg-hover both
   flip with the theme. Pinned to the invariant on-dark tokens carrying the
   same dark-theme values, so light mode cannot put dark text on a dark bar. */
.masthead__cta { padding-inline: var(--space-md); }

/* Ghost only. Secondary brings its own fill and its own near-black label, and
   both of these would fight it — a dark hover plate under a light button, and
   near-white text on a near-white ground. */
.btn-ghost.masthead__cta { color: var(--color-fg-on-dark); }
.btn-ghost.masthead__cta:hover { background: var(--color-bg-sunken); }

/* Tertiary. The quietest button in the set — no fill and no stroke at rest, so
   it reads as an affordance from where it sits rather than from an outline: a
   close control in a corner, an icon in a bar. The surface only arrives on
   hover. Variant=Tertiary in the library carries no border token at all, which
   is the whole difference between this and Ghost. */
.btn-tertiary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  block-size: var(--size-control-sm);
  padding-inline: var(--space-sm);
  background: var(--component-button-tertiary-bg);
  border: 0;
  border-radius: var(--radius-md);
  cursor: pointer;
  font-family: inherit;
  font-size: var(--font-size-ui-sm);
  line-height: var(--font-leading-ui-sm);
  letter-spacing: var(--font-tracking-ui-sm);
  font-weight: 600;
  color: var(--component-button-tertiary-fg);
  transition:
    background-color var(--motion-duration-instant) var(--motion-ease),
    color var(--motion-duration-instant) var(--motion-ease);
}

.btn-tertiary:hover { background: var(--component-button-tertiary-bg-hover); }

/* Divides the site's own links from the language control. */
.masthead__sep {
  inline-size: var(--border-hairline);
  block-size: var(--size-icon-md);
  background: var(--color-border-on-dark-strong);
  margin-inline: var(--space-2xs);
}

/* ---------------------------------------------------------------- language */

.lang { position: relative; }

/* UI Small, the same step the ghost CTA beside it takes — it was the last
   mono label left in the bar, and at 11px it read as a footnote next to a 13px
   button. The menu below stays mono: the codes there sit in a 2.5ch column
   beside their names, and a proportional face would break that alignment.
   Colour stays one step down from the CTA — this is the secondary control. */
.lang__btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  block-size: var(--size-control-sm);
  padding-inline: var(--space-xs);
  background: none;
  border: 0;
  border-radius: var(--radius-md);
  cursor: pointer;
  font-family: inherit;
  font-size: var(--font-size-ui-sm);
  line-height: var(--font-leading-ui-sm);
  letter-spacing: var(--font-tracking-ui-sm);
  font-weight: 600;
  color: var(--color-fg-on-dark-secondary);
  transition:
    color var(--motion-duration-instant) var(--motion-ease),
    background-color var(--motion-duration-instant) var(--motion-ease);
}

.lang__btn:hover {
  color: var(--color-fg-on-dark);
  background: var(--component-button-ghost-bg-hover);
}

.lang__globe { inline-size: var(--size-icon-sm); block-size: var(--size-icon-sm); }

.lang__chev {
  inline-size: var(--size-icon-sm);
  block-size: var(--size-icon-sm);
  transition: rotate var(--motion-duration-instant) var(--motion-ease);
}

.lang__btn[aria-expanded="true"] .lang__chev { rotate: 180deg; }

.lang__menu {
  position: absolute;
  inset-block-start: calc(100% + var(--space-2xs));
  inset-inline-end: 0;
  z-index: 10;
  margin: 0;
  padding: var(--space-3xs);
  list-style: none;
  min-inline-size: 168px;
  border: var(--border-hairline) solid var(--color-border-on-dark);
  border-radius: var(--radius-md);
  background: var(--color-bg-dark-raised);
  box-shadow: 0 var(--shadow-card-y-hover) var(--shadow-card-blur-hover) var(--color-shadow-card);
}

.lang__menu[hidden] { display: none; }

.lang__item {
  inline-size: 100%;
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-2xs) var(--space-xs);
  background: none;
  border: 0;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-family: inherit;
  text-align: start;
  color: var(--color-fg-on-dark-secondary);
  transition:
    color var(--motion-duration-instant) var(--motion-ease),
    background-color var(--motion-duration-instant) var(--motion-ease);
}

.lang__item:hover { color: var(--color-fg-on-dark); background: var(--component-button-ghost-bg-hover); }

.lang__short { color: var(--color-fg-on-dark-tertiary); inline-size: 2.5ch; }

.lang__name {
  font-size: var(--font-size-ui-sm);
  line-height: var(--font-leading-ui-sm);
}

[aria-selected="true"] .lang__short { color: var(--color-brand-on-dark-text); }
[aria-selected="true"] .lang__name  { color: var(--color-fg-on-dark); }

@media (max-width: 40rem) {
  .masthead__nav .socials { display: none; }
}

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

.hero {
  position: relative;
  isolation: isolate;
  /* The reading-lane pseudo below bleeds 4% past the hero on each side, which
     is 63px at desktop width — enough to give the document a sideways scroll
     and shunt the whole page off its left edge. Clip rather than shrink the
     bleed, so the gradient still runs to the frame edge. */
  overflow-x: clip;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(14px, 1.8vw, 22px);
  padding: clamp(16px, 3vh, 40px) clamp(20px, 3.4vw, 44px);
  text-align: center;
}

/* Top-aligned from tablet up, so the gap over the headline is a number rather
   than whatever is left over.

   The hero is the 1fr row of the page grid and was centring in it, which makes
   the space above the heading equal to the space below the panel and grow with
   the window: 24.8px at 720 tall, 68.9 at 900, 151.3 at 1100. There is no way
   to cap that while still centring — the two gaps are the same free space
   halved — so the content sits at the top and the padding is the gap.

   The leftover height now collects below the panel instead, where it is the
   attractor rather than a hole: the field is the thing that fills it. */
@media (min-width: 769px) {
  .hero {
    justify-content: flex-start;
    /* Not the flat 20px it started as. Top-aligning fixed the gap that grew
       to 151px on a tall window, but it fixed it at the other end: on a large
       monitor the block sat right under the masthead with 300px of nothing
       below it. This sits it down a little, and the clamp is what keeps that
       honest — the floor is the 20px a short laptop needs, where every pixel
       above the headline is a pixel of the form below the fold. */
    padding-block-start: clamp(var(--space-lg), 6vh, var(--space-6xl));
  }
}

/* A well under the words that moves with them. The wings stay bright either
   side, so the figure frames the type rather than running underneath it. */
.hero::before {
  content: "";
  position: absolute;
  inset: -6% -4%;
  z-index: -1;
  /* Softened one step at every stop. This scrim exists to hold contrast under
     the headline, not to mute the field — and with the attractor smaller it
     covers proportionally more of it, so the same values darkened more. */
  background: radial-gradient(46% 46% at 50% 46%,
    rgb(from var(--color-bg-canvas) r g b / .44) 0%,
    rgb(from var(--color-bg-canvas) r g b / .28) 42%,
    rgb(from var(--color-bg-canvas) r g b / .08) 72%,
    rgb(from var(--color-bg-canvas) r g b / 0) 92%);
  pointer-events: none;
}

.hero__title {
  margin: 0;
  /* In ch rather than px so the cap tracks the fluid font size instead of
     biting at some arbitrary pixel width. There is one hero sentence now and
     it is short, so this no longer binds here — it is the guard for the next
     one, and for sign-in, which shares this class.

     `balance` keeps the break even on the sizes that do wrap. */
  max-inline-size: 26ch;
  text-wrap: balance;
  /* Fluid between two real steps of the scale. The leading uses the same vw
     with the ratio folded in, so it tracks the size instead of drifting. */
  font-size: clamp(var(--font-size-h3), 5.4vw, var(--font-size-h1));
  line-height: clamp(var(--font-leading-h3), 5.6vw, var(--font-leading-h1));
  letter-spacing: var(--font-tracking-h1);
  font-weight: 700;
  /* One flat colour. The heading used to carry a gradient clipped to the glyphs
     and swept across on a loop; with the ink solid there is nothing to clip, so
     the padding that used to hold the descenders inside the painted box goes
     with it. The hero is an always-dark band, hence the on-dark step rather
     than the theme-following one. */
  color: var(--color-fg-on-dark);
}

.hero__lead {
  /* Its own stacking context, so the scrim below can sit at z-index -1: under
     the words, above the field. Without it the pseudo would fall behind the
     hero entirely and do nothing. */
  position: relative;
  isolation: isolate;
  margin: 0;
  max-inline-size: 50ch;
  font-size: clamp(var(--font-size-body), 1.7vw, var(--font-size-h5));
  line-height: clamp(var(--font-leading-body), 2.4vw, var(--font-leading-h5));
  letter-spacing: var(--font-tracking-lead);
  font-weight: 300;
  /* The on-dark variant, not the plain fg token — the hero is an always-dark
     band, so a theme-following token would flip to near-black here in light
     mode. */
  color: var(--color-fg-on-dark);
  text-wrap: pretty;
}

/* A dark oval under the lead, on both the waitlist and sign-in.

   The hero already has a scrim, but it is a viewport-sized well centred on the
   panel — broad enough to lift the whole middle and therefore too diffuse to do
   much for one paragraph of 24px light type sitting over the brightest part of
   the field. This one is sized to the text: `closest-side` ties the ellipse to
   the box, so it tracks the paragraph as the copy or the viewport changes
   rather than being a fixed shape the words happen to sit inside.

   Inset out on both axes so the falloff finishes past the text — a scrim whose
   edge lands on a descender is worse than none. */
.hero__lead::before {
  content: "";
  position: absolute;
  inset: -70% -14%;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(closest-side ellipse at 50% 50%,
    rgb(from var(--color-bg-canvas) r g b / .66) 0%,
    rgb(from var(--color-bg-canvas) r g b / .42) 46%,
    rgb(from var(--color-bg-canvas) r g b / 0) 100%);
}

/* ------------------------------------------------------- the hero carousel */
/* The phone's replacement for the lead. Nothing here paints above 768 — see
   the compact block at the foot of this file, which is also where the lead and
   the footer's pillar row are taken out. */

.pitch { display: none; }

.pitch__track {
  display: flex;
  overflow-x: auto;
  overscroll-behavior-inline: contain;
  scroll-snap-type: x mandatory;
  /* The scrollbar is the mechanism, not the affordance — the dots below say
     there is more, and a grey bar under one sentence says nothing. */
  scrollbar-width: none;
}
.pitch__track::-webkit-scrollbar { display: none; }

/* Full width each, so one is centred at a time and the track's height is the
   tallest of the three — the dots then hold still instead of stepping up and
   down as the copy changes length. */
.pitch__slide {
  flex: 0 0 100%;
  scroll-snap-align: center;
  margin: 0;
  padding-inline: var(--space-xs);
  /* The lead's voice, since this is standing in for the lead. */
  font-size: var(--font-size-body);
  line-height: var(--font-leading-body);
  letter-spacing: var(--font-tracking-lead);
  font-weight: 300;
  color: var(--color-fg-on-dark);
  text-wrap: pretty;
}

.pitch__dots {
  display: flex;
  justify-content: center;
  gap: var(--space-2xs);
  /* Pulled up by exactly the dead space under the pill. Each dot is a 44px
     button around a 4px mark, so 20px of empty target sat below the dots and
     was read as air before the form — 48px of it, against 20 above. Taking it
     back here rather than shrinking the button keeps the target at the touch
     minimum and only stops it being counted twice. */
  margin-block-end: calc(-1 * var(--space-lg));
}

/* The button is the touch target and the pill inside it is the mark, so the
   thing you aim at is 44px tall while the thing you see stays 4px. */
.pitch__dot {
  display: grid;
  place-items: center;
  block-size: var(--size-touch-min);
  padding: 0 var(--space-2xs);
  background: none;
  border: 0;
  cursor: pointer;
}

.pitch__pill {
  display: block;
  overflow: hidden;
  inline-size: var(--space-xs);
  block-size: var(--space-2xs);
  border-radius: var(--radius-full);
  /* The on-dark border step, not the glass one. Glass is white at 10%, which
     over the hero composites to about #242324 and reads as 1.27:1 — a dot you
     have to already know is there. This lands at 2.40:1.

     It is also the token that belongs here: glass flips to black at 10% under
     the light theme, and this band does not flip, so on a light page the dots
     would have gone from nearly invisible to invisible. The on-dark family is
     theme-invariant by design. */
  background: var(--color-border-on-dark-strong);
  transition: inline-size var(--motion-duration-base) var(--motion-ease);
}

.pitch__dot[aria-current="true"] .pitch__pill { inline-size: var(--space-2xl); }

/* Reusing the pillars' keyframe: it is scaleX(0) to scaleX(1) and that is
   exactly what this is. Linear for the same reason — it is a clock, and an
   eased bar spends its last third crawling. Origin flipped by hand for RTL,
   because transforms are physical. */
.pitch__fill {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  background: var(--color-brand-on-dark);
  transform: scaleX(0);
  transform-origin: left;
}
[dir="rtl"] .pitch__fill { transform-origin: right; }

.pitch__dot[aria-current="true"] .pitch__fill {
  animation: pillar-fill var(--pitch-dwell, 3000ms) linear forwards;
}

@media (prefers-reduced-motion: reduce) {
  .pitch__pill { transition: none; }
  /* The carousel does not advance on its own under this setting, so the bar
     has nothing to count down. Shown full rather than empty: it marks which
     slide you are on, which is still true. */
  .pitch__dot[aria-current="true"] .pitch__fill { animation: none; transform: scaleX(1); }
}

/* Anything that swaps when the audience changes fades through, so the page
   never jumps between two different lengths of sentence. */
.swap {
  transition:
    opacity var(--motion-duration-instant) var(--motion-ease),
    transform var(--motion-duration-instant) var(--motion-ease);
}
.swap.is-out { opacity: 0; transform: translateY(var(--space-2xs)); }

/* -------------------------------------------------- segmented: the audience */
/* Selection is a raised thumb with a brand stroke, never a brand fill —
   a brand fill would say "act here", and this only reports who you are. */

/* Lives inside the panel now, so it is sized to the panel's width, not the
   hero — full-width with the two options sharing it evenly. */
.audience {
  position: relative;
  display: flex;
  inline-size: 100%;
  padding: var(--space-2xs);
  background: var(--component-segmented-track-bg);
  border: var(--border-hairline) solid var(--component-segmented-track-border);
  border-radius: var(--radius-full);
}

/* Gradient/Surface/Segmented selected — two paints, per the library: the raised
   surface, then a brand wash falling from the top edge. The wash tints the
   active thumb without filling it, because a brand fill would read as a button
   rather than a state.

   The library style is newer than the token export (component updated 25 Aug
   06:40, tokens.css generated 24 Aug), so there is no --gradient-surface-
   segmented-* yet. This is built from --color-brand-wash to the documented
   intent; swap it for the real pair on the next token export. */
.audience__thumb {
  position: absolute;
  inset-block: var(--space-2xs);
  background:
    linear-gradient(180deg,
      var(--color-brand-wash) 0%,
      rgb(from var(--color-brand-primary) r g b / 0) 64%),
    var(--component-segmented-item-bg-selected);
  border: var(--border-hairline) solid var(--component-segmented-item-border-selected);
  border-radius: var(--radius-full);
  /* "Raised", against a track the library calls a recessed surface. */
  box-shadow: 0 var(--shadow-card-y-rest) var(--shadow-card-blur-rest)
              var(--color-shadow-card);
  /* Offset and width, not translate. translate would put the slide on the
     compositor, which is the faster path in principle — but the thumb then
     stops tracking the selection wherever the compositor is throttled, and it
     is out of flow so animating the offset reflows nothing anyway. */
  transition:
    inset-inline-start var(--motion-duration-base) var(--motion-ease),
    inline-size var(--motion-duration-base) var(--motion-ease);
  pointer-events: none;
}

/* The stroke, and only the stroke, takes the side you have chosen. The badge's
   accent border rather than a flat teal: it is theme-aware the same way the
   brand token it replaces is — #25e1e7 on dark, #096e71 on light — and it is
   already the colour of the Business DNA badge, so the two agree.

   Everything else about the thumb is left as the library draws it. The raised
   surface, the wash falling from the top edge and the shadow are what say
   "selected"; the stroke is the only part naming which side. */
[data-audience="business"] .audience__thumb {
  border-color: var(--component-badge-accent-border);
  /* And the wash with it, so the tint agrees with the stroke instead of a
     teal outline sitting on a pink glow.

     Composed from the accent at .2 rather than swapped for --color-accent-wash,
     which the library publishes at .1 — half of --color-brand-wash. Swapping
     the token would have made the teal thumb visibly flatter than the pink
     one, which is the same trap the portal's floor radial set. The pair it
     replaces is theme-invariant, so this one is too.

     The plate underneath is untouched: that is the raised surface the library
     calls Segmented item selected, and it is what makes this a thumb rather
     than a coloured pill. Only the tint over it changes side. */
  background:
    linear-gradient(180deg,
      rgb(from var(--color-accent-on-dark) r g b / .2) 0%,
      rgb(from var(--color-accent-on-dark) r g b / 0) 64%),
    var(--component-segmented-item-bg-selected);
}

.audience__option {
  position: relative;
  z-index: 1;
  flex: 1;
  min-block-size: var(--size-control-md);
  padding-inline: var(--space-sm);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 0;
  border-radius: var(--radius-full);
  cursor: pointer;
  font-family: inherit;
  font-size: var(--font-size-ui-sm);
  line-height: var(--font-leading-ui-sm);
  letter-spacing: var(--font-tracking-ui-sm);
  font-weight: 600;
  color: var(--component-segmented-item-fg);
  white-space: nowrap;
  transition:
    color var(--motion-duration-instant) var(--motion-ease),
    background-color var(--motion-duration-instant) var(--motion-ease);
}

/* The unselected segments take the hover fill the library ships for them. No
   stroke and no wash, so a hovered segment never reads as a second selection —
   the border and the brand wash are what mark the active one. */
.audience__option:not([aria-selected="true"]):hover {
  background: var(--component-segmented-item-bg-hover);
  color: var(--component-segmented-item-fg-selected);
}

.audience__option[aria-selected="true"] {
  color: var(--component-segmented-item-fg-selected);
}

.audience__option:disabled {
  color: var(--component-segmented-item-fg-disabled);
  cursor: not-allowed;
}
.audience__option[aria-selected="true"] { color: var(--component-segmented-item-fg-selected); }

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

.panel {
  /* A little more air than the hero's own gap gives — the panel is a change of
     surface, not the next paragraph. Held here so the headline and lead keep
     their tighter rhythm. */
  margin-block-start: var(--space-sm);
  inline-size: 100%;
  max-inline-size: 424px;
  padding: var(--border-hairline);
  border-radius: var(--radius-xl);

  /* The hairline, with the brand caught in one corner.

     Two layers on the same box, and the order matters: the pink is a radial
     anchored off the top-left, so it is strongest where the corner turns and
     has fallen to nothing by 62% — a corner highlight, not a border that
     happens to be pink. Under it, the neutral hairline the panel has always
     had, which still draws the other three sides.

     Alpha throughout — no solid step. --color-brand-glow is pink/alpha-30 and
     --color-brand-wash is alpha-20, so the falloff is the ramp the system
     already defines rather than numbers picked here. Solid pink at the corner
     read as a lit edge rather than a hairline that happens to catch the brand.
     Only the final stop is written inline: there is no alpha-0 token to reach
     for, and a gradient needs somewhere to end.
     Everything under it is unchanged, so removing the first layer restores the
     original hairline exactly. */
  background: var(--frame-corner), var(--frame-hairline);
  text-align: start;
}

.panel__inner {
  padding: clamp(18px, 2.2vw, 24px);
  border-radius: calc(var(--radius-xl) - var(--border-hairline));
  background: var(--frame-fill);
  box-shadow: 0 var(--shadow-card-y-hover) var(--shadow-card-blur-hover) var(--color-shadow-card);
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

/* `display: flex` above outranks the UA rule for [hidden], so the code step and
   the email step would both show at once without this. */
.panel__inner[hidden] { display: none; }

@supports (backdrop-filter: blur(1px)) {
  .panel__inner { backdrop-filter: blur(20px) saturate(1.3); }
}

/* ------------------------------------------------------------------- field */

.field-row {
  display: flex;
  align-items: center;
  block-size: var(--size-control-lg);
  padding-inline: var(--space-md);
  background: var(--component-input-bg);
  border: var(--border-hairline) solid var(--component-input-border);
  border-radius: var(--radius-md);
  transition:
    border-color var(--motion-duration-instant) var(--motion-ease),
    box-shadow var(--motion-duration-instant) var(--motion-ease);
}

/* `hidden` has to win here. The attribute's UA rule is a bare [hidden] at one
   attribute of specificity, and .field-row sets display: flex at one class —
   equal weight, later origin, so the author sheet took it and a field marked
   hidden went on rendering at its full 52px. Anything in this file that toggles
   a row with the attribute rather than a class depends on this line. */
.field-row[hidden] { display: none; }

.field-row:hover { border-color: var(--component-input-border-hover); }

.field-row:has(input:focus-visible) {
  border-color: var(--component-input-border-focus);
  box-shadow: 0 0 0 var(--border-focus) var(--color-brand-wash);
}

.field-row.is-error { border-color: var(--component-input-border-error); }

.field-row input {
  flex: 1;
  min-inline-size: 0;
  block-size: 100%;
  background: none;
  border: 0;
  outline: none;
  font-family: inherit;
  font-size: var(--font-size-body);
  color: var(--component-input-value);
}

/* Autofill. Chrome paints its own fill and text colour in the UA layer, where
   `background` and `color` cannot reach — an autofilled address turned the
   field white with near-black text on a dark panel.

   Three separate workarounds are needed, and all three are load-bearing:
     · a large inset shadow, because it paints above the UA fill and is the only
       property that can cover it;
     · -webkit-text-fill-color, because `color` is ignored on an autofilled
       value;
     · a transition long enough to never arrive, because Chrome animates its
       fill back in and would otherwise repaint over the shadow after a moment.

   Vendor-prefixed and outside the token system by necessity. The values are
   still tokens; only the mechanism is a workaround. */
.field-row input:-webkit-autofill,
.field-row input:-webkit-autofill:hover,
.field-row input:-webkit-autofill:focus,
.field-row input:-webkit-autofill:active,
.otp__box:-webkit-autofill,
.otp__box:-webkit-autofill:hover,
.otp__box:-webkit-autofill:focus,
.otp__box:-webkit-autofill:active {
  box-shadow: inset 0 0 0 1000px var(--component-input-bg);
  -webkit-text-fill-color: var(--component-input-value);
  caret-color: var(--component-input-value);
  transition: background-color 100000s ease 0s;
}

/* The focus ring is a box-shadow too, and the rule above would erase it while
   an autofilled box has focus. Restated so the two can coexist. */
.otp__box:-webkit-autofill:focus {
  box-shadow:
    inset 0 0 0 1000px var(--component-input-bg),
    0 0 0 var(--border-strong) var(--color-focus-ring);
}

.field-row input::placeholder { color: var(--component-input-placeholder); }

/* Design System v1 › Inputs (Figma 25:59). Helper text — neutral and error
   alike — is the Caption style: sans, regular, the UI-small metrics. It was
   set at body-sm in the mono face here, which is the label and figure family.
   A sentence in mono reads as a machine string rather than as something
   written to be read, and the place that costs most is the error, where the
   reader is already stuck and least willing to work at it. */
.helper {
  margin: 0;
  color: var(--component-input-helper);
  font-family: var(--font-family-sans), ui-sans-serif, -apple-system, "Helvetica Neue", Arial, sans-serif;
  font-size: var(--font-size-ui-sm);
  line-height: var(--font-leading-ui-sm);
  letter-spacing: var(--font-tracking-ui-sm);
  font-weight: 400;
  text-transform: none;
}

.helper--error { color: var(--component-input-helper-error); }
.helper[hidden] { display: none; }

/* An error belongs under the control it is about. The consent message used to
   share the field's slot, which put "accept product updates" under the email
   box and above the checkbox it was actually describing — the one place a
   reader is guaranteed not to look for it.

   The consent row pads itself out to a 44px touch target, so sitting after it
   in normal flow would leave the message a touch-target's worth of air below
   the label. That padding is taken back here, so this message sits the same
   one gap under its control that the field's message does under the field. */
.consent + .helper--error {
  margin-block-start: calc((var(--size-touch-min) - var(--font-leading-ui-sm)) / -2);
}

/* ---------------------------------------------------------------- checkbox */
/* Design System v1 › Selection controls › Checkbox (Figma 38:34).
   Square by design — round means one choice. The 44px touch target is the
   whole row, not the box. */

.consent {
  display: flex;
  align-items: flex-start;
  gap: var(--space-xs);
  /* The 44px touch row comes from padding, not from min-height with the label
     pinned to the top. That version left the label sitting on the ceiling of a
     44px box with 26px of dead space under it — the row measured symmetrically
     but read bottom-heavy, because what the eye spaces against is the text, not
     the button's box. Equal padding centres the label inside the target while
     the row still clears 44px, and .consent__box keeps aligning to the FIRST
     line rather than to the middle of a wrapped label.
     min-block-size stays as a floor in case the label type ever changes. */
  padding-block: calc((var(--size-touch-min) - var(--font-leading-ui-sm)) / 2);
  padding-inline: 0;
  min-block-size: var(--size-touch-min);
  background: none;
  border: 0;
  cursor: pointer;
  text-align: start;
  font-family: inherit;
}

.consent:disabled { cursor: not-allowed; }

/* The hit area is the whole row, but the box is the control. The default ring
   would draw around the row and wrap the label with it, which reads as though
   the sentence were focusable. Move the ring onto the box and leave the row
   bare — the target stays 44px, only the indicator changes. */
.consent:focus-visible { outline: none; }

.consent:focus-visible .consent__box {
  outline: var(--border-focus) solid var(--color-focus-ring);
  outline-offset: var(--space-3xs);
}

.consent__box {
  position: relative;
  flex: 0 0 auto;
  inline-size: var(--space-lg);
  block-size: var(--space-lg);
  display: grid;
  place-items: center;
  background: var(--component-checkbox-bg);
  border: var(--border-rule) solid var(--component-checkbox-border);
  border-radius: var(--radius-xs);
  /* The spec centres the box against its label. That is only true while the
     label is one line — here it wraps, so centre it on the FIRST line instead.
     Tracks whichever leading the label is set in, so the two stay aligned. */
  margin-block-start: calc((var(--font-leading-ui-sm) - var(--space-lg)) / 2);
  transition:
    background-color var(--motion-duration-instant) var(--motion-ease),
    border-color var(--motion-duration-instant) var(--motion-ease);
}

.consent[aria-checked="true"] .consent__box,
.consent[aria-checked="mixed"] .consent__box {
  background: var(--component-checkbox-bg-checked);
  border-color: var(--component-checkbox-border-checked);
}

.consent:disabled .consent__box {
  background: var(--component-checkbox-bg-disabled);
  border-color: var(--component-checkbox-border-disabled);
}

/* The mark is icon/check and icon/minus from the library, sized to the 14px
   mark frame the component defines — 70% of the box, so it tracks the token. */
.consent__mark {
  grid-area: 1 / 1;                          /* check and minus share one cell */
  inline-size: calc(var(--space-lg) * .7);   /* the 14px mark frame in a 20px box */
  color: var(--component-checkbox-mark);
  opacity: 0;
  transform: scale(.6);
  transition:
    opacity var(--motion-duration-instant) var(--motion-ease),
    transform var(--motion-duration-instant) var(--motion-ease);
}

.consent:disabled .consent__mark { color: var(--component-checkbox-mark-disabled); }

.consent[aria-checked="true"]  .consent__mark--check,
.consent[aria-checked="mixed"] .consent__mark--minus {
  opacity: 1;
  transform: scale(1);
}

/* The component's label token is theme-following and resolves to full-strength
   foreground. This panel is always-dark and the line is consent small print,
   so it takes the on-dark secondary instead — quieter, and it cannot invert. */
.consent__text {
  font-size: var(--font-size-ui-sm);
  line-height: var(--font-leading-ui-sm);
  letter-spacing: var(--font-tracking-ui-sm);
  font-weight: 400;
  color: var(--color-fg-on-dark-secondary);
}

.consent:disabled .consent__text { color: var(--color-fg-on-dark-tertiary); }

/* --------------------------------------------------------------- panel head */
/* What this is, and how far it has got. Sits on the form and shares its frame,
   so heading, count and progress read as one object with the fields below. */

.panel-head {
  position: relative;
  /* Its own stacking context, so the tint below can sit at z-index -1: that
     paints it above this element's background but still under the text.
     Without the context the tint would fall behind the opaque card fill and
     disappear entirely. */
  isolation: isolate;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  /* The bar is a solid 4px block, so its gap to the edge reads as the full
     16px; the title's gap is measured from a line box with leading inside it
     and reads smaller. One step more at the end evens the two optically. */
  padding: var(--space-md) var(--space-lg) var(--space-lg);
  border-start-start-radius: calc(var(--radius-xl) - var(--border-hairline));
  border-start-end-radius: calc(var(--radius-xl) - var(--border-hairline));
  background: var(--component-card-bg);
  text-align: center;
}

/* teal to pink across the strip, at tint strength */
.panel-head::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(94deg,
    rgb(from var(--color-accent-on-dark) r g b / .10) 0%,
    rgb(from var(--color-brand-on-dark) r g b / .13) 100%);
}

/* H6 — the smallest heading step, which is still a heading. Sans and sentence
   case, so it reads as the panel's title rather than as another tag in a page
   that already has several. */
.panel-head__title {
  margin: 0;
  font-size: var(--font-size-h6);
  line-height: var(--font-leading-h6);
  letter-spacing: var(--font-tracking-h6);
  font-weight: 600;
  color: var(--color-fg-primary);
}

/* The figure the heading is really about: how many places are left.

   ONE DEVIATION from the figures rule: the claimed count is sans here, not
   mono. Both halves of this line are numbers in one sentence, and mono put a
   slashed zero in 460 against a plain one in 5,000 — two different glyphs for
   the same digit, a word apart. Tabular figures still apply, on the whole line
   rather than half of it: Manrope carries a tabular set, so the count keeps its
   even advance and does not jitter as the number climbs. */
.panel-head__count {
  margin: 0;
  font-size: var(--font-size-ui-sm);
  line-height: var(--font-leading-ui-sm);
  letter-spacing: var(--font-tracking-ui-sm);
  font-variant-numeric: tabular-nums;
  color: var(--color-fg-on-dark-secondary);
}

.panel-head__n { color: var(--color-brand-on-dark-text); }

.panel-head__bar {
  margin-block-start: var(--space-2xs);
  block-size: var(--space-2xs);
  border-radius: var(--radius-full);
  background: var(--component-progress-track);
  overflow: hidden;
}

/* The same teal-to-pink pair as the strip behind it, at full strength — the
   two-colour run is what makes the filled part legible at this height. */
.panel-head__fill {
  display: block;
  block-size: 100%;
  border-radius: var(--radius-full);
  /* Teal into pink, left to right — the accent handing over to the brand, and
     the only place on the page the two meet as equals. Both are the on-dark
     step of their family, so neither one recedes against the track. */
  background: linear-gradient(90deg,
    var(--color-accent-on-dark) 0%,
    var(--color-brand-on-dark) 100%);
  transition: inline-size var(--motion-duration-slow) var(--motion-ease);
}

/* The head owns the panel's top corners now. */
.panel__inner {
  border-start-start-radius: 0;
  border-start-end-radius: 0;
}

/* ------------------------------------------------------------ primary button */
/* The one element in the system with a gradient, a shadow and motion.
   Hover deepens the shadow — Shadow/Button/Rest (0 4 14) to Shadow/Button/Hover
   (0 8 26) — at 120ms on the system curve, per the Button spec. The fill is the
   same Gradient/Brand/Primary in both states: the design system's Hover variant
   binds the identical gradient stops, so the shadow is the whole difference.
   The button itself does not move. */

/* A primary button that cannot be pressed has to say so. The Instagram
   confirm starts disabled until a handle is typed, and without this it looked
   exactly like a working button that silently did nothing. */
.btn-primary:disabled,
.btn-primary[aria-disabled="true"] {
  cursor: not-allowed;
  opacity: 0.45;
  filter: saturate(0.6);
}
.btn-primary:disabled:hover,
.btn-primary[aria-disabled="true"]:hover { transform: none; }

.btn-primary {
  position: relative;
  block-size: var(--size-control-lg);
  padding-inline: var(--space-xl);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  border: 0;
  border-radius: var(--radius-md);
  cursor: pointer;
  font-family: inherit;
  font-size: var(--font-size-ui);
  line-height: var(--font-leading-ui);
  letter-spacing: var(--font-tracking-ui);
  font-weight: 600;
  color: var(--component-button-primary-fg);

  /* Two background layers: the shine rides on top of the fill, and the border
     radius clips it for free — no overflow: hidden and no extra element. */
  background-image:
    linear-gradient(110deg,
      rgb(255 255 255 / 0) 0%,
      rgb(255 255 255 / .45) 50%,
      rgb(255 255 255 / 0) 100%),
    var(--fill-brand-primary);
  background-repeat: no-repeat;
  background-size: 58px 100%, 100% 100%;
  background-position: -90px 0, 0 0;

  box-shadow: 0 var(--shadow-button-y-rest) var(--shadow-button-blur-rest)
              rgb(from var(--color-shadow-brand) r g b / .38);
  /* 120ms, the duration the Button spec names for hover. */
  transition: box-shadow var(--motion-duration-instant) var(--motion-ease);
}

/* The shadow falls further and softens, which reads as the button rising
   without the button actually moving. Moving it was the cause of the hover
   judder: a cursor resting in the bottom pixels got left behind as the button
   lifted away, :hover ended, it dropped back under the cursor, and the loop
   ran several times a second. Nothing travels now, so nothing can judder. */
.btn-primary:hover {
  box-shadow: 0 var(--shadow-button-y-hover) var(--shadow-button-blur-hover)
              rgb(from var(--color-shadow-brand) r g b / .58);
  /* one pass on hover-in, no loop, no replay until you leave and return */
  animation: shine var(--motion-duration-shine) var(--motion-ease) 1;
}

/* Pressed puts it back on the surface, which is why the shadow collapses to
   1px here. Without it the button would sit lifted while its shadow said
   otherwise. */
.btn-primary:active {
  background-image:
    linear-gradient(110deg, rgb(255 255 255 / 0), rgb(255 255 255 / 0)),
    linear-gradient(90deg, var(--gradient-brand-to), var(--gradient-brand-to));
  box-shadow: 0 var(--shadow-button-y-pressed) var(--shadow-button-blur-pressed)
              rgb(from var(--color-shadow-brand) r g b / .18);
}

@keyframes shine {
  from { background-position: -90px 0, 0 0; }
  to   { background-position: calc(100% + 90px) 0, 0 0; }
}

.btn-primary[aria-busy="true"] { pointer-events: none; opacity: .72; }

/* ----------------------------------------------------------------- spinner */
/* Design System v1 › Feedback › Spinner (Figma 219:90). The whole ring in the
   track colour with a 150° arc of it in the indicator colour, turning.

   A conic gradient masked into a ring rather than the usual border trick: a
   border can only produce arcs in quarter-circle steps, and this component's
   arc is 150°, which no combination of four border colours will give. */
.spinner {
  --spinner-size: var(--component-spinner-size-md);
  inline-size: var(--spinner-size);
  block-size: var(--spinner-size);
  border-radius: var(--radius-full);
  background: conic-gradient(
    var(--component-spinner-indicator) var(--component-spinner-arc),
    var(--component-spinner-track) 0);
  /* Cuts the disc back to a ring of exactly the component's stroke. */
  mask: radial-gradient(farthest-side,
    rgb(0 0 0 / 0) calc(100% - var(--component-spinner-stroke)),
    #000 calc(100% - var(--component-spinner-stroke)));

  /* Not a token: the motion scale tops out at 650ms for UI feedback and has no
     step for a loop that runs until something else ends it. Same reasoning as
     the heading sweep. */
  animation: spinner-turn 700ms linear infinite;
}

.spinner--sm { --spinner-size: var(--component-spinner-size-sm); }

@keyframes spinner-turn { to { rotate: 360deg; } }

/* Slowed rather than stopped. A spinner that holds still does not read as
   "motion suppressed", it reads as "this has hung". */
@media (prefers-reduced-motion: reduce) {
  .spinner { animation-duration: 2.4s; }
}

/* On the primary button the indicator is the button's own foreground, which
   the component defines as #ffffff — so it is white here without a second
   colour being introduced. The 10%-white track still reads against the brand
   fill as a slightly lighter ring. */
.btn-primary .spinner { --component-spinner-indicator: currentColor; }

/* The label keeps its place in the flow so the button cannot change width when
   it goes busy; only its ink is taken away, and the ring is centred over it. */
.btn-primary__busy {
  position: absolute;
  inset: 0;
  display: none;
  place-items: center;
}
.btn-primary[aria-busy="true"] .btn-primary__busy { display: grid; }
.btn-primary[aria-busy="true"] > :not(.btn-primary__busy) { opacity: 0; }

/* ---- Icon button - Variant=Primary ---------------------------------------
   Same fill, shadow and radius as the primary button, square instead of
   padded: 48px at large with a 20px glyph, per the component. The size tokens
   the library names (component/button/height-lg, icon-size-lg, radius) are not
   in the 24 Aug export, so this uses the scale tokens they alias — same values,
   and they follow if the scale moves.

   Unused today: the only icon button left in the product is the Tertiary modal
   close. Defined so that a primary one is correct the moment it is needed,
   rather than being invented at that point. */

.btn-icon-primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--size-control-lg);
  block-size: var(--size-control-lg);
  padding: 0;
  border: 0;
  border-radius: var(--radius-md);
  cursor: pointer;
  color: var(--component-button-primary-fg);
  background-image: var(--fill-brand-primary);
  box-shadow: 0 var(--shadow-button-y-rest) var(--shadow-button-blur-rest)
              rgb(from var(--color-shadow-brand) r g b / .38);
  transition: box-shadow var(--motion-duration-instant) var(--motion-ease);
}

.btn-icon-primary:hover {
  box-shadow: 0 var(--shadow-button-y-hover) var(--shadow-button-blur-hover)
              rgb(from var(--color-shadow-brand) r g b / .58);
}

.btn-icon-primary svg {
  inline-size: var(--size-icon-md);      /* 20px, the large icon size */
  block-size: var(--size-icon-md);
}

.btn-icon-primary--md { inline-size: var(--size-control-md); block-size: var(--size-control-md); }
.btn-icon-primary--sm { inline-size: var(--size-control-sm); block-size: var(--size-control-sm); }

/* ---- a trailing arrow ---------------------------------------------------
   Both glyphs ship and direction picks one, rather than mirroring a single
   path with a transform. An arrow is a directional icon and the system is
   explicit that arrow-right becomes arrow-left in RTL — a real swap, not a
   flipped copy, so the stroke caps and the join stay where they were drawn.
   Same construction as the consent checkbox, which ships check and minus. */

/* Read off the library's Button component, all three sizes:

     Size=Small   32 box · 12 padding-x · 16 icon
     Size=Medium  40 box · 16 padding-x · 20 icon
     Size=Large   48 box · 20 padding-x · 20 icon

   The icon steps once and then stops. Small is the only size that gets 16;
   Medium and Large share 20, and past Medium it is the padding that grows,
   not the glyph — a 24 icon on a 48 button would be the mark outgrowing the
   15px label beside it.

   These are Large, and this was shipping --size-icon-sm: the Small button's
   icon on a Large button. */
.btn-icon {
  flex: 0 0 auto;
  inline-size: var(--size-icon-md);
  block-size: var(--size-icon-md);
}

/* The one size modifier that exists. Back down to the Small button's own. */
.btn-primary--sm .btn-icon,
.btn-secondary--sm .btn-icon {
  inline-size: var(--size-icon-sm);
  block-size: var(--size-icon-sm);
}

.btn-icon .btn-icon__rtl { display: none; }

[dir="rtl"] .btn-icon .btn-icon__ltr { display: none; }
[dir="rtl"] .btn-icon .btn-icon__rtl { display: inline; }

/* ---------------------------------------------------------- secondary button */
/* Button · Variant=Secondary. A filled light button — the inverse of the page,
   which is what gives it weight without competing with the brand gradient.
   Large by default; --sm is the 32px Small variant the masthead uses. */

.btn-secondary {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  block-size: var(--size-control-lg);
  padding-inline: var(--space-lg);
  background: var(--component-button-secondary-bg);
  border: var(--border-hairline) solid var(--component-button-secondary-border);
  border-radius: var(--radius-md);
  cursor: pointer;
  font-family: inherit;
  font-size: var(--font-size-ui);
  line-height: var(--font-leading-ui);
  letter-spacing: var(--font-tracking-ui);
  font-weight: 600;
  color: var(--component-button-secondary-fg);
  transition:
    border-color var(--motion-duration-instant) var(--motion-ease),
    background-color var(--motion-duration-instant) var(--motion-ease);
}

.btn-secondary:hover { background: var(--component-button-secondary-bg-hover); }

/* Small, mirroring .btn-secondary--sm. Same heights the library binds:
   Size=Small is 32, Medium 40, Large 48. */
.btn-primary--sm {
  block-size: var(--size-control-sm);
  padding-inline: var(--space-md);
  gap: var(--space-2xs);
  font-size: var(--font-size-ui-sm);
  line-height: var(--font-leading-ui-sm);
  letter-spacing: var(--font-tracking-ui-sm);
}

.btn-secondary--sm {
  block-size: var(--size-control-sm);
  padding-inline: var(--space-sm);
  gap: var(--space-2xs);
  font-size: var(--font-size-ui-sm);
  line-height: var(--font-leading-ui-sm);
  letter-spacing: var(--font-tracking-ui-sm);
}

/* -------------------------------------------------------------- ghost button */
/* Button · Variant=Ghost · Size=Small. Bordered, which is what separates it
   from Tertiary — the borderless text variant. Type is UI Small, not a label:
   the component binds Manrope 13/18 semibold, so no mono and no uppercase.

   ONE DELIBERATE DEVIATION: the two border tokens are used the other way round
   from the Figma binding. The component sits at #8a8187 and drops to #544d53
   on hover, which makes the outline recede exactly when the pointer arrives.
   Reversed here — quiet at rest, brighter on hover — so the stroke and the
   fill move in the same direction instead of against each other. The aliases
   above stay faithful to Figma; only the order of use changes. */

.btn-ghost {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  block-size: var(--size-control-sm);
  padding-inline: var(--space-sm);
  background: var(--component-button-ghost-bg);
  border: var(--border-hairline) solid var(--component-button-ghost-border-hover);
  border-radius: var(--radius-md);
  cursor: pointer;
  font-family: inherit;
  font-size: var(--font-size-ui-sm);
  line-height: var(--font-leading-ui-sm);
  letter-spacing: var(--font-tracking-ui-sm);
  font-weight: 600;
  color: var(--component-button-ghost-fg);
  transition:
    background-color var(--motion-duration-instant) var(--motion-ease),
    border-color var(--motion-duration-instant) var(--motion-ease),
    color var(--motion-duration-instant) var(--motion-ease);
}

.btn-ghost:hover {
  background: var(--component-button-ghost-bg-hover);
  border-color: var(--component-button-ghost-border);
}

/* ----------------------------------------------------------------- pillars */
/* Three columns above the legal row. At rest each shows only its number and
   title. On hover the body opens as an overlay anchored to the bottom of the
   column, so it grows upward over the hero and the footer never changes
   height — the row below it does not move. */

.pillars {
  position: relative;
  z-index: 4;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  /* A light tint rather than a dark one. --color-border-subtle (#211e21) is a
     near-black that only separates two surfaces when both are lighter than it;
     over the field it disappeared into the particles. --color-border-glass is
     white at 10% on dark, and flips to black at 10% under the light theme, so
     the rule stays a rule either way. */
  border-block-end: var(--border-hairline) solid var(--color-border-glass);
}

.pillar {
  position: relative;
  display: block;
  inline-size: 100%;
  padding: 0;
  background: none;
  border: 0;
  border-inline-start: var(--border-hairline) solid var(--color-border-glass);
  cursor: pointer;
  font-family: inherit;
  text-align: start;
  color: var(--color-fg-on-dark-secondary);
  transition: color var(--motion-duration-instant) var(--motion-ease);
}

.pillar:first-child { border-inline-start: 0; }
.pillar:hover,
.pillar[data-auto="true"],
.pillar[aria-expanded="true"] { color: var(--color-fg-on-dark); }

/* raise the open one so its overlay sits above its neighbours */
.pillar:hover,
.pillar:focus-within,
.pillar[data-auto="true"],
.pillar[aria-expanded="true"] { z-index: 1; }

.pillar__face {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: var(--space-sm);
  padding: var(--space-md) var(--space-lg);
  text-align: center;
  transition: background-color var(--motion-duration-base) var(--motion-ease);
}

/* Keeps the label's mono face, tracking and tabular figures, but takes the
   title's size so the number and the title sit on one optical line. */
.pillar__n {
  color: var(--color-brand-on-dark-text);
  flex: 0 0 auto;
  font-size: var(--font-size-ui);
  line-height: var(--font-leading-ui);
}

.pillar__title {
  font-size: var(--font-size-ui);
  line-height: var(--font-leading-ui);
  letter-spacing: var(--font-tracking-ui);
  font-weight: 600;
}

/* The affordance. At rest a pillar is a number and a title on a flat row, with
   nothing to say that anything is behind it — the body only appeared once you
   were already hovering, which is too late to be an invitation.

   Down closed, up open — the same pair as the disclosure in the portal's side
   column, and the way a disclosure reads everywhere else. It is a state, not a
   direction of travel: pointing it at the panel meant the marker aimed one way
   at rest and the opposite way once you had followed it. align-self, because
   the face aligns on the baseline and an SVG's baseline is its bottom edge —
   left to the row it hung below the type instead of centring on it.

   On the always-dark footer band this takes the on-dark family, not
   --color-icon-*: those flip with the theme and this surface does not. */
.pillar__chev {
  flex: 0 0 auto;
  align-self: center;
  inline-size: var(--size-icon-sm);
  block-size: var(--size-icon-sm);
  color: var(--color-fg-on-dark-tertiary);
  transition:
    rotate var(--motion-duration-base) var(--motion-ease),
    color var(--motion-duration-base) var(--motion-ease);
}

.pillar:hover .pillar__chev,
.pillar:focus-within .pillar__chev,
.pillar[data-auto="true"] .pillar__chev,
.pillar[aria-expanded="true"] .pillar__chev {
  rotate: 180deg;
  color: var(--color-fg-on-dark-secondary);
}

/* The stroke under the open pillar. A pseudo rather than a border so nothing
   shifts by 2px when it appears.

   It grows from the inline start rather than from the centre, because it is no
   longer only a hover mark: while the row is cycling on its own this stroke IS
   the timer, drawing across in exactly the time the pillar holds the row, and
   a progress bar that opens outward from the middle does not read as elapsed
   time. Centre origin needed no RTL rule; a start origin does, because
   transforms are physical and scaleX(1) from `left` would run backwards
   against Arabic — hence the one override below. */
.pillar__face::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  block-size: var(--border-strong);
  /* Brand pink, pink/500. This is a stroke, and the library is explicit that
     fills, strokes, shadows and washes stay on 500 — 400 exists only for text
     and icons, where 500 drops to 5.81:1. A 2px rule has no contrast floor to
     meet, so there is nothing to trade for the half-step of lightness. */
  background: var(--color-brand-on-dark);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--motion-duration-base) var(--motion-ease);
}
[dir="rtl"] .pillar__face::after { transform-origin: right; }

/* Linear, and nothing else. Every other movement in the product is eased,
   but this one is a clock: an eased progress bar spends its last third
   crawling and tells you the wait is longer than it is. The duration comes
   from #pillars as --pillar-dwell, written there by the one constant in
   landing.js that also drives the timer — so the stroke cannot finish early
   or late relative to the pillar it belongs to. */
@keyframes pillar-fill {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

.pillar[data-auto="true"] .pillar__face::after {
  animation: pillar-fill var(--pillar-dwell, 4200ms) linear forwards;
}

.pillar:hover .pillar__face::after,
.pillar:focus-within .pillar__face::after,
.pillar[aria-expanded="true"] .pillar__face::after { transform: scaleX(1); }

/* The brand side's two pinks become one teal on the business side.

   The pair exists because #ff3d7b drops to 5.81:1 as text, so the ordinals
   take the lighter #FF5C95 while strokes stay on 500. The accent needs no such
   split: #25e1e7 is bright enough to read as type on this ground, and it is
   already what the portal sets the points figure and the info icon in. One
   token covers both the numbers and the bar. */
[data-audience="business"] .pillar__n { color: var(--color-accent-on-dark); }
[data-audience="business"] .pillar__face::after { background: var(--color-accent-on-dark); }

/* The same bar, where the phone puts it. Below 769 the pillar row is gone and
   the carousel is carrying these three steps, so its dot is the timeline —
   leaving it pink would be the brand colour marking business copy. */
[data-audience="business"] .pitch__fill { background: var(--color-accent-on-dark); }

.pillar:hover .pillar__face,
.pillar:focus-within .pillar__face,
.pillar[data-auto="true"] .pillar__face,
.pillar[aria-expanded="true"] .pillar__face {
  background: rgb(from var(--color-bg-dark) r g b / .6);
}

/* The body opens upward as an overlay, so the footer never changes height. */
.pillar__panel {
  position: absolute;
  inset-inline: 0;
  inset-block-end: 100%;
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  pointer-events: none;
  background: var(--color-bg-dark);
  border: var(--border-hairline) solid var(--color-border-glass);
  border-block-end: 0;
  border-start-start-radius: var(--radius-md);
  border-start-end-radius: var(--radius-md);
  transition:
    grid-template-rows var(--motion-duration-base) var(--motion-ease),
    opacity var(--motion-duration-base) var(--motion-ease);
}

/* The stroke leads: it draws in before the panel has finished opening, so the
   eye is already on the right column when the text arrives. */
.pillar__face::after { transition-duration: var(--motion-duration-instant); }

.pillar__panel > div { overflow: hidden; }

.pillar:hover .pillar__panel,
.pillar:focus-within .pillar__panel,
.pillar[data-auto="true"] .pillar__panel,
.pillar[aria-expanded="true"] .pillar__panel {
  grid-template-rows: 1fr;
  opacity: 1;
  /* Open, the overlay takes the pointer back. It is a descendant of the
     button, so :hover on the pillar holds while the cursor is anywhere inside
     it — which is the whole point once these open by themselves: reaching up
     to read the sentence used to leave the face and close the sentence. It
     stays inert while collapsed so a zero-height box is never in the way. */
  pointer-events: auto;
}

/* The panel unfolds, then the words rise into it — the small delay is what
   makes it read as one movement instead of two things happening at once.
   Leaving is immediate: a delay on the way out feels like lag, not polish. */
.pillar__body {
  display: block;
  max-inline-size: 44ch;
  margin-inline: auto;
  padding: var(--space-lg) var(--space-lg) var(--space-sm);
  text-align: center;
  text-wrap: pretty;
  font-size: var(--font-size-body-sm);
  line-height: var(--font-leading-body-sm);
  color: var(--color-fg-on-dark-secondary);
  opacity: 0;
  transform: translateY(var(--space-sm));
  transition:
    opacity var(--motion-duration-base) var(--motion-ease),
    transform var(--motion-duration-base) var(--motion-ease);
}

.pillar:hover .pillar__body,
.pillar:focus-within .pillar__body,
.pillar[data-auto="true"] .pillar__body,
.pillar[aria-expanded="true"] .pillar__body {
  opacity: 1;
  transform: none;
  transition-delay: var(--motion-duration-instant);
}

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

.footer {
  position: relative;
  border-block-start: var(--border-hairline) solid var(--color-border-glass);
  background: rgb(from var(--color-bg-canvas) r g b / .5);
}

/* Edge to edge on large desktop, same reasoning as the masthead. */
.footer__inner {
  padding: var(--space-md) clamp(20px, 3.4vw, 44px);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md) var(--space-xl);
}

/* The copyright holds the start edge; space-between on the bar pushes the
   links to the other. */
.footer__copy { color: var(--color-fg-on-dark-tertiary); }

.footer__legal {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-md) clamp(16px, 2vw, 26px);
  color: var(--color-fg-on-dark-tertiary);
}

.footer__legal a { transition: color var(--motion-duration-instant) var(--motion-ease); }
.footer__legal a:hover { color: var(--color-brand-on-dark-text); }

.socials { display: inline-flex; align-items: center; gap: var(--space-3xs); }

/* Icon button · Variant=Tertiary · Size=Small. No border and no fill at rest —
   the mark carries it — and the plate appears only on hover.

   The hover plate is brand pink rather than Tertiary's #191619: these three are
   the only outbound links in the bar, and the brand fill marks them as leaving
   the site. --color-brand-on-dark, not --color-brand-on-dark-text, because this
   is a fill; pink/400 is reserved for text and icons. The mark flips to
   --color-brand-on-solid so it stays legible on the pink. */
.social {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--size-control-sm);
  block-size: var(--size-control-sm);
  border-radius: var(--radius-md);
  color: var(--component-button-tertiary-fg);
  transition:
    background-color var(--motion-duration-instant) var(--motion-ease),
    color var(--motion-duration-instant) var(--motion-ease);
}

.social:hover {
  background: var(--color-brand-on-dark);
  color: var(--color-brand-on-solid);
}

/* 16px in a 32px box, which is what Size=Small binds. */
.social svg { inline-size: var(--size-icon-sm); block-size: var(--size-icon-sm); }

/* -------------------------------------------------------- the other door */
/* Each form ends by pointing at the other one: the waitlist offers sign-in,
   sign-in offers the waitlist. A form that dead-ends is how someone who came
   to the wrong door gives up instead of walking to the right one. */

.form__alt {
  /* Inside the form now, so it takes the panel's own alignment and sits tight
     under the button rather than floating below the card. The panel's 12px gap
     is the whole spacing — this belongs to the button above it. */
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: center;
  gap: var(--space-2xs) var(--space-xs);
  font-size: var(--font-size-ui-sm);
  line-height: var(--font-leading-ui-sm);
  letter-spacing: var(--font-tracking-ui-sm);
  color: var(--color-fg-on-dark-tertiary);
}

.form__alt a {
  color: var(--color-brand-on-dark-text);
  text-decoration: underline;
  text-underline-offset: var(--space-3xs);
  transition: color var(--motion-duration-instant) var(--motion-ease);
}
.form__alt a:hover { color: var(--color-brand-on-dark-hover); }

/* Inside #form, so the code step hides it along with the rest of that step —
   no separate rule needed. */

/* ------------------------------------------------------------------ entrance */

/* Visible by DEFAULT, and the entrance is an animation the element borrows
   rather than a state it has to be rescued from.

   It used to be `opacity: 0` in the base rule, undone by `.is-ready` which
   React added inside requestAnimationFrame after hydration. Two things were
   wrong with that, and the second is the serious one:

   - requestAnimationFrame is PAUSED in a background tab, so a page opened in
     one (a middle-click, a link opened behind the current tab, a session
     restored at browser start) sat there with the headline, the whole signup
     panel and the footer at zero opacity until somebody brought the tab
     forward.
   - Far worse: it made the page's content conditional on JavaScript running
     at all. A blocked bundle, a script error thrown before that effect, an
     extension, a timeout on bad mobile data, and the hero and the form stayed
     invisible for good. The masthead was the only thing that rendered. On paid
     traffic that is a silent total bounce that appears in no log.

   An animation with `backwards` fill has neither problem. The element's
   resting state is visible, so it is visible with no JavaScript on the page at
   all; the animation only borrows the opening frame during its delay, and it
   always finishes because the document timeline advances on wall-clock time
   whether the tab is in front or not. The stagger reads exactly as before. */

@keyframes enter {
  from { opacity: 0; transform: translateY(var(--space-md)); }
  to   { opacity: 1; transform: none; }
}

[data-enter] {
  animation: enter var(--motion-duration-slow) var(--motion-ease) var(--enter-delay, 0ms) backwards;
}

/* Nothing to undo: without the animation the element is already in its
   resting, visible state. */
@media (prefers-reduced-motion: reduce) {
  [data-enter] { animation: none; }
}

/* ------------------------------------------------------------------ compact */

@media (max-width: 768px) {
  /* The lead steps aside for the carousel, and the carousel says what the
     pillar row at the foot of the page used to say — so the row goes with it.
     Keeping both would be the same three sentences twice on one screen, and
     the copy would be read where nobody scrolls rather than under the
     headline.

     Everything below this line about .pillar is now unreachable at this width.
     It is left standing because putting the row back is this one declaration,
     and deleting forty lines to save nothing is how a revert turns into a
     rebuild. */
  .pillars { display: none; }
  .hero__lead { display: none; }
  .pitch { display: block; }

  /* No hover on touch, so the bodies are simply always shown and the overlay
     becomes an ordinary stacked block. */
  .pillars { grid-template-columns: 1fr; }
  .pillar {
    border-inline-start: 0;
    border-block-start: var(--border-hairline) solid var(--color-border-glass);
    /* The overlay sits before the face in the DOM because on desktop it is
       absolutely positioned above it. Once it drops into normal flow here,
       that order has to be undone or the body reads before its own title. */
    display: flex;
    flex-direction: column;
  }
  .pillar__face {
    order: -1;
    justify-content: flex-start;
    text-align: start;
    padding-inline: clamp(20px, 3.4vw, 44px);
  }
  .pillar__face::after { display: none; }
  /* Every body is already open down here and none of them can be closed,
     so a disclosure chevron would be pointing at nothing. */
  .pillar__chev { display: none; }
  .pillar__body {
    max-inline-size: none;
    margin-inline: 0;
    text-align: start;
    padding-inline: clamp(20px, 3.4vw, 44px);
  }
  .pillar:first-child { border-block-start: 0; }
  .pillar__panel {
    position: static;
    grid-template-rows: 1fr;
    opacity: 1;
    background: none;
    backdrop-filter: none;
    /* No card here — it is stacked content in the flow, not something floating
       above the row. The border only made sense while it was an overlay. */
    border: 0;
  }
  /* The panel was being opened without the words inside it being revealed —
     they kept the opacity: 0 and the 12px offset the desktop hover animates
     away, so every body rendered as an empty box on touch. Nothing here can
     hover, so they are simply shown. */
  .pillar__body {
    padding-block: 0 var(--space-md);
    opacity: 1;
    transform: none;
  }
  .pillar__face { padding-block-end: var(--space-2xs); }

  /* The whole band goes darker on touch. On desktop the pillars sit at the very
     bottom of the frame where the field has thinned out; on a phone the footer
     lands wherever the scroll puts it, which is often straight across the
     brightest part of the attractor.

     .82 rather than a guess: sampled the framebuffer behind #pillars at this
     viewport and computed the contrast of the body colour (#b0a7ac, 14px, so
     4.5:1 applies) against it. At the inherited .5 the worst percentiles read
     3.02:1 and 1.60:1 — the second is a near-white particle under a glyph, and
     it is effectively unreadable. .82 lifts those to 6.42:1 and 5.11:1, both
     clear. Past .9 the band stops looking like a scrim and starts looking like
     a solid bar sitting on the animation.

     .pillars is inside .footer, so this covers the cards and the legal row in
     one rule rather than two that could drift apart. */
  .footer { background: rgb(from var(--color-bg-canvas) r g b / .82); }

  /* The title carries the step on a phone — the body under it is the detail, so
     the two should not sit at the same weight. On desktop the row inherits
     --color-fg-on-dark-secondary and the hover panel does the separating; there
     is no hover here, so the title takes the full foreground instead.
     The ordinal keeps its teal: it is a different kind of thing, not a quieter
     version of the same one. */
  .pillar__title { color: var(--color-fg-on-dark); }
  /* the domain and the entry link will not both fit beside the mark */

  /* A tall hero means a viewport-sized well, which would erase the figure.
     Narrow the scrim to the band the words actually occupy. */
  .hero::before {
    inset: 0 -10%;
    background: radial-gradient(80% 34% at 50% 34%,
      rgb(from var(--color-bg-canvas) r g b / .66) 0%,
      rgb(from var(--color-bg-canvas) r g b / .46) 44%,
      rgb(from var(--color-bg-canvas) r g b / 0) 82%);
  }
  /* The page keeps its full-viewport floor here too, so the footer sits on the
     bottom edge instead of stopping wherever the content happens to end. This
     was `auto` from the days when the body could not scroll and a page taller
     than the viewport would have been clipped — now it scrolls, so the floor is
     free: shorter content pins the footer down, taller content just scrolls. */
  /* Asymmetric here, unlike every other width. The bar above is sticky on a
     phone, so it already puts a band of air over the headline; the hero's own
     top padding was stacking a second one on top of it. The floor keeps its
     full measure — that is what pins the footer to the bottom edge. */
  .hero {
    padding-block: clamp(8px, 1.6vh, 20px) clamp(32px, 8vh, 72px);
    gap: var(--space-md);
  }

  /* Sticky on phones only. On a desktop the bar is a few centimetres from
     everything; on a phone it is the only way back to the top of a page that
     scrolls for several screens, and scrolling away from it costs a flick.

     It fades out downward rather than sitting on a hard edge — the field is
     moving behind it, and a solid bar with a border would read as a lid on
     the animation. The gradient hides the field where the words are and lets
     it back through underneath, with no line between the two. */
  .masthead {
    position: sticky;
    inset-block-start: 0;
    /* z-index comes from the base rule — one number for "the bar is over the
       page", rather than two that have to be kept in step. */
    /* The fade needs somewhere to happen. This is the run-off below the bar's
       own contents, not padding around them. */
    padding-block-end: clamp(28px, 5vh, 48px);
    /* Solid to the foot of the logo row, then out. Fading from the very top
       let the page's own headings show through at half strength behind the
       wordmark, which read as a printing fault rather than as a fade. */
    background: linear-gradient(180deg,
      var(--color-bg-canvas) 0%,
      var(--color-bg-canvas) 44%,
      rgb(from var(--color-bg-canvas) r g b / .82) 62%,
      rgb(from var(--color-bg-canvas) r g b / .34) 82%,
      rgb(from var(--color-bg-canvas) r g b / 0) 100%);
  }

  .footer__inner { justify-content: center; text-align: center; }
}

@media (max-height: 700px) and (min-width: 769px) {
  .hero { gap: var(--space-xs); }
  .hero__lead { display: none; }        /* the headline and the field carry it */
}

/* --------------------------------------------------------- reduced motion */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
  .btn-primary:hover { animation: none; }  /* drop the shine, keep the shadow */
}

/* ------------------------------------------------------------- the code step */
/* One job on the screen. The hero heading and lead were selling the waitlist,
   and the three founding steps were describing what comes after — neither helps
   anyone read six digits off their phone, so both step aside. The field keeps
   running behind: it is the one thing that says the page is still alive. */

[data-step="code"] .hero__title,
[data-step="code"] .hero__lead,
[data-step="code"] .pitch,
[data-step="code"] .pillars,
[data-step="code"] .panel-head { display: none; }

/* Centred, whatever the rest of the page is doing. The hero is top-aligned
   from tablet up, which is right for a headline standing over a form and wrong
   for this: six boxes and a button, alone on the screen with every other piece
   of furniture hidden, belong in the middle of it.

   Equal padding, so the centring is not pulled off by the 56.4/28.2 pair the
   hero carries the rest of the time. Two selectors' worth of specificity, so
   this also beats the design-canvas paddings set further down the file. */
[data-step="code"] .hero {
  justify-content: center;
  padding-block: var(--space-lg);
}

/* And its own top margin off, or the centring is 12px out. That margin is the
   extra air between the lead and the panel — "a change of surface, not the
   next paragraph" — and the lead is not here to need it. Left on, it sits
   inside the centred line and pushes the panel down by exactly its own height:
   measured 234.3 above against 222.9 below. */
[data-step="code"] .panel { margin-block-start: 0; }

/* With the head gone the form owns the panel's top corners again. */
[data-step="code"] .panel__inner {
  border-start-start-radius: calc(var(--radius-xl) - var(--border-hairline));
  border-start-end-radius: calc(var(--radius-xl) - var(--border-hairline));
}

.code { gap: var(--space-md); }

/* ---- leaving ------------------------------------------------------------ */
/* A control, not fine print: getting the address wrong is the likeliest reason
   to be stuck here, so the way out is the first thing in the reading order. */

.code__back {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  margin-inline-start: calc(-1 * var(--space-xs));
  padding: var(--space-2xs) var(--space-xs);
  background: none;
  border: 0;
  border-radius: var(--radius-md);
  cursor: pointer;
  font-family: inherit;
  font-size: var(--font-size-ui-sm);
  line-height: var(--font-leading-ui-sm);
  letter-spacing: var(--font-tracking-ui-sm);
  font-weight: 600;
  color: var(--color-fg-on-dark-secondary);
  transition: color var(--motion-duration-instant) var(--motion-ease);
}

/* Colour only. The plate this used to grow on read as a button appearing
   where there had not been one — this is a way out of the step, not a control
   competing with Verify. */
.code__back:hover { color: var(--color-fg-on-dark); }

.code__back-icon {
  inline-size: var(--size-icon-sm);
  block-size: var(--size-icon-sm);
}

/* A directional glyph, so it mirrors by hand. The browser will not do it and
   `transform` on an icon that also moves on hover is worse than a second path. */
[dir="rtl"] .code__back-icon { scale: -1 1; }

/* ---- what to do --------------------------------------------------------- */

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

.code__title {
  margin: 0;
  /* H5. This is the only heading on the screen once the code step takes over —
     the hero title and lead are hidden — so it carries the page rather than
     sitting inside something larger, and H6 read as a subhead with nothing
     above it. */
  font-size: var(--font-size-h5);
  line-height: var(--font-leading-h5);
  letter-spacing: var(--font-tracking-h5);
  font-weight: 600;
  color: var(--color-fg-on-dark);
  /* Fill, not balance. `balance` evens the lines by pulling words down, which
     on "Verify your email to claim your founding spot" broke after "to" with
     the rest of the line empty. Two full lines beat two half ones when the
     panel is this narrow. */
  text-wrap: wrap;
}

/* Carries the address, so it is the line that lets someone catch a typo before
   they go hunting for an email that was never sent to them. */
.code__sub {
  margin: 0;
  font-size: var(--font-size-ui-sm);
  line-height: var(--font-leading-ui-sm);
  letter-spacing: var(--font-tracking-ui-sm);
  color: var(--color-fg-on-dark-secondary);
  overflow-wrap: anywhere;   /* a long address must not widen the panel */
}

/* ---- the six boxes ------------------------------------------------------ */

/* One step of air on top of the form's own 12px gap, above and below. The row
   of boxes is the only thing on this screen that has to be got right, so it
   gets to sit apart from the sentence above it and the button below it. */
.otp {
  margin-block-start: var(--space-xs);
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: var(--space-xs);
}

/* ---- the gap above a submit ---------------------------------------------
   24px between a primary button and whatever sits above it, in every form and
   every modal. The containers do not share a gap — .panel__inner runs at 12px
   and .code at 16 — so the margin is whatever makes up the difference rather
   than one number applied blindly.

   The consent row is the exception, and deliberately: its box carries 13px of
   padding to make a 44px touch target, so it already puts the label ~25px clear
   of the button. Adding the margin there would measure the same and look half
   again too big. */
.panel__inner .btn-primary,
.task__body .btn-primary { margin-block-start: var(--space-sm); }   /* 12 + 12 */
.code .btn-primary       { margin-block-start: var(--space-xs); }   /* 16 +  8 */
.consent + .btn-primary  { margin-block-start: 0; }

/* And the gap BETWEEN buttons stacked on each other — Share above Download,
   any pair where one action sits over another. --space-sm, the same figure as
   the margin above a primary, so a column of controls is spaced the same
   whether the thing above is a field or another button. Held here as one
   custom property rather than typed at each site, so the next stack has
   somewhere to read it from instead of a number to guess at. */
:root { --gap-button-stack: var(--space-sm); }

/* And the same below it. Whatever follows a submit — the free-to-join line, the
   way out to the waitlist, the resend row — is a footnote to the button, not
   the next control, so it gets one step of air rather than the form's own gap.
   Held here rather than on each of those three, so they cannot drift apart. */
.btn-primary + .form__alt          { margin-block-start: var(--space-xs); }   /* 12 + 8 */
.code .btn-primary + .code__resend { margin-block-start: var(--space-2xs); } /* 16 + 4 */

/* The columns divide the panel, so the row fits whatever the panel is without
   a media query. min-inline-size: 0 lets them shrink below the UA's default
   input width on a narrow phone.

   Taller than a text field on purpose. The design system has no OTP component
   and --size-control-lg (48px) is the largest control it defines, so the height
   is composed from two tokens rather than invented: 48 + 8 = 56. That is the
   conventional size for a code box — it reads as a slot to drop one character
   into, not as a field to type a sentence in, and it clears the 44px touch
   minimum with room for a fingertip that lands off-centre. */
.otp__box {
  min-inline-size: 0;
  block-size: calc(var(--size-control-lg) + var(--space-xs));
  padding: 0;
  text-align: center;
  background: var(--component-input-bg);
  border: var(--border-hairline) solid var(--component-input-border);
  border-radius: var(--radius-md);
  /* One step up from the field size too — a single digit in a 56px box at the
     body size would float in the middle of it. */
  font-size: var(--font-size-h5);
  line-height: var(--font-leading-h5);
  font-variant-numeric: tabular-nums;
  color: var(--color-fg-on-dark);
  caret-color: var(--color-brand-on-dark-text);
  transition:
    border-color var(--motion-duration-instant) var(--motion-ease),
    box-shadow var(--motion-duration-instant) var(--motion-ease);
}

.otp__box:focus {
  outline: none;
  border-color: var(--component-input-border-focus);
  box-shadow: 0 0 0 var(--border-strong) var(--color-focus-ring);
}

.otp__box.is-error { border-color: var(--component-input-border-error); }

/* ---- resending ---------------------------------------------------------- */
/* Two states in one line. The countdown is text because nothing can be done
   with it yet; when it runs out the same line becomes a question and a button. */

.code__resend {
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  min-block-size: var(--size-control-sm);
  font-size: var(--font-size-ui-sm);
  line-height: var(--font-leading-ui-sm);
  letter-spacing: var(--font-tracking-ui-sm);
  color: var(--color-fg-on-dark-tertiary);
}

/* Mono and tabular so the digits hold their column and the line does not
   twitch on every tick. */
.code__timer {
  font-family: var(--font-family-mono), ui-monospace, SFMono-Regular, Menlo, monospace;
  font-variant-numeric: tabular-nums;
}

.code__offer {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
}

/* Both states set a display, which outranks the UA rule for [hidden] — without
   this the countdown and the offer show side by side on one line. */
.code__timer[hidden],
.code__offer[hidden] { display: none; }

.code__resend-btn {
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
  font: inherit;
  font-weight: 600;
  color: var(--color-brand-on-dark-text);
  text-decoration: underline;
  text-underline-offset: 3px;
  transition: color var(--motion-duration-instant) var(--motion-ease);
}

.code__resend-btn:hover { color: var(--color-brand-on-dark-hover); }

@media (max-width: 26rem) {
  .otp { gap: var(--space-2xs); }
}

/* ==========================================================================
   Proportional scaling — desktop and up
   ==========================================================================
   One composition, authored at 1440x900, scaled to fit the viewport. A small
   laptop, a 27" monitor and a 6K display all see the same layout at the same
   proportions rather than the same layout with different amounts of air in it.

   Nothing below 1025px is touched. A phone is not a small desktop and does not
   want to be one — it keeps the fluid, stacking layout it already has.

   zoom, not transform: scale. zoom participates in layout, so hit testing,
   focus rings, sticky and fixed positioning all keep working and text is laid
   out at its final size rather than rasterised and stretched. transform would
   also make .page a containing block for fixed descendants, which would break
   every overlay on the site.

   The ratio has to be a unitless number, and CSS will not divide a length by a
   length. tan(atan2(a, b)) is how you get one: atan2 reads the two lengths as
   a rise and a run and returns an angle, and tan turns that angle back into
   the ratio. Checked in the browser rather than assumed — at 1541px wide it
   returns 1.0701, which is exactly 1541/1440.

   min() of the two ratios is a `contain` fit: the composition is bounded by
   whichever axis runs out first, so it never crops and the footer stays on
   screen. The floor stops body text falling below ~13px on a small laptop —
   past that the page scrolls instead. The ceiling stops a 6K display blowing
   the design up four times; it is roughly twice a laptop's physical size but
   four times its pixels, and matching the pixels would make everything
   physically enormous at the same viewing distance. */

:root {
  /* 1 everywhere the scaling does not apply, so any calc that divides by it
     is still valid on a phone. */
  --ui-scale: 1;
}

@media (min-width: 1025px) {
  :root {
    --design-w: 1440px;
    /* Taller than the 1440x900 this is nominally authored at, and deliberately.
       Measured at 1x, the portal comes to 911px and the waitlist to 901 — both
       already past 900, which would put the footer a hair below the fold at
       every scale rather than at none of them. 940 clears the taller of the
       two with about 30px to spare, which is roughly one extra line of heading
       before anything has to scroll.

       The cost is that a 1440x900 viewport lands at 0.96x rather than exactly
       1.00x. Worth it: a browser's viewport on a 1440x900 screen is nearer
       1440x760 once its own chrome is subtracted, so an exact 1.00x was never
       going to be the common case, and a footer that is always visible is what
       was actually asked for. */
    --design-h: 940px;
    --ui-scale: clamp(0.85,
      min(tan(atan2(100vw, var(--design-w))), tan(atan2(100svh, var(--design-h)))),
      1.8);
  }

  /* The field and its scrims are siblings of .page and stay out of this: they
     are full-bleed at native resolution, and scaling the canvas would only
     cost it pixels. */
  .page { zoom: var(--ui-scale); }

  /* Viewport units and percentages behave differently inside a zoomed subtree,
     and only one of them needs correcting. Both measured rather than assumed:

       100svh   resolves against the real viewport and is then multiplied by
                the zoom, so a 100svh box at zoom 2 renders at twice the
                viewport height. It has to be divided back out.

       100%     resolves against the containing block as the zoomed subtree
                sees it, which is already the parent's size divided by the
                zoom. It is correct as written, and dividing it again — which
                is the obvious thing to reach for — leaves the page rendering
                at 65% of the viewport width with the layout cramped into it.

     So the width is left alone: .page is a block and fills its parent, which
     comes out exactly viewport-wide once zoomed. Only the height is corrected. */
  .page { min-height: calc(100svh / var(--ui-scale)); }

  /* The fluid values that are still interpolating at the baseline rather than
     pinned to one end of their clamp. Left alone they would grow once with the
     viewport and again with the zoom — the vh-based ones end up around 40% over
     at 4K. Re-expressed as their share of the design canvas, so the zoom is the
     only thing scaling them. Everything not listed here already sits on its
     clamp's max at 1440 and needs nothing. */
  /* Both ends, since the top is fluid again. Same conversion as everything
     else in here: the vh is re-expressed as its share of the design canvas so
     the UI zoom is the only thing scaling it — 6vh and 3vh of 940, which is
     56.4 over the headline and 28.2 under the panel, held there while the zoom
     does the rest. */
  .hero {
    padding-block: calc(var(--design-h) * .06) calc(var(--design-h) * .03);
  }
}

/* ============================================================== legal pages */

/* Terms and Privacy are the only long-form reading in the product. Everything
   else here is a screen you act on; these are documents you scan, so they get
   a reading measure and a type scale of their own rather than the hero's.

   No canvas behind them. The field is the waitlist's argument for itself and
   it belongs where somebody is deciding — not under two thousand words they
   have to read carefully. The wash and the sink stay, so the page is still
   recognisably the same product. */
.doc {
  inline-size: 100%;
  max-inline-size: var(--measure-text);
  margin-inline: auto;
  padding-inline: clamp(20px, 3.4vw, 44px);
  padding-block: var(--space-6xl) var(--space-7xl);
}

.doc__eyebrow {
  color: var(--color-fg-tertiary);
  margin-block-end: var(--space-sm);
}

/* H3 rather than H1. The page title is the only heading here and it competes
   with nothing, so it does not need the hero's size — at 68 it would take two
   lines of a 720px measure before the document had said anything. */
.doc__title {
  margin: 0;
  font-size: var(--font-size-h3);
  line-height: var(--font-leading-h3);
  letter-spacing: var(--font-tracking-h3);
  /* 600, not 700. The whole heading ramp in the design system is SemiBold from
     H2 down; only H1 and Display go heavier. Taking the H3 size with a Bold
     weight made a step that does not exist in the scale. */
  font-weight: 600;
  color: var(--color-fg-primary);
  text-wrap: balance;
}

/* The date is the whole point of a legal page's header: which version is this,
   and when did it change. Mono and tabular so a run of revisions lines up. */
.doc__stamp {
  margin-block: var(--space-sm) 0;
  /* Body Small, so the stamp sits on a real step of the scale. Left to inherit
     it took the base body size, which put a 17px mono line under the title —
     a size the scale has no mono style at. It is metadata; it reads below the
     prose, not level with it. */
  font-size: var(--font-size-body-sm);
  line-height: var(--font-leading-body-sm);
  color: var(--color-fg-tertiary);
}

.doc__lead {
  margin-block: var(--space-lg) 0;
  font-size: var(--font-size-body);
  line-height: var(--font-leading-body);
  color: var(--color-fg-secondary);
}

/* A rule rather than more space. Twelve sections of prose need something that
   says "new subject" without adding a screenful of air to a long page. */
.doc__section {
  margin-block-start: var(--space-5xl);
  padding-block-start: var(--space-3xl);
  border-block-start: var(--border-hairline) solid var(--color-border-glass);
}

.doc__section:first-of-type {
  border-block-start: 0;
  padding-block-start: 0;
}

/* Flex, not a margin on the numeral. The heading mixes a number with Latin
   text inside a paragraph that is RTL in Arabic, and the bidi algorithm
   reorders those two runs — which put the numeral's margin-inline-end on the
   outside of the pair and closed the gap between them. A flex gap is applied
   between the items whichever way round the runs end up. */
.doc__h {
  margin: 0 0 var(--space-md);
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
  font-size: var(--font-size-h6);
  line-height: var(--font-leading-h6);
  letter-spacing: var(--font-tracking-h6);
  font-weight: 600;
  color: var(--color-fg-primary);
}

/* The number is not decoration: these documents get referred to by section, so
   the numeral has to be selectable and it has to survive being read aloud. */
.doc__n {
  flex: 0 0 auto;
  /* Medium, not the heading's SemiBold. Only 400 and 500 of DM Mono ship, so
     inheriting 600 left the browser to synthesise a weight that does not
     exist — and Number and Label Large are both Medium in the scale. */
  font-weight: 500;
  color: var(--color-brand-on-dark-text);
}

/* Scoped to the sections rather than to every p in the document. Written as
   `.doc p` these outweighed .u-label by one element of specificity and quietly
   took the eyebrow with them — a Label meant to set at 11px was rendering as
   body text at 17 — and did the same to the stamp and to the lead's top
   margin. The prose rules belong to the prose. */
.doc__section p,
.doc__section li {
  font-size: var(--font-size-body);
  line-height: var(--font-leading-body);
  color: var(--color-fg-secondary);
}

.doc__section p { margin-block: 0 var(--space-md); }
.doc__section p:last-child { margin-block-end: 0; }

.doc ul {
  margin-block: 0 var(--space-md);
  padding-inline-start: var(--space-xl);
}

.doc li { margin-block-end: var(--space-xs); }
.doc li::marker { color: var(--color-fg-tertiary); }

.doc strong { color: var(--color-fg-primary); font-weight: 600; }

/* The Link component's own tokens, which nothing in the product was using
   yet. Its 24 variants are all textDecoration: NONE, and the global reset on
   line 203 already strips the underline, so colour carries the affordance and
   the hover step is the whole interaction. The offset that used to sit here
   was inert — there was no underline for it to offset.

   --component-link-fg rather than --color-brand-on-dark-text: these documents
   sit on the theme canvas, not on an always-dark band, so the link has to be
   able to follow the theme. They render identically while data-theme is
   pinned to dark; they stop being identical the moment it is not. */
.doc a {
  color: var(--component-link-fg);
  transition: color var(--motion-duration-instant) var(--motion-ease);
}

.doc a:hover { color: var(--component-link-fg-hover); }

/* A definition list, because half of a privacy page is "this field, for this
   reason" and a paragraph makes that pair hard to scan. Two columns where
   there is room, stacked where there is not. */
.doc__dl {
  display: grid;
  grid-template-columns: minmax(0, 12rem) minmax(0, 1fr);
  gap: var(--space-sm) var(--space-xl);
  margin-block: 0 var(--space-md);
}

.doc__dl dt {
  font-size: var(--font-size-body-sm);
  line-height: var(--font-leading-body-sm);
  color: var(--color-fg-primary);
  font-weight: 600;
}

.doc__dl dd {
  margin: 0;
  font-size: var(--font-size-body-sm);
  line-height: var(--font-leading-body-sm);
  color: var(--color-fg-secondary);
}

@media (max-width: 40rem) {
  .doc { padding-block: var(--space-4xl) var(--space-5xl); }
  .doc__title { font-size: var(--font-size-h4); line-height: var(--font-leading-h4); letter-spacing: var(--font-tracking-h4); }
  .doc__dl { grid-template-columns: minmax(0, 1fr); gap: var(--space-3xs) 0; }
  .doc__dl dd { margin-block-end: var(--space-sm); }
}

/* ------------------------------------------------- desktop width and footer */

/* At 1440 the 720px column sat inside roughly 360px of empty band on each
   side, which reads as an indent rather than a centred document. The container
   now takes the same measure the rest of the site uses, so the document lines
   up with the masthead above it.

   Nothing inside is held back to the reading measure. Capping the prose left
   the text in a 720px column inside a 1220px frame, which put the indent back
   one level in rather than removing it, and folded the title onto two lines so
   that it read as a hand-placed break. The document fills its container.

   The cost is a long line — roughly 1130px of 17px text on a wide screen,
   past the length prose is comfortable at. That is the tradeoff asked for, and
   it is the reason --measure-text exists; reinstating it is one rule. */
@media (min-width: 64rem) {
  .doc { max-inline-size: var(--measure-container); }
}

/* Both documents ship in both languages as markup, not as strings swapped in
   by script: legal text has to survive JavaScript being off, and a crawler has
   to be able to read the Arabic. The language control writes lang and dir onto
   <html>; these two rules are the whole mechanism.

   The half that is hidden keeps its real lang attribute rather than being
   stripped out, so assistive technology announces whichever half it is given
   in the right voice. */
:root[lang="en"] .page--doc [lang="ar"],
:root[lang="ar"] .page--doc [lang="en"] { display: none; }

/* The footer, pinned. These are the only pages long enough that the pair of
   links between Terms and Privacy would otherwise sit several thousand pixels
   down, reachable only by finishing the document.

   .page is a grid, and a grid item's sticky containing block is its own grid
   area — for the auto-sized footer row that is exactly the footer's height, so
   there is nothing for it to travel in and `position: sticky` does nothing.
   Switching the document pages to block layout gives the footer the full page
   as its containing block. min-height: 100svh on .page still holds it to the
   bottom of the viewport when a document is shorter than the screen, which is
   what the 1fr row was doing before.

   Sticky keeps the footer in flow, so it reserves its own space at the end and
   no line of the document ends up underneath it. It does need an opaque fill:
   the landing page's half-transparent canvas would let the text it is covering
   read through as it scrolls past. */
.page--doc { display: block; }

/* The masthead rides along for the same reason the footer does, and by the
   same mechanism: block layout on .page gives it the whole document as its
   containing block, which a grid area would not.

   Opaque, and above the prose. It is an always-dark band, so it keeps that
   background rather than the theme canvas the document sits on — otherwise a
   line of text would read straight through it on the way past. */
.page--doc .masthead {
  position: sticky;
  inset-block-start: 0;
  z-index: 2;
  background: var(--color-bg-dark);
}

.page--doc .footer {
  position: sticky;
  inset-block-end: 0;
  background: var(--color-bg-canvas);
}
