
/*
 * Colour is taken verbatim from libs/tokens/src/index.css rather than re-typed
 * as hex, so a retune of the product palette carries to the site instead of
 * drifting from it. Brand hexes are noted for the ones a designer will ask for.
 */
:root {
  --white: #ffffff;
  --zinc-50: oklch(98.5% 0 0);
  --zinc-100: oklch(96.7% 0.001 286.375);
  --zinc-200: oklch(92% 0.004 286.32);
  --zinc-300: oklch(87.1% 0.006 286.286);
  --zinc-400: oklch(70.5% 0.015 286.067);
  --zinc-500: oklch(55.2% 0.016 285.938);
  --zinc-600: oklch(44.2% 0.017 285.786);
  --zinc-700: oklch(37% 0.013 285.805);
  --zinc-800: oklch(27.4% 0.006 286.033);
  --zinc-950: oklch(14.1% 0.005 285.823);

  /* The sheet each product sits on. The captures are cut out with their alpha
     kept, so these can change without the images needing to be re-matted. */
  --sheet-enterprise: #e4eef4;
  --sheet-passport: #e3e1e1;

  --brand-50: oklch(0.970 0.014 267.1);   /* #f1f5ff */
  --brand-100: oklch(0.923 0.035 267.1);  /* #dbe6fe */
  --brand-200: oklch(0.862 0.064 267.1);  /* #bfd1fd */
  --brand-500: oklch(0.545 0.233 267.1);  /* #355bf5 — the brand */
  --brand-600: oklch(0.486 0.267 267.1);  /* #233af2 */

  --emerald-50: oklch(97.9% 0.021 166.113);
  --emerald-500: oklch(69.6% 0.17 162.48);
  --emerald-700: oklch(50.8% 0.118 165.612);
  --amber-50: oklch(98.7% 0.022 95.277);
  --amber-700: oklch(55.5% 0.163 48.998);
  --orange-50: oklch(98% 0.016 73.684);
  --orange-700: oklch(55.3% 0.195 38.402);
  --red-50: oklch(97.1% 0.013 17.38);
  --red-700: oklch(50.5% 0.213 27.518);

  --bg: var(--white);
  --bg-sub: var(--zinc-50);
  --ink: var(--zinc-950);
  --ink-2: var(--zinc-600);
  --ink-3: var(--zinc-500);
  --line: var(--zinc-200);
  --line-soft: var(--zinc-100);

  --font-display: 'Archivo', system-ui, sans-serif;
  --font-body: 'Inter', system-ui, sans-serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, 'SF Mono', monospace;

  --gutter: 32px;
  --measure: 1200px;
  --radius: 14px;
  --radius-sm: 10px;
  --rhythm: 148px;
}

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

/*
 * `hidden` is an attribute, and any `display` in a rule below beats it -- so a
 * component that sets `display: grid` silently un-hides everything it holds.
 * That caught the booking dialog three times over: the confirmation panel, the
 * per-field errors and the footer's chevrons all set display and all ignored
 * the attribute. This restores it once, rather than each component having to
 * remember to opt back in.
 */
[hidden] { display: none !important; }

/*
 * Both elements, and clip rather than hidden.
 *
 * Boxes hang past the right edge here on purpose -- the closing wheel by 28px
 * on a phone, the hero stage by 2. On `body` alone the stage's reached the
 * document anyway: measured in WebKit at 402px, scrollWidth came back 404, and
 * a document wider than its viewport is one iOS lets the reader drag sideways.
 * With `clip` on both, scrollWidth is 402 exactly. `hidden` stays in front of
 * it as the fallback for anything that has not learned `clip`, and has to,
 * since it is the declaration `clip` overrides.
 */
html, body { overflow-x: hidden; overflow-x: clip; }

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


body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 17px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

img, svg { max-width: 100%; }
a { color: inherit; }

:focus-visible {
  outline: 2px solid var(--brand-500);
  outline-offset: 2px;
  border-radius: 4px;
}

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

.skip {
  position: absolute;
  left: -9999px;
}
.skip:focus {
  left: 16px;
  top: 12px;
  z-index: 100;
  background: var(--ink);
  color: var(--white);
  padding: 10px 14px;
  border-radius: 8px;
  font-size: 14px;
}

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 500;
  letter-spacing: -0.028em;
  line-height: 1.04;
  margin: 0;
  text-wrap: pretty;
}

h1 { font-size: clamp(38px, 5.4vw, 68px); letter-spacing: -0.034em; }
h2 { font-size: clamp(30px, 3.6vw, 46px); }
h3 { font-size: 21px; line-height: 1.25; letter-spacing: -0.018em; }

p { margin: 0; }

.lede {
  font-size: clamp(17px, 1.35vw, 20px);
  line-height: 1.5;
  color: var(--ink-2);
  max-width: 62ch;
  text-wrap: pretty;
}

.eyebrow {
  font-family: var(--font-mono);
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.hero__eyebrow {
  margin: 0;
  font-size: clamp(14px, 1.1vw, 16.5px);
  color: var(--ink);
}

/* One tint across the whole product-word run, per the Figma. An earlier
   revision shaded each of the four words separately; this one does not. */
.hl { color: var(--brand-500); }

/* Bare at the top of the page and plated once it is over content: the plate
   is what the nav needs to stay legible while the page scrolls under it, and
   at rest it would be an 82%-white rectangle on white. */
.nav {
  position: sticky;
  top: 0;
  z-index: 40;
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background-color 180ms ease, backdrop-filter 180ms ease, border-color 180ms ease;
}
.nav[data-stuck='true'],
/* The policy pages carry no script, so nothing ever sets data-stuck there --
   and a transparent header over scrolling body text is unreadable. */
.nav--solid {
  background: color-mix(in oklab, var(--white) 94%, transparent);
  backdrop-filter: saturate(180%) blur(6px);
  border-bottom-color: var(--line);
}

.nav__inner {
  display: flex;
  align-items: center;
  gap: 28px;
  height: 66px;
  max-width: var(--measure);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.nav__logo { display: block; width: 156px; padding-block: 4px; color: var(--ink); flex: none; }
.nav__logo svg { display: block; width: 100%; height: auto; aspect-ratio: 188 / 40; }

.nav__links { display: flex; gap: 26px; margin-inline: auto; }
.nav__links a {
  text-decoration: none;
  font-size: 14.5px;
  font-weight: 450;
  color: var(--ink-2);
  transition: color 140ms ease;
}
.nav__links a:hover { color: var(--ink); }

/* `margin-left: auto` rather than relying on .nav__links as the spacer: the
   links go display:none below 900px, which left the actions jammed against the
   logo with a third of the bar empty on tablets and landscape phones. */
.nav__cta { display: flex; gap: 8px; flex: none; margin-left: auto; }

/*
 * Above the breakpoint the wrapper is not a box at all: `display: contents`
 * drops it out of the layout so .nav__links and .nav__cta are laid out by the
 * bar's own flex, exactly as they were before the wrapper existed.
 */
.nav__drop { display: contents; }

/* The burger belongs to the narrow layout only. */
.nav__burger { display: none; }

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 40px;
  padding-inline: 17px;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  font-family: var(--font-body);
  font-size: 14.5px;
  font-weight: 500;
  letter-spacing: -0.005em;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: background-color 140ms ease, border-color 140ms ease, color 140ms ease;
}
.btn--primary { background: var(--brand-500); color: var(--white); }
.btn--primary:hover { background: var(--brand-600); }
.btn--ghost { background: transparent; border-color: var(--line); color: var(--ink); }
.btn--ghost:hover { background: var(--zinc-50); border-color: var(--zinc-300); }
.btn--lg { height: 48px; padding-inline: 22px; font-size: 15.5px; }

/*
 * The reveal. `data-reveal` is written by site.js and never by the markup, so
 * a page whose script fails shows everything rather than nothing -- the usual
 * way this effect hides a whole site.
 *
 * Opacity and transform are compositor properties and cost nothing. The blur
 * does not: it is a GPU pass over the block for as long as it runs, which is
 * why it is declared only on the waiting state. The settled rule names no
 * filter at all, so a filter list interpolates to none and nothing is left
 * carrying a rendering surface -- and nothing is left creating a containing
 * block for the fixed and 3D things further down the page.
 */
[data-reveal='wait'] {
  opacity: 0;
  transform: translate3d(0, 22px, 0);
  filter: blur(6px);
}
[data-reveal='in'] {
  opacity: 1;
  transform: none;
  /* The delay is set per element, and staggers siblings so a row of cards
     arrives as a row. It is on the transition rather than an animation so that
     an element revealed mid-scroll cannot be caught part-way through. */
  transition: opacity 900ms cubic-bezier(0.22, 1, 0.36, 1) var(--reveal-in, 0ms),
              transform 900ms cubic-bezier(0.22, 1, 0.36, 1) var(--reveal-in, 0ms),
              filter 900ms cubic-bezier(0.22, 1, 0.36, 1) var(--reveal-in, 0ms);
}
@media (prefers-reduced-motion: reduce) {
  [data-reveal='wait'] { opacity: 1; transform: none; filter: none; }
  [data-reveal='in'] { transition: none; }
}

/*
 * The policy pages. One measure for the whole document rather than the grid the
 * landing page uses -- these are read top to bottom, and 72 characters is where
 * a line stops needing the eye to travel back.
 *
 * `scroll-margin-top` on the headings is what makes the contents list usable:
 * without it a jump puts the heading under the sticky nav.
 */
.legal { padding-block: 64px 96px; }
.legal__head { max-width: 72ch; margin-bottom: 40px; padding-bottom: 28px; border-bottom: 1px solid var(--line); }
.legal__head h1 { font-size: clamp(30px, 3.4vw, 44px); }
.legal__stamp { margin-top: 12px; font-family: var(--font-mono); font-size: 12px; color: var(--ink-3); }

.legal__body { max-width: 72ch; }
.legal__body h2, .legal__body h3 { scroll-margin-top: 96px; }
.legal__body h2 { margin: 44px 0 14px; font-size: 21px; letter-spacing: -0.012em; }
.legal__body h3 { margin: 30px 0 10px; font-size: 17px; }
.legal__body p { margin: 0 0 15px; font-size: 15.5px; line-height: 1.65; color: var(--ink-2); }
.legal__body ul { margin: 0 0 18px; padding-left: 20px; display: grid; gap: 7px; }
.legal__body li { font-size: 15.5px; line-height: 1.6; color: var(--ink-2); }
.legal__body a { color: var(--brand-500); text-decoration: underline; text-underline-offset: 2px; }
.legal__body a:hover { color: var(--brand-600); }
/* Long unbroken URLs and addresses appear throughout these documents. */
.legal__body { overflow-wrap: anywhere; }

.section { padding-block: var(--rhythm); }
.section--tight { padding-block: calc(var(--rhythm) * 0.62); }
.section__head { display: grid; gap: 18px; max-width: 720px; margin-bottom: 56px; }

.hero { --hero-pad: 84px; padding-top: var(--hero-pad); padding-bottom: 0; }
.hero__head { position: relative; }
.hero__copy { position: relative; z-index: 0; }
/* minmax(0, …) rather than the default auto track: a grid item's min-width is
   its content, so the nowrap CTA button was widening the whole column and
   taking the headline and eyebrow out past the viewport with it. */
/* The column carries no max-width of its own. A max-width on the h1 cannot
   bind inside a narrower grid column, so a cap here would be the thing that
   decides the headline's measure and the 20em below would never apply. */
.hero__copy { display: grid; grid-template-columns: minmax(0, 1fr); gap: 22px; }
.hero h1 { font-size: clamp(28px, 4.2vw, 56px); line-height: 1.1; max-width: 20em; }
.hero__actions { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; margin-top: 6px; justify-items: start; }

/*
 * Dark ground, light content. The mark and the labels sit on near-black, so
 * the hairline that bounded it against white is gone -- the panel's own edge
 * is the boundary now -- and the particle palette in site.js is inverted to
 * match, since brand blue on near-black is barely there.
 */
.stage {
  position: relative;
  margin-top: 56px;
  height: 660px;
  border-radius: 18px;
  background: var(--zinc-950);
  overflow: hidden;
  isolation: isolate;
  /* Transformed from site.js on scroll: it grows, and tilts on X so the near
     bottom edge projects wider than the top. The origin is the top edge so the
     top stays put and only the bottom pushes out. The canvas inside is a
     raster, so it is resampled rather than redrawn -- a slight softening of
     1px dots, and not worth re-measuring the backing store every frame of a
     scroll to avoid. */
  transform-origin: 50% 0%;
  will-change: transform;
}
/* Behind everything, and never animated -- it is painted once per size. */
.stage__dust {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  z-index: 0;
  pointer-events: none;
}
.stage__canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }

/* Badges riding the outbound stream. Positioned from JS in stage coordinates,
   so the layer is the stage box and each badge is centred on its own point. */
.flights { position: absolute; inset: 0; pointer-events: none; }
.flight {
  position: absolute;
  top: 0; left: 0;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 12px 5px 10px;
  border-radius: 999px;
  /* Filled rather than the soft emerald the inline chips use: this one floats
     over the particle field and has to hold its own against it. */
  background: var(--emerald-500);
  box-shadow: 0 6px 16px -8px oklch(69.6% 0.17 162.48 / 0.75);
  font-size: 12.5px;
  font-weight: 500;
  letter-spacing: -0.005em;
  color: var(--white);
  white-space: nowrap;
  /* The reveal turns about the middle of the RIGHT edge, so that end settles
     first and the far end follows it up. JS composes the rotation, and its
     tilt is signed to match this side. */
  transform-origin: 100% 50%;
  will-change: transform, opacity;
}
.flight__tick { flex: none; color: var(--white); }

/*
 * The three parties, named around the mark. `pointer-events: none` so a label
 * never interrupts a drag passing over it — the pointer listener is on .stage.
 */
/* Placed from the field's centre, so it follows Size and Vertical offset
   rather than carrying a second set of coordinates to keep in step. */
.stage__mark {
  position: absolute;
  transform: translate(-50%, -50%);
  pointer-events: none;
  line-height: 0;
}
.stage__mark svg { display: block; width: 100%; height: 100%; }
.stage__mark path { fill: var(--white); }

.orbit {
  position: absolute;
  inset: 0;
  pointer-events: none;
  /* Top is a fixed inset; the sides are a share of the width, so they stay in
     proportion as the widget narrows and land near the middle of the gutter
     between the border and the mark. */
  --orbit-top: 44px;
  --orbit-side: 12%;
}

/*
 * Pinned to a fixed inset rather than centred on a percentage: a percentage
 * left puts the node's middle at a fraction of the width, so its clearance
 * from the border tracked the label's width and the two sides sat at 132px and
 * 114px from their edges for no reason anyone chose.
 */
.orbit__node {
  position: absolute;
  display: grid;
  justify-items: center;
  gap: 10px;
  width: max-content;
}
/*
 * Anchored by the near edge, not centred, so each sits exactly --orbit-top /
 * --orbit-side from its border whatever its label measures. Three positions
 * spaced around the centred mark, so each stream arrives from its own quarter.
 */
.orbit__node--top { left: 50%; top: var(--orbit-top); transform: translateX(-50%); }
.orbit__node--bleft { left: var(--orbit-side); bottom: var(--orbit-top); }
.orbit__node--bright { right: var(--orbit-side); bottom: var(--orbit-top); }
/* Full white, not dimmed: these name the three parties, and a label that has
   to be read is not decoration. */
.orbit__icon { display: block; width: 26px; height: 26px; color: var(--white); }
.orbit__label {
  font-size: 15px;
  font-weight: 450;
  letter-spacing: -0.008em;
  color: var(--white);
  white-space: nowrap;
}

/* With no logo row under it the lead-in is the block's only heading, so it
   carries a little more weight and the quotes sit closer to it. */
/*
 * A full rhythm above, the tight one below. The stage that sits over this
 * carries a scroll-driven scale that grows it up to 10%, so its rendered
 * bottom reaches past its layout box and eats into whatever gap is declared
 * here -- 92px of padding was measuring 70px on screen. The quotes below run
 * straight into the next section, so only the top needed opening up.
 */


.proof__line {
  margin: 0;
  text-align: center;
  font-size: 14px;
  color: var(--ink);
}

/*
 * The testimonial stack. Five quotes as one pile of cards: the front one
 * legible, two peeking out behind it, the rest parked at zero opacity until
 * they rotate forward. site.js carries the depth; everything here is geometry.
 *
 * The handoff's tokens map onto this page's own rather than being pasted as
 * hex -- background-0 is --white, border-100 and border-200 are --line and
 * --zinc-200, typography-900/600/500 are --ink and --ink-3.
 */
/* No panel around it, and no padding of its own: the stack is already a stack
   of cards, and the section's own rhythm is what spaces it from its
   neighbours -- padding here was adding to that and making this one block sit
   further from the page than the rest.
 *
 * The section gives its bottom rhythm away for the same reason. The block
 * above it ends flush, so the space over the eyebrow is this section's 148 and
 * nothing else; the block below opens with its own 148, so keeping one here
 * made the gap under the dots twice the gap over the line. */
.proof { padding-bottom: 0; }
.proof__line {
  margin: 0 0 32px;
  font-size: 15px;
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: 0.01em;
  color: var(--ink-3);
  text-align: center;
}
.stack {
  max-width: 760px;
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 40px;
}

/*
 * The viewport is as tall as the front card plus the two peeks behind it, and
 * every card is the same fixed height. Both are load-bearing: the quotes are
 * different lengths, and on content-driven heights the longest card poked out
 * from behind the front one and read as being out of order.
 */
.stack__view {
  position: relative;
  width: 100%;
  --card-h: 236px;
  /* Exactly where the deepest visible card ends, from the same two constants
     the transform uses: the third card is scaled by two 0.035 steps and pushed
     down by two 18px ones. A round reserve left 45px of nothing under the
     stack, which read as the section running long rather than as space. */
  height: calc(var(--card-h) * (1 - 2 * 0.035) + 36px);
}
.stack__card {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: var(--card-h);
  margin: 0;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 28px;
  padding: 40px 44px 36px;
  border: 1px solid var(--zinc-200);
  border-radius: 14px;
  background: var(--white);
  /* The depth is the distance in front of the active card, written by site.js.
     transform-origin is what makes the ones behind read as receding into the
     pile rather than sliding down out of it. */
  transform-origin: top center;
  transform: translateY(calc(var(--d, 0) * 18px)) scale(calc(1 - var(--d, 0) * 0.035));
  z-index: calc(10 - var(--d, 0));
  opacity: 1;
  pointer-events: none;
  cursor: default;
  transition: transform 520ms cubic-bezier(0.22, 0.61, 0.36, 1),
              opacity 320ms ease,
              box-shadow 320ms ease;
}
.stack__card[data-front] {
  box-shadow: 0 1px 2px oklch(0 0 0 / 0.04);
  pointer-events: auto;
  cursor: pointer;
}
/* Only three are ever visible; the other two wait at zero behind the pile. */
.stack__card[data-deep] { opacity: 0; }

.stack__card blockquote {
  margin: 0;
  font-size: 24px;
  line-height: 1.5;
  color: var(--ink);
  text-wrap: pretty;
}
.stack__card figcaption {
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink-3);
}

.stack__dots { display: flex; align-items: center; gap: 8px; }
.stack__dots button {
  width: 7px;
  height: 7px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: var(--zinc-200);
  cursor: pointer;
  transition: background-color 150ms ease, width 150ms ease;
}
.stack__dots button[data-on] { width: 22px; background: var(--ink); }

@media (max-width: 900px) {
  .stack { gap: 32px; }
  .stack__view { --card-h: 268px; }
  .stack__card { padding: 32px 30px 28px; gap: 22px; }
  .stack__card blockquote { font-size: 20px; }
}
@media (max-width: 600px) {
  .stack__view { --card-h: 300px; }
  .stack__card { padding: 26px 22px 24px; }
  .stack__card blockquote { font-size: 18px; }
}

/* Static and dot-driven: the auto-advance is stopped in site.js, and without
   the transition the stack simply redraws at the chosen quote. */
@media (prefers-reduced-motion: reduce) {
  .stack__card { transition: none; }
  .stack__dots button { transition: none; }
}
/*
 * Two dark cards side by side. Text sits top-left inside the card and the
 * product UI bleeds in below it, cropped by the card's own rounding — so the
 * mock reads as a window onto the product rather than a framed screenshot.
 *
 * The three data-frame variants this replaces (laptop / bleed / pinned) and
 * the segmented control that switched between them are gone: the framing is
 * settled, and a switcher with one option is a switcher with none.
 */
.surfaces { display: grid; grid-template-columns: 1fr; gap: 22px; }

/*
 * A light card with the product on a device inside it, stacked one per row.
 *
 * This replaced a dark card whose screenshot bled off two edges. The bleed
 * showed more pixels but never showed a whole screen, and the two tinted
 * photographs underneath were doing the work of telling the products apart --
 * which is a job the device itself does better, and for free: a laptop reads
 * as the facility's tool and a phone as the clinician's before a word is read.
 */
.surface {
  position: relative;
  display: grid;
  /* minmax(0, 1fr), not the implicit auto track: an auto track sizes to its
     max-content, and the capture's max-content is its intrinsic 2092px --
     which grew the whole card past the viewport, taking the heading off the
     right edge with it. `overflow: hidden` clips the pixels but does not stop
     the width propagating. */
  grid-template-columns: minmax(0, 1fr);
  justify-items: center;
  /* Edge to heading, and heading block to capture. */
  gap: 64px;
  padding: 80px 40px 56px;
  border-radius: 18px;
  overflow: hidden;
  isolation: isolate;
  background: var(--sheet-enterprise);
}
.surface[data-tone='passport'] { background: var(--sheet-passport); }
/* The capture is a hand holding the phone, cut off mid-forearm. Given room
   below it that cut reads as a mistake, so the image runs off the bottom of
   the sheet instead and the card's own corner clips it. */
.surface[data-tone='passport'] { padding-bottom: 0; }

/*
 * The sheets run 192px wider than the column the rest of the page is set to,
 * 96px past it on each side. Only where there is room: the column caps at 1136
 * inside a 1200 measure, so the bleed needs 1328 of viewport, and this waits
 * for 1392 so a gutter is left beside it.
 */
@media (min-width: 1392px) {
  .surface {
    width: calc(100% + 192px);
    margin-inline: -96px;
  }
}

.surface__banner {
  text-align: center;
  display: grid;
  justify-items: center;
  gap: 14px;
  max-width: 60ch;
}
.surface__banner h3 {
  font-size: clamp(26px, 2.4vw, 34px);
  line-height: 1.1;
  letter-spacing: -0.022em;
}
.surface__lede {
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink-2);
  max-width: 62ch;
  text-wrap: pretty;
}

/*
 * The capture carries its own chrome -- a browser window for the facility app,
 * a phone in a hand for the passport -- so there is no device frame drawn
 * around it any more. Both are cut out, so what shows behind them is the
 * sheet.
 */
.surface__shot { width: 100%; display: grid; justify-items: center; }

/*
 * Sized by height, which is what the design fixes; the width follows the
 * capture's own aspect, and max-height rather than height means a viewport too
 * narrow for the full width shrinks the capture instead of squashing it.
 *
 * `width: 100%` and not `auto`, which is what this had. With a w-descriptor
 * srcset the browser divides the chosen file's intrinsic width by its density
 * to get a density-corrected intrinsic width -- which is exactly the `sizes`
 * length. So at `auto` the layout width WAS the sizes attribute, and a sizes
 * value that disagreed with the slot rendered the capture at the wrong size
 * rather than merely fetching a suboptimal file. It measured 246px in a 270px
 * slot on a phone. At 100% the CSS owns the layout and sizes only picks the
 * file, which is the job it should have.
 */
.surface__img {
  width: 100%;
  height: auto;
  /* The height the design fixes, expressed as the width that produces it --
     669 and 760 tall are 1046 and 926 wide at these captures' own ratios.
     Capping the width rather than the height is what keeps the ratio: with a
     definite width, max-height clamps the height and stretches the picture
     instead of shrinking the box. Measured doing exactly that at 1248x669,
     ratio 1.87 against the capture's 1.56. */
  max-width: 1046px;
}
.surface__img--phone { max-width: 926px; }


/* Open grid, not the hairline-divided table this was: six short features read
   as a list of capabilities, and the rules around each one made them look like
   cells of data instead. */
.agents { display: grid; grid-template-columns: repeat(4, 1fr); gap: 34px 30px; }

/*
 * The one dark band on the page. This block is the claim about what the
 * software does without being asked, and a ground the rest of the page does
 * not use is what keeps the four from reading as four more cards.
 */
.agents-block { background: var(--zinc-950); }
.agents-block h2 { color: var(--white); }
.agents-block .lede,
.agents-block .agent p { color: var(--zinc-400); }
.agents-block .scope__note { color: var(--zinc-500); }

/* No card. Four short pieces of writing on the same ground, which is what
   they are -- a panel around each was drawing a box that said nothing the
   heading did not. */
.agent { display: grid; align-content: start; gap: 9px; }
.agent h3 { color: var(--white); }
/* The mark, and nothing behind it: a tile reads as a button, and these are
   labels for what the card is about. */
.agent__icon {
  display: block;
  margin-bottom: 6px;
  color: var(--brand-200);
}
.agent h3 { font-size: 16.5px; letter-spacing: -0.012em; }
.agent p { font-size: 14.5px; line-height: 1.55; margin: 0; text-wrap: pretty; }
.agent__icon svg { width: 22px; height: 22px; }
@media (max-width: 1000px) { .agents { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 620px) { .agents { grid-template-columns: 1fr; } }

/*
 * Copy left, photograph right. The photograph stretches to whatever the copy
 * column ends up being, so the two edges line up without either side being
 * given a fixed height to keep in step with the other.
 */
.impact-block {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.82fr);
  gap: 64px;
  align-items: stretch;
}
.impact__copy { display: grid; align-content: start; }

/* Big and stacked, as its own column head rather than the page-wide centred
   one the other sections use -- it is the left edge of a two-column block. */
.impact__head {
  max-width: none;
  margin: 0 0 52px;
  justify-items: start;
  text-align: left;
}
.impact__head h2 { font-size: clamp(38px, 4.4vw, 58px); line-height: 1.02; }

.impact__photo {
  margin: 0;
  border-radius: 18px;
  overflow: hidden;
  background: var(--zinc-100);
  min-height: 420px;
}
.impact__photo img {
  display: block;
  width: 100%; height: 100%;
  object-fit: cover;
  /* Biased down rather than centred. The source is taller than the slot, so
     cover crops top and bottom; what is at the top is ceiling and what is at
     the bottom is her feet, and cutting a subject off at the ankles reads as a
     mistake in a way that a cropped ceiling does not. */
  object-position: 50% 68%;
}

.impact { display: grid; grid-template-columns: repeat(2, 1fr); gap: 44px 36px; }
.impact__cell { display: grid; gap: 14px; align-content: start; }
.impact__cell h3 {
  /* Inter, not the display face: these are the tops of short entries in a
     column, not section titles, and Archivo made each one announce itself
     over the sentence it introduces. */
  font-family: var(--font-body);
  font-size: 16px;
  font-weight: 550;
  letter-spacing: -0.008em;
  line-height: 1.3;
}
.impact__cell p { font-size: 15px; line-height: 1.55; color: var(--ink-2); max-width: 34ch; }

/* The numeral each of these used to carry said only what position it was in.
   An icon says what the feature is about before the heading is read. */
/*
 * Coloured per cell rather than one grey for all six: the icon is the first
 * thing read in each cell, and six identical tiles made them scan as one
 * block. The hues are the circles' palette, so the page keeps one set.
 */
/*
 * No tile, one green. The tinted box was the loudest thing in each cell and
 * put a row of grey chiclets above the words that carry the meaning; with it
 * gone the icon reads as a mark beside the heading rather than a button.
 *
 * emerald-700 rather than the brighter 500: a 1.6px stroke on white at that
 * lightness goes washed.
 *
 * It sizes to the glyph rather than keeping the tile's 42px box: that height
 * was reserving room for a tile, and without one it left the icon floating
 * 31px above its own heading -- nearer the block above than the words it
 * belongs to. The negative margin closes the rest of the cell's gap, so the
 * icon and the heading read as one pair and the gap below the heading is the
 * larger of the two.
 */
.impact__icon {
  display: grid;
  place-items: center start;
  margin-bottom: -6px;
  color: var(--emerald-700);
}
/* Sized up a little: 19px was judged inside a tile that framed it, and the
   same glyph naked on the page reads smaller than it measures. */
.impact__icon svg { width: 22px; height: 22px; }

@media (max-width: 1000px) {
  /* The photograph stops earning its column before the copy does: two text
     columns beside it leave both too narrow. It goes below, full width and
     shorter, so it is still in the section without squeezing the words. */
  .impact-block { grid-template-columns: 1fr; gap: 44px; }
  /* Portrait source, so a landscape box below the breakpoint would be a band
     across her middle. It keeps a portrait shape and is capped in width, which
     also stops one photograph filling a phone screen on its own. */
  .impact__photo {
    min-height: 0;
    /* align-self: start, or the block's `align-items: stretch` sets the height
       and the aspect ratio then derives the WIDTH from it -- which made the
       slot 214px on a phone while `sizes` was told 300px, and an overstated
       sizes pulls down a bigger candidate than the slot can show. Width drives
       now, and the measurement matches what sizes claims. */
    align-self: start;
    width: 100%;
    aspect-ratio: 3 / 4;
    max-width: 380px;
    margin-inline: auto;
    order: 1;
  }
  .impact__head { margin-bottom: 40px; }
}
@media (max-width: 620px) {
  .impact { grid-template-columns: 1fr; gap: 36px; }
  .impact__photo { max-width: 300px; }
  .impact__cell p { max-width: none; }
}

/*
 * After factory.ai's surfaces block: a centred head, then a numbered list down
 * the left whose selected row opens, and one panel on the right. The numbers
 * and the hairline between rows are what make it read as a list rather than as
 * three buttons in a stack.
 */
.section__head--mid { max-width: 680px; margin-inline: auto; text-align: center; }

/* The band keeps the page's own ground; only the panel is dark, so the dark
   reads as the thing being shown rather than as a section break. */
.verify { position: relative; }

.scope { display: grid; grid-template-columns: 0.82fr 1.18fr; gap: 56px; align-items: start; }

.scope__tabs { display: grid; align-content: start; }
.scope__tab {
  display: grid;
  text-align: left;
  padding: 20px 0;
  border: 0; border-top: 1px solid var(--line);
  background: none;
  font: inherit; color: inherit; cursor: pointer;
}
.scope__tab:first-child { border-top: 0; padding-top: 0; }
.scope__tab-name {
  font-size: clamp(20px, 1.9vw, 26px);
  line-height: 1.15; letter-spacing: -0.018em; font-weight: 500;
  color: var(--ink-3);
  transition: color 260ms ease;
}
.scope__tab[aria-selected='true'] .scope__tab-name { color: var(--ink); }
/*
 * Only the open row carries its line. `grid-template-rows` animates where
 * `height: auto` cannot, so the row opens rather than appearing -- and the
 * inner element needs `min-height: 0` or a grid item refuses to go below its
 * content and nothing moves.
 */
.scope__tab-sub {
  display: grid; grid-template-rows: 0fr;
  font-size: 14.5px; line-height: 1.5; color: var(--ink-2);
  transition: grid-template-rows 320ms ease, opacity 260ms ease;
  opacity: 0;
}
.scope__tab-sub > * { min-height: 0; overflow: hidden; }
.scope__tab[aria-selected='true'] .scope__tab-sub { grid-template-rows: 1fr; opacity: 1; padding-top: 8px; }

/* Every panel in the same grid cell: the pane is as tall as the tallest, so
   the rotation cross-fades in place instead of resizing the frame under the
   reader. `visibility` rather than `display` because a hidden grid item still
   has to contribute its height -- and it takes the panel out of the
   accessibility tree, which `opacity` alone would not. */
/*
 * The dark panel, on a grid. factory.ai draws no grid lines on their version at
 * all -- what reads as one there is the panel's own artwork sitting on their
 * twelve-column rhythm -- so this is the same idea on our pitch, as two
 * repeating gradients that cost a paint and no elements.
 *
 * The grid is a layer of its own rather than a background on the panel, because
 * the panel's rounded corners have to clip it and a background-image would also
 * sit under the rows rather than behind them.
 */
.scope__pane {
  position: relative;
  display: grid;
  padding: 34px 36px;
  border-radius: var(--radius);
  background: var(--zinc-100);
  /* Still clips, so the panels keep to the rounded corners. `isolation` went
     with the grid that used to sit behind them at a negative z-index. */
  overflow: hidden;
}
.scope__panel {
  grid-area: 1 / 1;
  transition: opacity 300ms ease;
}
.scope__panel[data-on='false'] { opacity: 0; visibility: hidden; }

.scope__list { display: grid; gap: 0; margin: 0; padding: 0; list-style: none; }
.scope__list li {
  display: grid; grid-template-columns: 9.5rem 1fr; gap: 16px;
  padding: 11px 0;
  border-top: 1px solid var(--line);
  font-size: 14.5px; color: var(--ink-2);
}
.scope__list li:first-child { border-top: 0; }
/* The same face and size as the line beside it. The source was set in mono
   at 12.5 against body copy at 14.5, which read as two rows rather than one
   row of two columns; weight carries the difference instead. */
.scope__src { font-size: 14.5px; font-weight: 550; color: var(--ink); }

/* Names with nothing to say about them run two to a row, which stands twenty
   of them at the same height as the ten sources and keeps the frame still. */
.scope__list--names { grid-template-columns: 1fr 1fr; column-gap: 28px; }
.scope__list--names li { display: block; color: var(--ink); }
.scope__list--names li:nth-child(2) { border-top: 0; }

.scope__note { margin-top: 20px; font-size: 13px; color: var(--ink-3); }

@media (max-width: 900px) {
  .scope { grid-template-columns: 1fr; gap: 32px; }
  .scope__pane { padding: 24px 20px; }
  .scope__list--names { grid-template-columns: 1fr; }
  .scope__list--names li:nth-child(2) { border-top: 1px solid var(--line-soft); }
}
@media (max-width: 680px) {
  .scope__list li { grid-template-columns: 1fr; gap: 2px; }
}

.split { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr); gap: clamp(32px, 5vw, 72px); align-items: center; }
.split__copy { display: grid; gap: 18px; }
.split__panel {
  border: 1px solid var(--line); border-radius: 18px; overflow: hidden; background: var(--white);
  box-shadow: 0 1px 2px oklch(14.1% 0.005 285.823 / 0.05), 0 22px 48px -26px oklch(14.1% 0.005 285.823 / 0.16);
}

.trail { display: grid; }
.trail__row {
  display: grid; grid-template-columns: 96px minmax(0, 1fr); gap: 16px; align-items: center;
  padding: 17px 22px; border-bottom: 1px solid var(--line-soft); font-size: 13.5px;
}
.trail__row:last-child { border-bottom: 0; }
.trail__row > :first-child { font-family: var(--font-mono); font-size: 10.5px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.trail__row > :nth-child(2) { color: var(--ink-2); }
.trail__row strong { color: var(--ink); font-weight: 550; }

/* No background of its own: the wheel behind the copy is the block's visual now,
   and a tinted plate under it only muddied the blue. `position` and `overflow`
   stay -- they are what anchor the wheel and crop it to the section. */
.cta {
  position: relative; overflow: hidden;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
}
.cta__inner { position: relative; text-align: center; display: grid; gap: 26px; justify-items: center; padding-block: 108px; }
.cta h2 { max-width: 22ch; }
.cta__actions { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; }
/* Scoped to this block rather than changed on .btn--ghost, which the two nav
   buttons and the demo dialog's Cancel also wear. Here the button sits over the
   wheel, where a transparent ground leaves the label reading straight off the
   blades. */


.faq { border-top: 1px solid var(--line); }
.faq__item { border-bottom: 1px solid var(--line); }
.faq__q {
  width: 100%; display: flex; align-items: center; gap: 20px; justify-content: space-between;
  padding: 22px 2px; background: none; border: 0; cursor: pointer; text-align: left;
  font-family: var(--font-display); font-size: 19px; font-weight: 500; letter-spacing: -0.018em; color: var(--ink);
}
.faq__q:hover { color: var(--brand-500); }
.faq__icon { flex: none; color: var(--ink-3); transition: transform 220ms cubic-bezier(0.16, 1, 0.3, 1); }
.faq__item[data-open='true'] .faq__icon { transform: rotate(45deg); color: var(--brand-500); }
.faq__a { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 260ms cubic-bezier(0.16, 1, 0.3, 1); }
.faq__item[data-open='true'] .faq__a { grid-template-rows: 1fr; }
.faq__a > div { overflow: hidden; }
.faq__a p { padding: 0 2px 24px; max-width: 76ch; color: var(--ink-2); font-size: 15.5px; }

.footer { padding-top: 72px; }

/*
 * The footer emblem. Geometry is the lab's rig scaled to sit at 132px: a bar
 * is pushed out by --radius and its transform-origin pulled back the same
 * amount, so the pivot stays on the stage centre whatever the radius. rotateX
 * stands each bar on its long edge; rotate() then swings it round the point.
 * Orthographic on purpose -- no perspective -- so zoom is a plain scale and
 * every transform is a pure rotation.
 */
/* One size drives the whole rig. The four bar measurements were fixed numbers
   that happened to be fractions of the 132px box, so expressing them as those
   fractions makes the size a single control and leaves the footer's wheel
   pixel-identical to what it was. */
.spin[hidden] { display: none; }
.spin {
  --spin-size: 132px;
  --radius: calc(var(--spin-size) / 6);
  --w: calc(var(--spin-size) / 3);
  --h: calc(var(--spin-size) / 6);
  --t: calc(var(--spin-size) / 26.4);
  --dur: 20s;
  width: var(--spin-size); height: var(--spin-size); margin: 0 0 36px;
  position: relative;
  transform-style: preserve-3d;
}
.spin__rig {
  position: absolute; inset: 0;
  transform-style: preserve-3d;
  /* Only the orbit takes these; nothing else on the page is read against them. */
  transform: rotateX(var(--rx, -26deg)) rotateY(var(--ry, -54.6deg)) rotateZ(var(--rz, 45deg));
}
.spin__bar {
  position: absolute;
  width: var(--w); height: var(--h);
  left: calc(50% + var(--radius) - var(--w) / 2);
  top: calc(50% - var(--h) / 2);
  transform-origin: calc(var(--w) / 2 - var(--radius)) 50%;
  transform-style: preserve-3d;
  animation: spin-turn var(--dur) linear infinite;
}
@keyframes spin-turn {
  from { transform: rotate(0deg) rotateX(90deg); }
  to   { transform: rotate(360deg) rotateX(90deg); }
}
/* Six faces make a slab rather than a plane: a plane has no projected area
   edge-on and blinks out once a turn, a slab always presents a side. */
.spin__face {
  position: absolute; left: 50%; top: 50%;
  border-radius: var(--br, 2px);
  /* site.js writes --fill per face: one hue and saturation from the wheel's
     colour, with the per-bar ramp and per-face shading moving only lightness. */
  background: var(--fill, #476ff5);
  backface-visibility: hidden;
}
.spin__face--front  { width: var(--w); height: var(--h); transform: translate(-50%,-50%) translateZ(calc(var(--t) / 2)); }
.spin__face--back   { width: var(--w); height: var(--h); transform: translate(-50%,-50%) translateZ(calc(var(--t) / -2)) rotateY(180deg); }
.spin__face--left   { width: var(--t); height: var(--h); transform: translate(-50%,-50%) translateX(calc(var(--w) / -2)) rotateY(-90deg); }
.spin__face--right  { width: var(--t); height: var(--h); transform: translate(-50%,-50%) translateX(calc(var(--w) / 2)) rotateY(90deg); }
.spin__face--top    { width: var(--w); height: var(--t); transform: translate(-50%,-50%) translateY(calc(var(--h) / -2)) rotateX(90deg); }
.spin__face--bottom { width: var(--w); height: var(--t); transform: translate(-50%,-50%) translateY(calc(var(--h) / 2)) rotateX(-90deg); }

@media (prefers-reduced-motion: reduce) {
  .spin__bar { animation-play-state: paused; }
}

/* After `.spin`, not before it: both selectors are one class deep, so source
   order is the whole of the cascade between them and an earlier modifier loses
   `position` and `margin` back to the base rule.

   Sits on the wrap's own left edge, so it lines up with every other block on
   the page rather than at an arbitrary inset, and is centred by a negative
   margin rather than a translate -- the element carries preserve-3d and its own
   transform would be one more thing in that context to reason about. The copy
   is centred and narrow, so it never reaches this far out. */
.spin--cta {
  /* The geometry Michael dialled in the motion lab, in its own units rather
     than as fractions of the box -- so Size scales the rig with a transform
     instead of redefining every measurement. The footer's wheel keeps the
     base rule's values. */
  --radius: 30px; --w: 19px; --h: 27px; --t: 0px; --br: 4px;
  --rx: 2deg; --ry: -35deg; --rz: 45deg;
  /*
   * The box stays at the frame those measurements were dialled against and the
   * scale does all the resizing, so Size is the rendered size in pixels. It
   * used to size the box AND scale it, which multiplied the number by itself
   * over 132 -- 180 rendered at 245, and with no ceiling on the control that
   * runs away fast.
   */
  --spin-size: 132px;
  /* translate before scale, so a move of 40px is 40px on screen whatever the
     size: the leftmost function is the outermost, and is not scaled by the one
     to its right. */
  /*
   * `--spin-fit` and `--spin-dy` exist because the other three are written by
   * paintSpin as INLINE styles, which no stylesheet rule can override. These
   * two are only ever set in CSS, so a media query can still resize and move
   * the wheel without the panel and the breakpoint fighting each other.
   */
  transform: translate(var(--spin-x, 0px), calc(var(--spin-y, 0px) + var(--spin-dy, 0px)))
             scale(calc(var(--spin-scale, 1) * var(--spin-fit, 1) * var(--spin-near, 1)));
  /* Only the lean-in below ever changes this, and only on a pointer device, so
     the transition costs nothing at rest. */
  transition: transform 900ms cubic-bezier(0.16, 1, 0.3, 1);
  /* Centred on the section rather than sitting on the wrap's left edge: at this
     size it is a backdrop behind the copy, not an ornament beside it. The
     negative margins are half the box on each axis, so `50%` lands its centre
     on the section's centre and the transform then works from there. */
  position: absolute;
  top: 50%;
  left: 50%;
  margin: calc(var(--spin-size) * -0.5) 0 0 calc(var(--spin-size) * -0.5);
  opacity: var(--spin-opacity, 1);
  /* Decorative and aria-hidden, and at this size it covers the whole section --
     so it must not swallow a click or a text selection meant for the copy. */
  pointer-events: none;
}
/*
 * Below 1080px the centred copy reaches the wheel, so it stops being an
 * ornament beside the text and becomes a ground behind it -- smaller, fainter
 * and pushed down, rather than removed. The block has no other visual, and
 * taking it away left the closing section as centred text between two
 * hairlines on the whole of mobile and tablet.
 */
@media (max-width: 1080px) {
  .spin--cta { --spin-fit: 0.3; --spin-dy: -300px; opacity: 0.42; }
}
@media (max-width: 680px) {
  .spin--cta { --spin-fit: 0.22; --spin-dy: -400px; opacity: 0.34; }
}
.footer__meta {
  display: flex; flex-wrap: wrap; gap: 12px 28px; align-items: baseline;
  padding-bottom: 56px; font-size: 14px; color: var(--ink-3);
}
.footer__links { display: flex; gap: 20px; margin-left: auto; }
.footer__links a { text-decoration: none;
  /* 22px of text is below a thumb. The padding is the hit area and the
     negative margin keeps the row exactly where it was. */
  display: inline-block; padding: 10px 8px; margin: -10px -8px;
}
.footer__links a:hover { color: var(--ink); }
.footer__mark { color: var(--ink); line-height: 0; padding-bottom: 18px; }
.footer__mark svg { width: 100%; height: auto; display: block; aspect-ratio: 150.6 / 13.5; }

@media (max-width: 1080px) {
  :root { --rhythm: 112px; }
  .impact { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 900px) {
  /*
   * The menu. Below this width the four section links had no home at all --
   * they were simply display:none, which put Enterprise Platform, Clinician
   * Passport, What we verify and FAQ out of reach on a phone.
   *
   * The panel is absolutely positioned under the bar rather than pushing it
   * down, so opening the menu does not move the page behind it.
   */
  .nav__burger {
    display: grid;
    place-items: center;
    width: 40px; height: 40px;
    margin-left: auto;
    padding: 0;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--white);
    cursor: pointer;
  }
  /* Three rules from one element: the bar itself plus its two pseudo-elements,
     so the shape is one box to move rather than three to keep in step. */
  .nav__burger span,
  .nav__burger span::before,
  .nav__burger span::after {
    content: '';
    display: block;
    width: 17px; height: 1.5px;
    border-radius: 2px;
    background: var(--ink);
    transition: transform 220ms cubic-bezier(0.22, 1, 0.36, 1), opacity 140ms linear;
  }
  .nav__burger span { position: relative; }
  .nav__burger span::before { position: absolute; top: -5.5px; }
  .nav__burger span::after { position: absolute; top: 5.5px; }

  /* Open: the middle bar goes, the outer two cross. */
  .nav__burger[aria-expanded='true'] span { background: transparent; }
  .nav__burger[aria-expanded='true'] span::before { transform: translateY(5.5px) rotate(45deg); }
  .nav__burger[aria-expanded='true'] span::after { transform: translateY(-5.5px) rotate(-45deg); }

  .nav__drop {
    display: grid;
    gap: 18px;
    position: absolute;
    top: 100%; left: 0; right: 0;
    padding: 18px var(--gutter) 22px;
    background: var(--white);
    border-bottom: 1px solid var(--line);
    box-shadow: 0 18px 40px -26px oklch(0 0 0 / 0.3);
  }
  .nav__drop[hidden] { display: none; }

  .nav__links {
    display: grid;
    gap: 2px;
    margin-inline: 0;
  }
  .nav__links a {
    padding: 11px 0;
    font-size: 16px;
    color: var(--ink);
  }
  .nav__cta {
    margin-left: 0;
    padding-top: 6px;
    border-top: 1px solid var(--line-soft);
  }
  .nav__cta .btn { flex: 1; justify-content: center; }

  .orbit { --orbit-top: 30px; --orbit-side: 8%; }
  .orbit__label { font-size: 13px; }
  .orbit__icon { width: 22px; height: 22px; }
  .orbit__node { gap: 8px; }
  .split { grid-template-columns: 1fr; }
  .stage { height: 440px; }
}

@media (max-width: 680px) {
  :root { --gutter: 20px; --rhythm: 88px; }

  /*
   * Both nav actions on their own row. Side by side they need about 295px,
   * which does not fit beside the logo on any phone — and because the body
   * clips horizontal overflow, the second one was not scrolled off but simply
   * unreachable. Wrapping is width-independent, where shrinking the type to
   * fit is a bet on how wide two particular labels render.
   */
  body { font-size: 16px; }
  .hero { padding-top: 52px; }

  /* The actions live in the menu now rather than coming off the page. */
  .nav__inner { height: 56px; flex-wrap: nowrap; }

  /*
   * The widget gets room and its labels back. 270px was a letterbox: the disc
   * had to be tiny to fit, and the three labels were hidden because they would
   * not fit around it. Square-ish is the shape this graphic wants, so it takes
   * a share of the viewport instead of a fixed height, and the labels shrink
   * rather than disappear.
   */
  .stage { height: min(78vw, 420px); margin-top: 36px; }
  .orbit { visibility: visible; --orbit-top: 12px; --orbit-side: 2%; }
  .orbit__label { font-size: 11px; letter-spacing: -0.005em; }
  .orbit__icon { width: 19px; height: 19px; }
  .orbit__node { gap: 4px; }
  .impact { grid-template-columns: 1fr; }
  .footer__links { margin-left: 0; width: 100%; }
}

/*
 * The wheel leans in while the CTA button is hovered: it grows a little and
 * site.js eases its rotation down. Set from script rather than by `:hover` on
 * the section, because the rate and the scale have to move together -- and
 * deliberately NOT on focus. The button opens the demo dialog, and focus
 * returns to it on close, which would leave the wheel slowed and enlarged with
 * no pointer near it. `.surface` had exactly that bug via `:focus-within`.
 */
.spin--cta[data-near='true'] { --spin-near: 1.035; }

@media (prefers-reduced-motion: reduce) {
  .spin--cta { transition: none; }
}

/* A native <dialog>, so Esc, focus containment and the top layer come from the
   platform rather than from script. */
.modal {
  padding: 0;
  border: 0;
  background: none;
  max-width: min(560px, calc(100vw - 32px));
  width: 100%;
}
.modal::backdrop { background: oklch(20% 0.01 286 / 0.44); }
.modal__panel {
  position: relative;
  background: var(--white);
  border-radius: var(--radius);
  padding: 26px 26px 22px;
  box-shadow: 0 24px 60px -20px oklch(20% 0.02 286 / 0.28);
}
.modal__panel h2 { margin: 6px 0 8px; font-size: 23px; line-height: 1.2; }

/*
 * The detail panel. Stripe's equivalent is an in-page expander at content
 * width that scrolls with the document; this is a top-layer <dialog> that
 * scrolls internally instead. Same thing to look at, and Esc, focus
 * containment and the backdrop come from the platform rather than from script.
 */
/*
 * After Stripe's hds-dialog. Theirs is a fixed full-viewport overlay with
 * `overflow: auto` holding a dialog at its own natural height -- 2628px, when
 * measured -- so the whole dialog scrolls past rather than its content
 * scrolling inside a fixed frame. Here the <dialog> is that overlay itself: it
 * is already in the top layer and already covers the viewport, so it needs no
 * extra element around it. `overscroll-behavior` keeps the scroll from
 * chaining out to the page underneath once the end is reached.
 */
/* One column. Two fields side by side halved the measure of each for no gain
   -- the panel is 560px wide, and a name in 260px of it reads as cramped. */
/*
 * The booking dialog.
 *
 * Built to the canvas design, in this page's own tokens: --brand-500 for
 * --primary-500, --ink / --ink-2 / --ink-3 for the typography ramp, --line for
 * the borders, --red-700 for the errors. The one deliberate departure is the
 * heading face -- the design's --font-heading is Archivo here, which is what
 * every other h2 on the page is set in.
 *
 * The panel is a column: header, progress, body, footer. The footer is what
 * makes it one flow rather than three forms -- Back and the primary button are
 * in the same place on every step, and only their labels change.
 */
/*
 * Centred in the viewport, which a <dialog> does on its own -- the handoff
 * top-aligned it so height changes would grow downward, and the 440px floor
 * below makes that unnecessary: the panel is the same height on every step,
 * so there is no change to grow.
 */
/* Keeps the shared panel padding rather than zeroing it, so the 40px the
   sections carry sits inside another 26 -- and the footer's hairline stops
   short of the panel edge instead of cutting it in two. */
/* The dialog box itself does not clip, so the panel's shadow is not cut off at
   its own edge -- the panel is what has a shape, and it does its own clipping
   for the length of a resize. Scrolling is what overflow:auto was buying, so it
   comes back at a height where the tallest step no longer fits the viewport. */
.modal--book { overflow: visible; }
@media (max-height: 720px) {
  .modal--book { overflow: auto; }
}
.modal--book .modal__panel {
  --book-inset: 26px;
  /* Wider than the panel, so even the buttons -- which are a fraction of its
     width and sit well inside it -- are clear of the edge by the end. A
     percentage of each part's own width would leave the narrow ones parked
     halfway across. */
  --cascade-travel: 560px;
  border-radius: 16px;
  /*
   * The steps are different heights. A height transition needs a number at
   * both ends and the panel's is `auto` either side of a step change, so
   * site.js pins the old height, makes the swap, and sets the new one across
   * a forced reflow.
   */
  transition: height 100ms ease-out;
  /*
   * 180ms. It went 320 (the handoff's) -> 70 -> here: 70 read as the panel
   * being already there rather than arriving, which turned out to be too
   * abrupt for something that covers the page. 180 is long enough to see it
   * move and short enough not to wait for it.
   *
   * The travel scales with the duration -- 8px is a move at 180ms where it
   * would be a jump at 70.
   *
   * Worth knowing before changing it again: the duration is the smaller half
   * of the delay. showModal takes about 45ms to reach a first animated frame,
   * of which the page's own work is 6, so click-to-settled is the duration
   * plus roughly that.
   */
  animation: book-in 180ms cubic-bezier(0.2, 0.8, 0.2, 1) both;
}
@keyframes book-in {
  from { opacity: 0; transform: translateY(8px) scale(0.985); }
  to { opacity: 1; transform: none; }
}

/* The dim comes up with the panel rather than snapping in under it. */
/*
 * The page goes to a near-solid zinc ground rather than a dim over it, so the
 * panel reads as sitting on a surface instead of floating over a page still
 * asking to be read. The blur is on the backdrop and not on the page: the hero
 * holds two canvases mid-animation, and a filter over them would cost a pass
 * per frame instead of one here.
 */
#demo-modal::backdrop {
  background: oklch(96.7% 0.001 286.375 / 0.92);
  backdrop-filter: blur(10px);
}
#demo-modal[open]::backdrop { animation: dim-in 180ms ease both; }
@keyframes dim-in {
  from { opacity: 0; }
  to { opacity: 1; }
}
/*
 * A step leaves in a cascade: the question first, then the lede, the fields,
 * and last the two buttons, each a beat behind the one above it, so the step
 * reads as being swept out rather than cut. The next one arrives the same way
 * from the far side. The close button is not part of this -- it closes the
 * dialog, not the step -- which is what keeps it still throughout.
 *
 * site.js decides what the parts are, since that depends on the step, and
 * numbers them in --cascade.
 */
.book__part {
  animation: 400ms ease-out both;
  animation-delay: calc(var(--cascade, 0) * 30ms);
}
.book[data-cascade='out'] .book__part { animation-name: cascade-out-left; }
.book[data-cascade='in'] .book__part { animation-name: cascade-in-right; }
.book[data-cascade='out-back'] .book__part { animation-name: cascade-out-right; }
.book[data-cascade='in-back'] .book__part { animation-name: cascade-in-left; }

/* Clipped only while the cascade runs, so the parts vanish at the panel edge
   rather than crossing the page, and the panel does not clip the rest of the
   time. */
.modal--book .modal__panel[data-cascade] { overflow: hidden; }

/* The fade finishes before the travel does, so what leaves is already
   invisible by the time it reaches the edge. */
@keyframes cascade-out-left {
  from { opacity: 1; transform: none; }
  60% { opacity: 0; }
  to { opacity: 0; transform: translateX(calc(var(--cascade-travel) * -1)); }
}
@keyframes cascade-out-right {
  from { opacity: 1; transform: none; }
  60% { opacity: 0; }
  to { opacity: 0; transform: translateX(var(--cascade-travel)); }
}
@keyframes cascade-in-right {
  from { opacity: 0; transform: translateX(var(--cascade-travel)); }
  45% { opacity: 1; }
  to { opacity: 1; transform: none; }
}
@keyframes cascade-in-left {
  from { opacity: 0; transform: translateX(calc(var(--cascade-travel) * -1)); }
  45% { opacity: 1; }
  to { opacity: 1; transform: none; }
}

/* The spacer between body and footer is what pins the footer to the bottom
   when a step is short. */
.book, .book form { display: flex; flex-direction: column; min-height: 440px; }
.book form { flex: 1; }
.book__body { flex: 1; }

.book__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 26px var(--book-inset) 0;
}
/* The step's own question is the title. Each step asks one thing, so a
   standing headline over a changing field would be the smaller half of what
   the reader needs. */
.book__head h2 { margin: 0; font-size: 26px; line-height: 1.18; letter-spacing: -0.02em; }
.book__lede { margin: 8px 0 0; font-size: 15px; line-height: 1.5; color: var(--ink-2); }

/* The panel's corner rather than the header's end: it closes the dialog, not
   the step, and out of the header's flow it stops setting the title's width. */
.book__close {
  position: absolute;
  top: 14px;
  right: 14px;
  display: grid;
  place-items: center;
  width: 32px; height: 32px;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--ink-3);
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease;
}
@media (hover: hover) {
  .book__close:hover { background: var(--zinc-100); color: var(--ink); }
}

/* Starts under the header, as it reads. The floor leaves room below on the
   short steps, and the footer is pinned to the bottom of it. */
.book__body { padding: 16px var(--book-inset) 20px; }
.step { display: grid; gap: 12px; }
@keyframes step-prev {
  from { opacity: 0; transform: translateX(-18px); }
  to { opacity: 1; transform: none; }
}

/*
 * The label lives inside the field's box, above the input, and the box carries
 * the border. One target: the whole box is the label, so a click anywhere in
 * it reaches the input.
 */
.field { display: grid; gap: 7px; }
.field__box {
  display: block;
  padding: 8px 14px 9px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--white);
  cursor: text;
  transition: border-color 120ms ease, box-shadow 120ms ease;
}
.field__label {
  display: block;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.01em;
  color: var(--ink-2);
}
.field__box input {
  width: 100%;
  height: 24px;
  margin-top: 2px;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 15px;
  color: var(--ink);
}
.field__box input:focus { outline: none; }
.field__box input::placeholder { color: var(--ink-3); }
.field__box:focus-within {
  border-color: var(--brand-500);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--brand-500) 18%, transparent);
}
.field[data-bad] .field__box,
.field[data-bad] .field__box:focus-within {
  border-color: var(--red-700);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--red-700) 14%, transparent);
}

.field__error {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0;
  font-size: 12px;
  color: var(--red-700);
}
.field__error svg { flex: none; width: 13px; height: 13px; }

.choices { display: grid; gap: 8px; }

/*
 * One tap is the answer and the step. These are buttons in a radiogroup rather
 * than radios with a Continue beside them: the choice already carries the
 * intent, and asking for a second gesture to confirm it is the thing this flow
 * exists to remove.
 */
.choice {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 48px;
  padding: 0 14px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--white);
  font: inherit;
  font-size: 15px;
  font-weight: 500;
  color: var(--ink);
  cursor: pointer;
  transition: border-color 120ms ease, background-color 120ms ease;
}
@media (hover: hover) {
  .choice:hover { border-color: var(--zinc-300); }
}
.choice:focus-visible { outline: 2px solid var(--brand-500); outline-offset: 2px; }
.choice[aria-checked='true'] {
  border-color: var(--brand-500);
  background: var(--brand-50);
}
/* The tick holds its space whether or not it is shown, so choosing an option
   does not shunt its label sideways. */
.choice__tick { display: grid; place-items: center; width: 16px; height: 16px; color: var(--brand-500); }
.choice__tick svg { width: 16px; height: 16px; opacity: 0; transition: opacity 120ms ease; }
.choice[aria-checked='true'] .choice__tick svg { opacity: 1; }

.book__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 20px var(--book-inset) 26px;
}
.book__back {
  display: flex;
  align-items: center;
  gap: 0;
  height: 40px;
  padding: 0 12px;
  border: 0;
  border-radius: 8px;
  background: none;
  font: inherit;
  font-size: 14px;
  font-weight: 500;
  color: var(--ink-2);
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease;
}
@media (hover: hover) {
  .book__back:hover { background: var(--zinc-100); color: var(--ink); }
}
.book__go { gap: 0; }
/* Both chevrons opt out of shrinking: an inline SVG's automatic minimum size
   is zero, so in a flex row it collapses rather than pushing the label. */
/* The chevrons close their own width rather than leaving the flow, and carry
   the gap beside them as their own margin so it collapses with them. */
.book__back-icon,
.book__go-icon {
  flex: none;
  overflow: hidden;
}
.book__back-icon { margin-right: 5px; }
.book__go-icon { margin-left: 7px; }
.book__back-icon[data-off],
.book__go-icon[data-off] { width: 0; opacity: 0; }
.book__back-icon[data-off] { margin-right: 0; }
.book__go-icon[data-off] { margin-left: 0; }

.book__back [data-back-label],
.book__go [data-go-label] { white-space: nowrap; }

.book__done {
  display: grid;
  justify-items: center;
  text-align: center;
  gap: 16px;
  padding: 64px 44px 56px;
}
.book__done-tick {
  display: grid;
  place-items: center;
  width: 56px; height: 56px;
  border-radius: 999px;
  background: var(--emerald-50);
  color: var(--emerald-700);
}
.book__done-tick svg { width: 28px; height: 28px; }
.book__done h2 { margin: 0; font-size: 26px; letter-spacing: -0.02em; }
.book__done p { margin: 0; font-size: 15px; line-height: 1.55; color: var(--ink-2); max-width: 38ch; }
.book__done strong { color: var(--ink); font-weight: 600; }

@media (max-width: 560px) {
  .modal--book .modal__panel { --book-inset: 20px; }
  .book__head h2 { font-size: 22px; }
  .book__done { padding: 48px 24px 40px; }
}
@media (prefers-reduced-motion: reduce) {
  .choice, .field__box, .choice__tick svg,
  .modal--book .modal__panel { transition: none; }
  .modal--book .modal__panel, .book__part, #demo-modal[open]::backdrop { animation: none; }
}
