/* ==========================================================================
   Placeholder Studio — stylesheet
   Built exclusively from the tokens in tokens.css.
   Structure and motion mirror the extracted design system; all content is
   generic placeholder copy.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Reset (matches the Tailwind preflight behaviours the source relies on)
   -------------------------------------------------------------------------- */
*,
*::before,
*::after { box-sizing: border-box; }

* { margin: 0; }

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  position: relative;
  min-height: 100vh;
  background-color: var(--bg-primary);
  color: hsl(var(--foreground));
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--lh-base);
  font-feature-settings: "rlig" 1, "calt" 1;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

body.lenis-active { height: auto; }

img, svg, video { display: block; max-width: 100%; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
a { color: inherit; text-decoration: none; }
ul { list-style: none; padding: 0; }

::selection { background: var(--accent); color: #fff; }

/* --------------------------------------------------------------------------
   Page colour field
   One continuous gradient spanning the entire document, sitting behind every
   section. Sections are transparent, so there is no per-section background to
   seam against — the colour simply travels as you scroll.
   Every stop is an extracted token: the surface tints for the vertical wash,
   the accent family for the blooms. Bloom alpha is capped at 0.13 so muted
   body text stays above 4.5:1 everywhere.
   -------------------------------------------------------------------------- */
body::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    /* Soft accent blooms, alternating side to side down the page */
    radial-gradient(62% 20% at 12% 13%, rgba(20, 225, 193, 0.13), transparent 70%),
    radial-gradient(56% 17% at 88% 26%, rgba(76, 229, 199, 0.12), transparent 72%),
    radial-gradient(70% 19% at 18% 40%, rgba(45, 232, 204, 0.11), transparent 70%),
    radial-gradient(58% 17% at 84% 54%, rgba(20, 225, 193, 0.12), transparent 72%),
    radial-gradient(74% 21% at 28% 68%, rgba(16, 198, 169, 0.10), transparent 70%),
    radial-gradient(64% 19% at 78% 82%, rgba(20, 225, 193, 0.11), transparent 72%),
    /* Continuous vertical wash through the surface tints */
    linear-gradient(
      to bottom,
      var(--surface-tint-2)   0%,
      var(--surface-tint-4)   7%,
      var(--bg-primary)      17%,
      var(--surface-tint-1)  30%,
      var(--surface-tint-5)  44%,
      var(--surface-tint-3)  57%,
      var(--bg-primary)      69%,
      var(--surface-tint-4)  81%,
      var(--surface-tint-2)  93%,
      var(--border-hairline) 100%
    );
}

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

/* --------------------------------------------------------------------------
   Layout primitives
   -------------------------------------------------------------------------- */
.section {
  position: relative;
  width: 100%;
  /* Transparent so the page field shows through — an opaque section
     background here is exactly what would create a visible seam. */
  background-color: transparent;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* Section rhythm recipe A — py-20 md:py-32 */
.section--rhythm-a { padding-block: 5rem; }
/* Section rhythm recipe B — pt-8 pb-20 md:pt-10 md:pb-24 lg:pt-12 lg:pb-32 */
.section--rhythm-b { padding-top: 2rem; padding-bottom: 5rem; }
/* Section rhythm recipe C — pt-20 pb-12 md:pt-24 md:pb-16 lg:pt-32 lg:pb-20 */
.section--rhythm-c { padding-top: 5rem; padding-bottom: 3rem; }
/* Section rhythm recipe D — pt-0 pb-6 md:pb-8 (logo strip) */
.section--rhythm-d { padding-top: 0; padding-bottom: 1.5rem; }

.rail {
  width: 100%;
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.rail--4xl { max-width: calc(var(--rail-4xl) + var(--gutter) * 2); }
.rail--5xl { max-width: calc(var(--rail-5xl) + var(--gutter) * 2); }
.rail--6xl { max-width: calc(var(--rail-6xl) + var(--gutter) * 2); }
.rail--7xl { max-width: calc(var(--rail-7xl) + var(--gutter) * 2); }

/* --------------------------------------------------------------------------
   Typography
   -------------------------------------------------------------------------- */
.display,
h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: var(--fw-semibold);
  color: var(--text-strong);
  letter-spacing: var(--tracking-tight);
}

h1, .h1 {
  font-size: 10vw;
  line-height: var(--leading-display);
}

h2, .h2 {
  font-size: 10vw;
  line-height: var(--leading-display);
}

h3, .h3 {
  font-size: 22px;
  line-height: var(--leading-tight);
}

/* Highlighted word inside a heading.
   The brand teal (#14E1C1) only reaches 1.59:1 on the light field, so this uses
   --accent-dark (#0A8F7A) at 3.83:1 — passes AA for large text, which every
   heading using it is. Do not swap this for --accent without rechecking. */
.accent-word {
  /* Instrument Serif italic against the DM Sans heading — the same sans +
     italic-serif pairing the extracted design system uses for accents. */
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: var(--fw-normal);
  letter-spacing: var(--tracking-tight);
  color: var(--accent-dark);
}

.serif-accent {
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: var(--fw-normal);
  letter-spacing: var(--tracking-tight);
}

.lede {
  font-family: var(--font-body);
  font-weight: var(--fw-medium);
  font-size: var(--text-base);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-tight);
  color: var(--text-body);
}

.body-copy {
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  color: var(--text-muted);
}

/* Mono eyebrow — the site's signature label treatment */
.eyebrow {
  font-family: var(--font-mono);
  font-weight: var(--fw-bold);
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow-sm);
  color: var(--text-muted);
}
.eyebrow--accent { color: var(--accent); }

/* Line break that only applies at the lg breakpoint (matches `hidden lg:block`) */
.lg-only { display: none; }

/* Accent rule that animates from width 0 → 48px on scroll */
.accent-rule {
  width: 0;
  height: 2px;
  margin-top: 0.5rem;
  border-radius: var(--radius-pill);
  background-image: var(--grad-rule);
  transition: width var(--dur-reveal) var(--ease-expo);
  transition-delay: 0.6s;
}
.is-visible .accent-rule,
.accent-rule.is-visible { width: 48px; }

/* --------------------------------------------------------------------------
   Global overlays — grain + custom cursor
   -------------------------------------------------------------------------- */
.grain {
  position: fixed;
  inset: 0;
  z-index: 9999;
  pointer-events: none;
  opacity: var(--grain-opacity);
  mix-blend-mode: multiply;
  background-repeat: repeat;
  /* Self-contained equivalent of the source's tiled /grain.gif */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
  animation: grain-shift 0.6s steps(1) infinite;
}

@keyframes grain-shift {
  0%   { background-position: 0 0; }
  25%  { background-position: -40px 20px; }
  50%  { background-position: 30px -30px; }
  75%  { background-position: -20px -10px; }
  100% { background-position: 0 0; }
}

.cursor-dot {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 99999;
  pointer-events: none;
  will-change: transform;
  mix-blend-mode: difference;
  opacity: 0;
  transform: translate(-50%, -50%) scale(0);
}
.cursor-dot__inner {
  width: 0.75rem;
  height: 0.75rem;
  border-radius: var(--radius-pill);
  background: #fff;
}

/* --------------------------------------------------------------------------
   Nav — fixed, mix-blend-difference, enters from translateY(-50px)
   -------------------------------------------------------------------------- */
.nav {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  z-index: 50;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1.5rem var(--gutter);
  mix-blend-mode: difference;
  color: #fff;
  pointer-events: none;
  opacity: 0;
  transform: translateY(-50px);
  animation: nav-in var(--dur-reveal) var(--ease-house) 0.1s both;
}

@keyframes nav-in {
  from { opacity: 0; transform: translateY(-50px); }
  to   { opacity: 1; transform: none; }
}

.nav__logo {
  font-family: var(--font-display);
  font-weight: var(--fw-semibold);
  font-size: var(--text-2xl);
  letter-spacing: var(--tracking-tighter);
  pointer-events: auto;
  z-index: 50;
}
.nav__logo em {
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: var(--fw-normal);
  padding-right: 0.25rem;
}

/* Inline links — hidden below md, where the hamburger takes over */
.nav__links {
  display: none;
  pointer-events: auto;
  z-index: 50;
  align-items: center;
  gap: 0.25rem;
}
.nav__links a {
  /* Padding carries the 44px touch target without inflating the type */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 0.875rem;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow-sm);
  color: #fff;
  opacity: 0.75;
  transition: opacity var(--dur-300) var(--ease-house);
}
.nav__links a:hover { opacity: 1; }

.nav__toggle {
  pointer-events: auto;
  z-index: 50;
  min-width: 44px;
  min-height: 44px;
  padding: 0.5rem;
  margin-right: -0.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  transition: transform var(--dur-300) var(--ease-house);
}
.nav__toggle:active { transform: scale(0.95); }

.nav__icon {
  position: relative;
  width: 1.5rem;
  height: 1.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
}
.nav__icon span {
  position: absolute;
  border-radius: var(--radius-pill);
  background: currentColor;
  transition: transform var(--dur-500) var(--ease-house),
              opacity var(--dur-300) var(--ease-house);
}
.nav__icon span:nth-child(1) { width: 100%; height: 2px; }
.nav__icon span:nth-child(2) { height: 100%; width: 2px; transform-origin: center; }
.nav[data-open="true"] .nav__icon { transform: rotate(45deg); }
.nav[data-open="true"] .nav__icon span:nth-child(2) { opacity: 0; }

/* Slide-down menu panel */
.nav-menu {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: var(--bg-dark);
  color: #fff;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: flex-start;
  padding: 6rem var(--gutter) var(--gutter);
  gap: 0.5rem;
  clip-path: inset(0 0 100% 0);
  transition: clip-path var(--dur-700) var(--ease-house);
  pointer-events: none;
}
.nav-menu[data-open="true"] {
  clip-path: inset(0 0 0 0);
  pointer-events: auto;
}
.nav-menu a {
  font-family: var(--font-display);
  font-weight: var(--fw-semibold);
  font-size: 12vw;
  line-height: var(--leading-tight-x);
  letter-spacing: var(--tracking-tight);
  opacity: 0;
  transform: translateY(40px);
  transition: opacity var(--dur-500) var(--ease-house),
              transform var(--dur-500) var(--ease-house),
              color var(--dur-300) var(--ease-house);
  transition-delay: calc(0.2s + var(--i) * 0.06s);
}
.nav-menu[data-open="true"] a { opacity: 1; transform: none; }
.nav-menu a:hover { color: var(--accent); }

/* --------------------------------------------------------------------------
   Button — gradient pill with cursor-tracking radial sheen
   -------------------------------------------------------------------------- */
.btn {
  position: relative;
  overflow: hidden;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: fit-content;
  max-width: 90vw;
  padding: 18px 3rem;
  border-radius: var(--radius-pill);
  border: 1px solid var(--accent-deep);
  background-image: var(--grad-button);
  box-shadow: var(--shadow-btn);
  transition: background-image var(--dur-300) var(--ease-house),
              box-shadow var(--dur-300) var(--ease-house),
              transform var(--dur-300) var(--ease-house);
}
.btn:hover {
  background-image: var(--grad-button-hover);
  box-shadow: var(--shadow-btn-hover);
  transform: translateY(-1px);
}
.btn:active { transform: scale(0.97); }

.btn__sheen {
  position: absolute;
  inset: -1px;
  border-radius: var(--radius-pill);
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--dur-700) var(--ease-house);
  background: radial-gradient(
    150px circle at var(--mx, 0px) var(--my, 0px),
    rgba(255, 255, 255, 0.4),
    transparent 80%
  );
}
.btn:hover .btn__sheen { opacity: 1; }

.btn__label {
  position: relative;
  z-index: 10;
  font-family: var(--font-body);
  font-size: 10px;
  font-weight: var(--fw-bold);
  line-height: var(--leading-none);
  text-transform: uppercase;
  letter-spacing: var(--tracking-widest);
  color: var(--text-strong);
  margin-top: 2px;
}

/* Full-width variant for the plan cards */
.btn--block {
  display: flex;
  width: 100%;
  max-width: none;
  padding-inline: 1.5rem;
}

.btn--ghost {
  background-image: none;
  background-color: transparent;
  border: 1px solid var(--rule-soft);
  box-shadow: none;
  padding: 14px 2rem;
}
.btn--ghost:hover {
  background-color: var(--surface-neutral-4);
  box-shadow: none;
  transform: translateY(-1px);
}

/* --------------------------------------------------------------------------
   Hero
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  width: 100%;
  background-color: transparent;
  overflow: hidden;            /* clips the full-bleed name — no horizontal scroll */
  display: flex;
  flex-direction: column;
  justify-content: flex-end;   /* portrait grows upward from the section base */
  padding-top: var(--nav-offset);
  padding-bottom: 0;

  /* Single source of truth for the portrait's size. The name is positioned
     against this too, so the two stay locked together at every breakpoint. */
}

/* Stand-in for the source's teal-clouds hero bitmap, built from the palette */
.hero__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  user-select: none;
  background:
    radial-gradient(120% 80% at 50% -10%, rgba(20, 225, 193, 0.55) 0%, rgba(20, 225, 193, 0) 60%),
    radial-gradient(70% 50% at 12% 0%, rgba(45, 232, 204, 0.45) 0%, rgba(45, 232, 204, 0) 65%),
    radial-gradient(80% 55% at 88% 8%, rgba(76, 229, 199, 0.40) 0%, rgba(76, 229, 199, 0) 70%),
    radial-gradient(100% 60% at 50% 0%, rgba(255, 255, 255, 0.65) 0%, rgba(250, 252, 251, 0) 55%),
    /* Ends fully transparent, not on an opaque colour, so the hero dissolves
       into the page field instead of stopping on a hard edge. */
    linear-gradient(to bottom, rgba(238, 243, 241, 0.9), rgba(250, 252, 251, 0) 72%);
  filter: blur(0.5px);
}

/* --------------------------------------------------------------------------
   Hero — display type with the cutout standing in front of it.

   The cutout PNG carries a real alpha channel, so the letters pass behind the
   actual silhouette. There is no photo background: the page IS the background.
   Layer order: .hero__bg (0) → .hero__display (1) → .hero__cutout (2) → UI (3)
   -------------------------------------------------------------------------- */
.hero__stage {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: calc(var(--rail-7xl) + var(--gutter) * 2);
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: flex;
  flex-direction: column;
  align-items: center;

  /* Sized by WIDTH, not height. How much of the name stays readable either
     side of the figure is a horizontal question, so driving it from width
     keeps that constant if the photo's aspect ratio ever changes again. */
  --cutout-w: clamp(165px, 34vw, 470px);
  /* Nudge right of centre so more of the name reads before the silhouette —
     centred, only the "R" cleared the figure and it looked like a stray letter. */
  --cutout-shift: calc(var(--cutout-w) * 0.19);
}

.hero__intro {
  position: relative;
  z-index: 3;
  text-align: center;
  font-family: var(--font-body);
  font-size: clamp(0.9rem, 3.4vw, 1.25rem);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-tight);
  color: var(--text-body);
  margin-bottom: 0.75rem;
}

/* --- Display type ---------------------------------------------------------- */
.hero__display {
  position: relative;
  z-index: 1;
  margin: 0;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  pointer-events: none;
  user-select: none;
}

.hero__line {
  display: block;
  font-family: var(--font-display);
  font-weight: var(--fw-semibold);
  /* Scales with the viewport but stays large, per the reference */
  font-size: clamp(2.6rem, 22vw, 220px);
  line-height: 0.86;
  letter-spacing: var(--tracking-tighter);
  white-space: nowrap;
}

.hero__line--solid { color: var(--ink-900); }

/* Outlined weight, matching the reference's second line.
   Note: DM Sans builds its R from overlapping contours, and a stroke outlines
   every contour — including the edges buried inside the overlap — so a faint
   seam shows where the leg meets the bowl. That is inherent to the glyph, not
   to these values; only filling the letter removes it. */
.hero__line--outline {
  color: transparent;
  -webkit-text-stroke: 2px var(--ink-900);
          text-stroke: 2px var(--ink-900);
}
/* Without text-stroke the fill would be invisible, so fall back to solid ink */
@supports not ((-webkit-text-stroke: 2px black) or (text-stroke: 2px black)) {
  .hero__line--outline { color: var(--ink-900); }
}

/* --- Cutout ---------------------------------------------------------------- */
/* In flow, pulled upward by a fraction of its own width. Anchoring the overlap
   to the figure itself — rather than to the bottom of the section — keeps the
   letters-behind-the-silhouette effect identical at every viewport, and makes
   it independent of the photo's aspect ratio. */
.hero__figure {
  position: relative;
  /* Deliberately NO z-index. With one, this became a stacking context and its
     ::before band was trapped above the display type, tinting the bottom fifth
     of every letter. The z-index now lives on the image instead, so the band
     (0) sits under the type (1) and the cutout (2) stays over it. */
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  margin-top: calc(var(--cutout-w) * -0.50);
  pointer-events: none;
}

.hero__cutout {
  display: block;
  position: relative;
  z-index: 2;                 /* over the display type; the band stays under it */
  width: var(--cutout-w);
  height: auto;
  margin-left: var(--cutout-shift);
  /* No mask and no vignette — the alpha channel is the blend. */
}

/* Colour band behind the lower torso.
   Ramps from clear up to --hero-top, reaching it fully where the cutout is
   cropped, so the hard bottom edge of the photo lands on a matching field
   instead of against a contrasting one.

   It is a SIBLING of .hero__figure, deliberately. As a child it inherited the
   figure's z-index of 2 and painted over the display type, tinting the bottom
   fifth of every letter — which made the R read as two separate pieces. At
   z-index 0 it sits above the hero background but below the type.

   Height tracks the cutout: image aspect is 1000x1091, so the figure is
   1.091 x --cutout-w tall, and the band covers 62% of that. */
.hero__figure::before {
  content: "";
  position: absolute;
  left: 50%;
  margin-left: -50vw;
  width: 100vw;
  bottom: 0;
  height: 62%;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(
    to bottom,
    rgba(149, 240, 225, 0) 0%,
    rgba(149, 240, 225, 0.45) 55%,
    var(--hero-top) 100%
  );
}

/* --- Meta row + actions ---------------------------------------------------- */
.hero__meta {
  position: relative;
  z-index: 3;
  width: 100%;
  margin-top: 1.5rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
  text-align: center;
}

.hero__place {
  font-family: var(--font-body);
  font-size: clamp(0.9rem, 3.2vw, 1.125rem);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-tight);
  color: var(--text-body);
}

.hero__actions {
  position: relative;
  z-index: 3;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  /* The figure now occupies real flow space, so this only needs breathing
     room rather than reserving the whole cutout height. */
  margin-top: 1.5rem;
  padding-bottom: 3rem;
}

.btn__label--dark { color: var(--ink-900); }
/* Three-step mono row */
.hero__steps {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.375rem;
  margin-top: 1.5rem;
  width: 100%;
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow-sm);
  color: var(--text-muted);
}
.hero__steps .step { display: flex; align-items: center; gap: 0.5rem; }
.hero__steps .step b { font-weight: var(--fw-bold); color: var(--text-subtle); }
.hero__steps .step span { color: var(--text-strong); }
.hero__steps .step--live b { color: var(--accent); }
.hero__steps .sep { color: var(--text-faint); font-size: var(--text-sm); }

/* Pulsing availability dot */
.hero__status {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: 0.625rem;
  margin-top: 1rem;
  opacity: 0.8;
}
.pulse-dot {
  position: relative;
  display: flex;
  height: 0.5rem;
  width: 0.5rem;
  flex-shrink: 0;
}
.pulse-dot i {
  position: absolute;
  display: inline-flex;
  height: 100%;
  width: 100%;
  border-radius: var(--radius-pill);
  background: var(--accent);
  opacity: 0.75;
  animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
}
.pulse-dot em {
  position: relative;
  display: inline-flex;
  height: 0.5rem;
  width: 0.5rem;
  border-radius: var(--radius-pill);
  background: var(--accent);
}
@keyframes ping {
  75%, 100% { transform: scale(2); opacity: 0; }
}
.hero__status span {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--text-muted);
  transform: translateY(1px);
}

/* Hero entrance — the source's exact CSS keyframe, staggered by inline delay */
@keyframes hero-rise {
  0%  { opacity: 0; transform: translateY(20px); }
  100% { opacity: 1; transform: none; }
}
.hero-rise {
  animation: hero-rise var(--dur-reveal) var(--ease-house) both;
  will-change: transform, opacity;
}

/* --------------------------------------------------------------------------
   Ambient orb — CSS stand-in for the Three.js ScrollBlob3D, driven by
   --blob-progress (0→1) written from JS on scroll.
   -------------------------------------------------------------------------- */
.blob-anchor {
  position: relative;
  width: 100%;
  /* The orb inside is min(70vw, 620px) wide, translated -50% and scaled up to
     1.25x, so it reaches past the viewport edge — and vw units include the
     scrollbar. That was adding ~19px of real horizontal scroll to the page.
     `clip` rather than `hidden`: hidden would force overflow-y to auto here
     and turn this into a scroll container. */
  overflow-x: clip;
}

.blob {
  position: absolute;
  z-index: 0;
  pointer-events: none;
  width: min(70vw, 620px);
  aspect-ratio: 1;
  left: 50%;
  top: 12%;
  border-radius: 50%;
  filter: blur(60px);
  opacity: 0.55;
  background:
    radial-gradient(45% 45% at 35% 30%, var(--blob-light-white) 0%, transparent 60%),
    radial-gradient(50% 50% at 70% 25%, var(--blob-light-warm) 0%, transparent 65%),
    radial-gradient(60% 60% at 30% 75%, var(--blob-light-cool) 0%, transparent 70%),
    radial-gradient(75% 75% at 50% 55%, var(--accent) 0%, rgba(20, 225, 193, 0) 72%);
  transform:
    translate(-50%, calc(var(--blob-progress, 0) * 55vh))
    rotate(calc(var(--blob-progress, 0) * 140deg))
    scale(calc(0.8 + var(--blob-progress, 0) * 0.45));
  animation: blob-morph 12s var(--ease-in-out) infinite;
  will-change: transform;
}
@keyframes blob-morph {
  0%, 100% { border-radius: 48% 52% 60% 40% / 55% 45% 55% 45%; }
  33%      { border-radius: 60% 40% 45% 55% / 40% 60% 40% 60%; }
  66%      { border-radius: 42% 58% 52% 48% / 62% 38% 62% 38%; }
}

/* Soft ambient orbs used by FAQ / Comparison in the source */
.orb {
  position: absolute;
  border-radius: 50%;
  background: var(--accent-05);
  filter: blur(120px);
  pointer-events: none;
  z-index: 0;
}
.orb--a { top: 0; left: -10%; width: 600px; height: 600px; }
.orb--b { top: 0; right: 25%; width: 500px; height: 500px; }

/* --------------------------------------------------------------------------
   Scroll reveal — IntersectionObserver toggles .is-visible
   -------------------------------------------------------------------------- */
.reveal {
  opacity: 0;
  transform: translateY(30px);
  transition:
    opacity var(--dur-reveal) var(--ease-house),
    transform var(--dur-reveal) var(--ease-house);
  transition-delay: calc(0.1s + var(--i, 0) * var(--stagger));
  will-change: transform, opacity;
}
.reveal.is-visible { opacity: 1; transform: none; }

.reveal--far { transform: translateY(80px); }
.reveal--near { transform: translateY(16px); }
.reveal--quint {
  transition-timing-function: var(--ease-quint);
  transition-duration: var(--dur-700);
}
.reveal--expo {
  transition-timing-function: var(--ease-expo);
}

/* Section head block shared by every content section */
.section-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.75rem;
  margin-bottom: 3rem;
}
.section-head--left { align-items: flex-start; text-align: left; }
.section-head h2 { max-width: 20ch; }
.section-head .lede { max-width: 52ch; }

/* --------------------------------------------------------------------------
   Featured case study (Portfolio)
   Replaces the old six-card grid. Stacks on small screens; from lg the shot
   and the write-up sit side by side.
   -------------------------------------------------------------------------- */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.case {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2rem;
  width: 100%;
  align-items: start;
}

/* --- Coverflow carousel ----------------------------------------------------
   Portrait cards in a band. The centre card is upright at z = 0; the others
   yaw inward and recede.

   Sharpness rule, learned the hard way: a card is rasterised at its CSS size
   and only afterwards 3D-transformed. Anything at positive z gets magnified by
   the perspective, which upscales that already-rendered bitmap and looks
   blurry no matter how large the source image is. So the centre card sits at
   exactly z = 0 and every other card has negative z — scaled DOWN, which never
   blurs.
   --------------------------------------------------------------------------- */
.carousel {
  position: relative;
  width: 100%;
  /* 1018 x 1080 — very slightly taller than square.
     Height factor is 1080/1018. Cards are wider than the old 2:3 portrait
     because this shape is much shorter, so there is room in the column. */
  --card-w: clamp(215px, 58vw, 520px);
  --card-h: calc(var(--card-w) * 1.0609);
}

/* Clouds sitting behind the cards, in the colour from the top of the page */
.carousel__sky {
  position: absolute;
  inset: -12% -6% -6%;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  border-radius: var(--radius-card);
}

.cloud {
  position: absolute;
  border-radius: 50%;
  filter: blur(38px);
  opacity: 0.55;
  background:
    radial-gradient(closest-side, var(--hero-top) 0%, rgba(149, 240, 225, 0.55) 55%, rgba(149, 240, 225, 0) 100%);
  will-change: transform;
}
/* Sizes and drift speeds staggered so the bank never reads as a repeating loop */
.cloud--1 { width: 46%; height: 58%; left: -14%; top:  6%; animation: cloud-drift 41s linear infinite; }
.cloud--2 { width: 34%; height: 44%; left:  28%; top: -8%; animation: cloud-drift 58s linear infinite -12s; opacity: 0.4; }
.cloud--3 { width: 52%; height: 62%; left:  62%; top: 12%; animation: cloud-drift 49s linear infinite -26s; }
.cloud--4 { width: 30%; height: 40%; left:  46%; top: 46%; animation: cloud-drift 66s linear infinite -40s; opacity: 0.34; }

@keyframes cloud-drift {
  from { transform: translateX(-14%); }
  50%  { transform: translateX(14%); }
  to   { transform: translateX(-14%); }
}

.carousel__viewport {
  position: relative;
  z-index: 1;
  width: 100%;
  height: calc(var(--card-h) + 3rem);
  perspective: 1250px;
  perspective-origin: 50% 50%;
  cursor: grab;
  touch-action: pan-y;
  -webkit-user-select: none;
  user-select: none;
}
.carousel__viewport:active { cursor: grabbing; }

.carousel__ring {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  /* No `will-change: transform` — promoting this to its own layer reintroduces
     fixed-scale rasterisation, which is a second way these go soft. */
}

.carousel__card {
  position: absolute;
  top: 50%;
  left: 50%;
  width: var(--card-w);
  height: var(--card-h);
  margin: calc(var(--card-h) / -2) 0 0 calc(var(--card-w) / -2);
  border-radius: var(--radius-card);
  overflow: hidden;
  background: var(--ink-900);
  box-shadow: 0 18px 44px rgba(10, 15, 13, 0.22);
  opacity: var(--depth, 1);
  /* transform written per frame from JS */
}

.carousel__card img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  pointer-events: none;
}

/* --- Overlay, only shown on the centre card ------------------------------- */
.carousel__overlay {
  position: absolute;
  inset: auto 0 0 0;
  padding: 1.1rem 1rem 1.2rem;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.4rem;
  color: #fff;
  text-align: left;
  background: linear-gradient(to top, rgba(10, 15, 13, 0.82) 0%, rgba(10, 15, 13, 0.45) 55%, rgba(10, 15, 13, 0) 100%);
  opacity: 0;
  transition: opacity var(--dur-500) var(--ease-house);
}
.carousel__card[data-front] .carousel__overlay { opacity: 1; }

.carousel__badge {
  position: absolute;
  top: calc(-1 * var(--card-h) + 1.6rem);
  right: 0.9rem;
  padding: 0.28rem 0.6rem;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.22);
  backdrop-filter: blur(6px);
  font-family: var(--font-mono);
  font-size: 8px;
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow-sm);
  color: #fff;
}

.carousel__title {
  font-family: var(--font-display);
  font-weight: var(--fw-semibold);
  font-size: clamp(1.05rem, 3.4vw, 1.4rem);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
  text-transform: uppercase;
}

.carousel__rule {
  width: 34px;
  height: 2px;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.7);
}

.carousel__sub {
  font-family: var(--font-body);
  font-size: var(--text-xs);
  line-height: var(--leading-snug);
  color: rgba(255, 255, 255, 0.82);
}

/* --- Chevrons ------------------------------------------------------------- */
.carousel__nav {
  position: absolute;
  top: 50%;
  margin-top: -22px;
  z-index: 5;
  width: 44px;            /* 44px min touch target */
  height: 44px;
  display: grid;
  place-items: center;
  border-radius: var(--radius-pill);
  background: rgba(250, 252, 251, 0.78);
  border: 1px solid var(--rule-soft);
  color: var(--ink-900);
  backdrop-filter: blur(6px);
  transition: background-color var(--dur-300) var(--ease-house),
              border-color var(--dur-300) var(--ease-house);
}
.carousel__nav:hover { background: #fff; border-color: var(--accent-40); }
.carousel__nav--prev { left: 0.25rem; }
.carousel__nav--next { right: 0.25rem; }

.carousel__hint {
  position: relative;
  z-index: 1;
  margin-top: 0.6rem;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow-sm);
  color: var(--text-subtle);
}

@media (prefers-reduced-motion: reduce) {
  .cloud { animation: none; }
}

/* --- Write-up ------------------------------------------------------------- */
.case__body {
  /* Centred block, but the prose itself stays left-aligned — centred body copy
     is markedly harder to read once it runs past a couple of lines. */
  max-width: 62ch;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 1.75rem;
}

.case__head { display: flex; flex-direction: column; gap: 0.375rem; }

.case__name {
  font-family: var(--font-display);
  font-weight: var(--fw-semibold);
  font-size: clamp(1.75rem, 6vw, 2.5rem);
  line-height: var(--leading-h3);
  letter-spacing: var(--tracking-tight);
}

.case__kicker {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow-sm);
  color: var(--text-muted);
}

.case__notes { display: flex; flex-direction: column; gap: 1.25rem; margin: 0; }
.case__note { display: flex; flex-direction: column; gap: 0.35rem; }

.case__note dt {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow-sm);
  color: var(--accent-dark);
}
.case__note dd {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  color: var(--text-body);
  max-width: 52ch;
}

.case__stack {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0;
}
.case__stack li {
  padding: 0.4rem 0.85rem;
  border-radius: var(--radius-pill);
  border: 1px solid var(--accent-15);
  background: var(--surface-card);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow-sm);
  color: var(--text-muted);
}

.case__actions { display: flex; }

/* --------------------------------------------------------------------------
   Logo marquee
   -------------------------------------------------------------------------- */
.marquee {
  width: 100%;
  overflow: hidden;
  mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
  -webkit-mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
}
/* Duration is written from JS so the speed stays constant no matter how many
   items had to be cloned in to cover the viewport — see initMarquee(). The
   30s fallback matches the original six-item group. */
.marquee__track {
  display: flex;
  width: max-content;
  animation: marquee var(--marquee-dur, 30s) linear infinite;
}
@keyframes marquee {
  0%   { transform: translateX(0); }
  100% { transform: translateX(-50%); }
}
.marquee__group {
  display: flex;
  align-items: center;
  gap: 3rem;
  padding-right: 3rem;
}
.marquee__item {
  font-family: var(--font-display);
  font-weight: var(--fw-semibold);
  font-size: var(--text-lg);
  letter-spacing: var(--tracking-tight);
  color: var(--text-subtle);
  white-space: nowrap;
  transition: color var(--dur-300) var(--ease-house);
}
.marquee:hover .marquee__item { color: var(--text-muted); }
.marquee:hover .marquee__track { animation-play-state: paused; }
/* The hover fade was for grey placeholder names. The wordmark stays solid —
   needs to outrank the two-class hover rule above, hence the extra class. */
.marquee:hover .marquee__logo { color: var(--text-strong); }

/* The belt now carries the wordmark itself, so it has to be set like the
   wordmark: tighter tracking and the serif italic on "Design". */
.marquee__logo {
  font-size: var(--text-2xl);
  letter-spacing: var(--tracking-tighter);
  color: var(--text-strong);
}
.marquee__logo em {
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: var(--fw-normal);
  padding-right: 0.25rem;
}

/* --------------------------------------------------------------------------
   Testimonial cards
   -------------------------------------------------------------------------- */
.quote-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.25rem;
  width: 100%;
}
.quote-card {
  padding: 1.75rem;
  border-radius: var(--radius-card);
  border: 1px solid var(--rule-hairline);
  background: var(--surface-card-plain);
  box-shadow: var(--shadow-card);
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  transition: transform var(--dur-500) var(--ease-house),
              border-color var(--dur-500) var(--ease-house);
}
.quote-card:hover { transform: translateY(-3px); border-color: var(--accent-40); }
.quote-card p {
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  color: var(--text-body);
}
.quote-card footer {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding-top: 1rem;
  border-top: 1px solid var(--rule-hairline);
}
.avatar {
  width: 2.25rem;
  height: 2.25rem;
  border-radius: var(--radius-pill);
  background: linear-gradient(140deg, var(--accent-top), var(--accent-deep));
  flex-shrink: 0;
}
.quote-card cite {
  font-style: normal;
  font-family: var(--font-display);
  font-weight: var(--fw-semibold);
  font-size: var(--text-sm);
  letter-spacing: var(--tracking-tight);
}
.quote-card small {
  display: block;
  font-family: var(--font-mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow-sm);
  color: var(--text-subtle);
}

/* --- Featured single testimonial ------------------------------------------
   One quote carries the section, so it gets a centred column rather than a
   third of a three-up row. Compound selector: it has to outrank the
   `repeat(3, 1fr)` this grid picks up at the md breakpoint further down.
   -------------------------------------------------------------------------- */
.quote-grid.quote-grid--single {
  grid-template-columns: minmax(0, 1fr);
  max-width: 620px;
  margin-inline: auto;
}
.quote-card--featured { gap: 1.5rem; }
/* Bled to the card edges — the shot is the card's lid, not an inset picture.
   Offsets must track .quote-card's 1.75rem padding. */
.quote-card__shot {
  margin: -1.75rem -1.75rem 0;
  border-radius: calc(var(--radius-card) - 1px) calc(var(--radius-card) - 1px) 0 0;
  overflow: hidden;
  background: var(--surface-neutral-3);
  border-bottom: 1px solid var(--rule-hairline);
}
/* Shown at its native 1:1 — any crop tight enough to shorten the card also
   cuts the browser chrome or the base of the phone, and the devices are the
   whole point of the shot. */
.quote-card__shot img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
}
.quote-card--featured p {
  font-size: var(--text-lg);
  line-height: var(--leading-relaxed);
}

/* --------------------------------------------------------------------------
   Comparison — two columns, positive/negative shadow direction
   -------------------------------------------------------------------------- */
/* ==========================================================================
   PLAN CARDS — apertures onto sky

   The idea: these are not panels resting on the page, they are openings cut
   through it. Real cloud drifts behind the glass, the card leans toward the
   pointer in 3D, and the content sits on its own Z-plane so it parallaxes
   against the sky rather than being painted flat onto it.

   Reuses the same cloud textures as the scroll fog, so the pricing belongs to
   the same weather as the rest of the page instead of being a separate idiom.

   Everything animated is transform/opacity. Tilt is pointer-driven and is
   switched off for coarse pointers and for reduced motion.
   ========================================================================== */
.compare {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2rem;
  width: 100%;
  position: relative;
  z-index: 1;
}

/* Outer element owns the reveal and the perspective. The tilt goes on the
   inner card, because .reveal animates to transform:none and would cancel it. */
.plan {
  position: relative;
  perspective: 1100px;
  perspective-origin: 50% 50%;
  /* Grid stretches .plan, but the card inside was sizing to its own content —
     so the two apertures ended up different heights. */
  display: flex;
}
.plan > .plan__card { flex: 1 1 auto; }

.plan__card {
  display: flex;
  flex-direction: column;   /* lets .plan__content fill, so margin-top:auto works */
  position: relative;
  border-radius: 26px;
  overflow: hidden;
  isolation: isolate;
  transform-style: preserve-3d;
  border: 1px solid rgba(255, 255, 255, 0.55);

  /* Deep, cool interior — the outdoors seen through the opening */
  background:
    linear-gradient(180deg, rgba(10, 15, 13, 0.92) 0%, rgba(10, 15, 13, 0.82) 40%, rgba(5, 5, 16, 0.90) 100%);

  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.28),
    0 2px 6px rgba(10, 15, 13, 0.10),
    0 22px 44px -18px rgba(10, 15, 13, 0.34),
    0 64px 100px -44px rgba(10, 15, 13, 0.40);

  /* --rx / --ry / --mx / --my are written from JS on pointer move */
  transform:
    rotateX(calc(var(--rx, 0) * 1deg))
    rotateY(calc(var(--ry, 0) * 1deg));
  transition: transform 500ms var(--ease-house), box-shadow 500ms var(--ease-house);
}
/* While actively tracking, drop the transform transition so it follows the
   pointer 1:1 instead of lagging behind it. */
.plan[data-tilting] .plan__card { transition: box-shadow 500ms var(--ease-house); }

/* --- The sky inside ------------------------------------------------------- */
.plan__sky {
  position: absolute;
  inset: -12%;
  z-index: 0;
  pointer-events: none;
  opacity: 0.5;
}
/* Each layer must travel EXACTLY one tile per loop or the reset is visible.
   Two constraints have to hold together:
     · translate distance == one tile width  (so the loop is invisible)
     · the strip stays wider than the card for the whole travel (so no edge
       can slide into view)
   Hence: strip width = card + travel + margin, and background-size expressed
   as the tile's share of that strip. The earlier version translated 0.222 of
   a tile, which snapped back three-quarters of a tile every cycle. */
.plan__sky span {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  background-repeat: repeat-x;
  background-position: left bottom;
  will-change: transform;
}
/* Height is `auto`, never a percentage. Forcing the height stretched the
   texture ~4x vertically on a phone, where the card is tall and narrow. With
   auto, the tile keeps its own 1:1 aspect at every viewport and the width
   alone drives the scale — which is also what keeps the loop maths valid,
   since travel is still a fixed percentage of the strip. */
.plan__sky span:nth-child(1) {
  background-image: url("assets/clouds/cloud-b.png");
  width: 400%;
  background-size: 63.25% auto;    /* tile = 2.53 card widths, square */
  opacity: 0.55;
  animation: sky-drift-a 38s linear infinite;
}
.plan__sky span:nth-child(2) {
  background-image: url("assets/clouds/cloud-a.png");
  width: 460%;
  background-size: 65.2174% auto;  /* tile = 3.0 card widths — different scale */
  opacity: 0.40;
  animation: sky-drift-b 26s linear infinite;
}
@keyframes sky-drift-a {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-63.25%, 0, 0); }     /* exactly one tile */
}
@keyframes sky-drift-b {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-65.2174%, 0, 0); }   /* exactly one tile */
}

/* Aurora wash — slow colour movement so the interior is never static */
.plan__aurora {
  position: absolute;
  inset: -20%;
  z-index: 1;
  pointer-events: none;
  mix-blend-mode: screen;
  opacity: 0.55;
  background:
    radial-gradient(38% 44% at 24% 28%, rgba(20, 225, 193, 0.55) 0%, rgba(20, 225, 193, 0) 70%),
    radial-gradient(34% 40% at 78% 22%, rgba(76, 229, 199, 0.42) 0%, rgba(76, 229, 199, 0) 72%),
    radial-gradient(46% 50% at 58% 82%, rgba(16, 198, 169, 0.38) 0%, rgba(16, 198, 169, 0) 74%);
  animation: aurora-drift 18s var(--ease-in-out) infinite alternate;
}
@keyframes aurora-drift {
  from { transform: translate3d(-4%, 2%, 0) scale(1); }
  to   { transform: translate3d(5%, -3%, 0) scale(1.12); }
}

/* Specular glare that tracks the pointer — light moving on real glass */
.plan__glare {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  opacity: 0;
  transition: opacity 400ms var(--ease-house);
  background: radial-gradient(
    260px circle at var(--mx, 50%) var(--my, 0%),
    rgba(255, 255, 255, 0.30) 0%,
    rgba(255, 255, 255, 0.08) 34%,
    rgba(255, 255, 255, 0) 68%);
}
.plan:hover .plan__glare { opacity: 1; }

/* Iridescent rim, rotating with the tilt angle */
.plan__card::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 4;
  border-radius: inherit;
  pointer-events: none;
  padding: 1px;
  background: conic-gradient(from calc(var(--ry, 0) * 4deg) at 50% 50%,
              rgba(20, 225, 193, 0) 0deg,
              rgba(20, 225, 193, 0.75) 70deg,
              rgba(255, 255, 255, 0.85) 150deg,
              rgba(76, 229, 199, 0.55) 240deg,
              rgba(20, 225, 193, 0) 360deg);
  /* Border-only: fill the box, then punch out everything inside the padding */
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  opacity: 0.55;
  transition: opacity 400ms var(--ease-house);
}
.plan:hover .plan__card::after { opacity: 1; }

/* --- Content, floated forward on its own plane ---------------------------- */
.plan__content {
  flex: 1 1 auto;
  position: relative;
  z-index: 5;
  padding: 2.5rem 2rem 2.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  transform: translateZ(38px);   /* the parallax: content rides above the sky */
  color: #fff;
}

.plan__eyebrow {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
  color: var(--accent);
  margin-bottom: 0.25rem;
}

.plan__title {
  font-family: var(--font-display);
  font-weight: var(--fw-semibold);
  font-size: clamp(1.75rem, 5.5vw, 2.375rem);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tighter);
  color: #fff;
}

.plan__sub {
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-tight);
  color: rgba(255, 255, 255, 0.72);
  max-width: 34ch;
}


/* Pricing zone. Both cards reserve the same height here even though one holds
   a toggle and the other a one-line note — that is what makes the rule, the
   feature list and everything below it start at the same y across the pair.
   Without it the two columns drift out of step by the height difference. */
.plan__pricing {
  /* Tall enough for the taller of the two cases (price + toggle). Measured,
     not guessed — at 124px the toggle overflowed and knocked the rule and the
     feature list 30px out of step between the columns. */
  min-height: 154px;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
}

.plan__note {
  margin-top: 0.75rem;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-tight);
  color: rgba(255, 255, 255, 0.60);
}

/* Pushed to the bottom of the card, so the two buttons line up on the same
   baseline no matter that one plan lists five features and the other four. */
.plan__cta {
  margin-top: auto;
  padding-top: 1.75rem;
}

/* --- Price + add-on toggle ------------------------------------------------
   Sits on the same Z-plane as the rest of the content, so it parallaxes
   against the sky with everything else.
   The teal is finally legible here: #14E1C1 measures 9.68:1 on the dark
   aperture, where it only managed 1.59:1 on the old light card.
   -------------------------------------------------------------------------- */
.plan__price {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.625rem;
  margin-top: 1.75rem;
}

.plan__amount {
  font-family: var(--font-display);
  font-weight: var(--fw-semibold);
  font-size: clamp(2.75rem, 9vw, 3.75rem);
  line-height: 1;
  letter-spacing: var(--tracking-tighter);
  color: var(--accent);
  /* Tabular figures stop the width jittering as the number counts up */
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  text-shadow: 0 0 34px rgba(20, 225, 193, 0.35);
}

.plan__period {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow-sm);
  color: rgba(255, 255, 255, 0.55);
}

/* --- The switch ----------------------------------------------------------- */
.plan__toggle {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-top: 1.25rem;
  padding: 0.625rem 0.875rem 0.625rem 0.75rem;
  min-height: 44px;                /* touch target */
  border-radius: var(--radius-pill);
  border: 1px solid rgba(255, 255, 255, 0.16);
  background: rgba(255, 255, 255, 0.06);
  cursor: pointer;
  user-select: none;
  transition: border-color var(--dur-300) var(--ease-house),
              background-color var(--dur-300) var(--ease-house);
}
.plan__toggle:hover {
  border-color: rgba(20, 225, 193, 0.45);
  background: rgba(255, 255, 255, 0.10);
}

/* Visually hidden but still focusable and still the real control */
.plan__toggle-input {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.plan__switch {
  position: relative;
  flex-shrink: 0;
  width: 42px;
  height: 24px;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.18);
  box-shadow: inset 0 1px 2px rgba(10, 15, 13, 0.5);
  transition: background-color var(--dur-300) var(--ease-house);
}
.plan__switch > span {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 18px;
  height: 18px;
  border-radius: var(--radius-pill);
  background: #fff;
  box-shadow: 0 1px 3px rgba(10, 15, 13, 0.45);
  transition: transform var(--dur-300) var(--ease-house);
}

.plan__toggle-input:checked ~ .plan__switch {
  background: linear-gradient(to right, var(--accent-deep), var(--accent));
}
.plan__toggle-input:checked ~ .plan__switch > span {
  transform: translateX(18px);
}

/* Focus ring on the switch, since the input itself is off-screen */
.plan__toggle-input:focus-visible ~ .plan__switch {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.plan__toggle-text {
  font-family: var(--font-body);
  font-size: var(--text-sm);
  letter-spacing: var(--tracking-tight);
  color: rgba(255, 255, 255, 0.88);
}

.plan__toggle-price {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-eyebrow-sm);
  color: rgba(255, 255, 255, 0.55);
  transition: color var(--dur-300) var(--ease-house);
}
.plan__toggle-input:checked ~ .plan__toggle-price { color: var(--accent); }

@media (prefers-reduced-motion: reduce) {
  .plan__switch > span { transition: none; }
}
.plan__rule {
  height: 1px;
  margin: 1.5rem 0 0.25rem;
  background: linear-gradient(to right,
              rgba(255, 255, 255, 0) 0%,
              rgba(255, 255, 255, 0.28) 20%,
              rgba(255, 255, 255, 0.28) 80%,
              rgba(255, 255, 255, 0) 100%);
}

.plan__list {
  display: flex;
  flex-direction: column;
  gap: 1.05rem;
  margin: 1.25rem 0 0;
  padding: 0;
}
.plan__list li {
  display: flex;
  gap: 0.875rem;
  align-items: flex-start;
  font-size: var(--text-base);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-tight);
  color: rgba(255, 255, 255, 0.88);
}

.tick {
  flex-shrink: 0;
  width: 1.375rem;
  height: 1.375rem;
  margin-top: 1px;
  border-radius: var(--radius-pill);
  display: grid;
  place-items: center;
  font-size: 11px;
  font-weight: var(--fw-bold);
  color: var(--bg-dark);
  background: linear-gradient(to bottom, #FFFFFF, var(--accent-top));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.90),
    0 2px 8px -1px rgba(20, 225, 193, 0.55);
  transition: transform var(--dur-300) var(--ease-house);
}
.plan:hover .tick { transform: scale(1.08); }

/* --- Featured: clearer weather, brighter rim ------------------------------ */
.plan--feature .plan__sky    { opacity: 0.72; }
.plan--feature .plan__aurora { opacity: 0.80; }
.plan--feature .plan__card {
  border-color: rgba(20, 225, 193, 0.45);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.35),
    0 2px 6px rgba(10, 15, 13, 0.12),
    0 26px 50px -18px rgba(20, 225, 193, 0.30),
    0 72px 110px -44px rgba(10, 15, 13, 0.44);
}

/* --- Touch and reduced motion --------------------------------------------- */
@media (hover: none), (pointer: coarse) {
  .plan__glare { display: none; }          /* nothing to track */
}

/* Phones: tighten the interior so the card is not needlessly lanky. The
   content is the same, it just stops wasting vertical space. */
@media (max-width: 480px) {
  .plan__content { padding: 2rem 1.5rem 1.875rem; }
  .plan__list { gap: 0.875rem; margin-top: 1rem; }
  .plan__price { margin-top: 1.375rem; }
  .plan__toggle { margin-top: 1rem; }
  .plan__rule { margin-top: 1.25rem; }
}
@media (prefers-reduced-motion: reduce) {
  .plan__sky span,
  .plan__aurora { animation: none; }
  .plan__card { transform: none !important; }
  .plan__content { transform: none; }
}
/* --------------------------------------------------------------------------
   Process — one card per step, each carrying its own artefact
   Was a three-row rule-separated list. The rows were interchangeable, so the
   section read as one block of text rather than three distinct stages. Each
   step is now a card with a drawn visual of what that stage produces.
   -------------------------------------------------------------------------- */
.process { width: 100%; display: flex; flex-direction: column; gap: 1.5rem; }

.step {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 2rem;
  padding: 2rem 1.5rem;
  border-radius: 26px;
  overflow: hidden;
  isolation: isolate;
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: linear-gradient(180deg,
              rgba(10, 15, 13, 0.94) 0%,
              rgba(10, 15, 13, 0.86) 45%,
              rgba(5, 5, 16, 0.94) 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.16),
    0 2px 6px rgba(10, 15, 13, 0.10),
    0 22px 44px -20px rgba(10, 15, 13, 0.34),
    0 64px 100px -46px rgba(10, 15, 13, 0.38);
  transition:
    transform var(--dur-500) var(--ease-house),
    box-shadow var(--dur-500) var(--ease-house),
    border-color var(--dur-500) var(--ease-house);
}
/* Beats `.reveal.is-visible { transform: none }` on source order, not
   specificity — both are two classes' worth. Keep this rule below it. */
.step:hover {
  transform: translateY(-4px);
  border-color: rgba(20, 225, 193, 0.45);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.28),
    0 26px 54px -22px rgba(20, 225, 193, 0.32),
    0 72px 110px -48px rgba(10, 15, 13, 0.46);
}

/* --- Weather inside the aperture ------------------------------------------
   The same cloud plates and aurora the pricing cards use, so the three steps
   belong to this page rather than to the panel they were sketched from. Each
   card gets a different plate scale, drift speed and aurora placement, so
   they read as three openings onto the same sky — not one card repeated.
   -------------------------------------------------------------------------- */
/* Pulled well down now that .step__wisp carries the texture. At 0.42 the grey
   plates and the green filaments were both competing for the same space and
   the card just read as murk — the wisps need a dark ground to glow against. */
.step__sky {
  position: absolute;
  inset: -12%;
  z-index: 0;
  pointer-events: none;
  opacity: 0.16;
}
.step__sky span {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  background-repeat: repeat-x;
  background-position: left bottom;
  will-change: transform;
}
/* Travel distance must equal exactly one tile width or the loop snaps. */
.step__sky span:nth-child(1) {
  background-image: url("assets/clouds/cloud-b.png");
  width: 400%;
  background-size: 63.25% auto;
  opacity: 0.5;
  animation: sky-drift-a 52s linear infinite;
}
.step__sky span:nth-child(2) {
  background-image: url("assets/clouds/cloud-c.png");
  width: 460%;
  background-size: 65.2174% auto;
  opacity: 0.34;
  animation: sky-drift-b 34s linear infinite;
}
/* Per-card: different plate scale and phase, so no two skies match. */
.step:nth-child(2) .step__sky span:nth-child(1) { animation-duration: 61s; animation-delay: -18s; }
.step:nth-child(2) .step__sky span:nth-child(2) { animation-duration: 29s; animation-delay: -9s; }
.step:nth-child(3) .step__sky span:nth-child(1) { animation-duration: 44s; animation-delay: -31s; }
.step:nth-child(3) .step__sky span:nth-child(2) { animation-duration: 39s; animation-delay: -22s; }

.step__aurora {
  position: absolute;
  inset: -20%;
  z-index: 1;
  pointer-events: none;
  mix-blend-mode: screen;
  opacity: 0.5;
  animation: aurora-drift 22s var(--ease-in-out) infinite alternate;
}
/* The light enters from a different corner on each card, which is what stops
   the three from reading as one card printed three times. */
.step:nth-child(1) .step__aurora {
  background:
    radial-gradient(40% 46% at 18% 26%, rgba(20, 225, 193, 0.50) 0%, rgba(20, 225, 193, 0) 70%),
    radial-gradient(36% 42% at 82% 74%, rgba(76, 229, 199, 0.34) 0%, rgba(76, 229, 199, 0) 72%);
}
.step:nth-child(2) .step__aurora {
  animation-duration: 17s;
  background:
    radial-gradient(44% 50% at 74% 20%, rgba(76, 229, 199, 0.46) 0%, rgba(76, 229, 199, 0) 72%),
    radial-gradient(38% 44% at 22% 86%, rgba(16, 198, 169, 0.36) 0%, rgba(16, 198, 169, 0) 74%);
}
.step:nth-child(3) .step__aurora {
  animation-duration: 26s;
  background:
    radial-gradient(52% 46% at 50% 108%, rgba(20, 225, 193, 0.52) 0%, rgba(20, 225, 193, 0) 70%),
    radial-gradient(30% 36% at 12% 12%, rgba(76, 229, 199, 0.28) 0%, rgba(76, 229, 199, 0) 74%);
}

/* --- Green wisps -----------------------------------------------------------
   The cloud PNGs are white-with-alpha. Painted as background-images they can
   only ever be grey fog, which is what .step__sky is. Used as MASKS over a
   teal gradient they become coloured filaments instead — same texture, doing
   the opposite job.

   `screen` is what makes them glow: it lightens toward the teal and never
   darkens, so the wisps read as emitted light rather than as paint. That only
   works over the card's dark interior — over the white page it would be a
   no-op, which is why this layer stops at the card edge.

   Masks are sized well under the earlier fog plates so the noise reads as
   structure rather than haze; three of them at different scales and speeds
   keep it from looking like one sheet sliding around.
   -------------------------------------------------------------------------- */
.step__wisp {
  position: absolute;
  inset: -25%;
  z-index: 1;
  pointer-events: none;
  mix-blend-mode: screen;
}
.step__wisp i {
  position: absolute;
  inset: 0;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: center;
          mask-position: center;
  will-change: transform;
}

/* Tight gradients on purpose. Broad ones filled the whole card and the result
   was green fog, not filaments — the reference works because most of the card
   stays dark and only the strands are lit. Each is weighted toward an edge so
   the middle, where the copy sits, stays legible. */
/* TWO masks per layer, intersected. One noise texture alone is soft fractal
   cloud and can only ever give round blobs. Keeping just the overlap of two
   copies at different scales cuts the soft shoulders away and leaves the
   narrow ridges where both happen to be dense — which is what reads as a
   tendril rather than a smudge.

   The -webkit- pair is not optional: Safari has no `mask-composite`, it uses
   `-webkit-mask-composite` with entirely different keywords (source-in, not
   intersect). Drop it and Safari shows only the first mask. */
.step__wisp i:nth-child(1) {
  background:
    radial-gradient(34% 40% at 78% 26%, #7BFFE6 0%, rgba(20, 225, 193, 0.55) 38%, rgba(20, 225, 193, 0) 68%);
  -webkit-mask-image: url("assets/clouds/cloud-a.png"), url("assets/clouds/cloud-c.png");
          mask-image: url("assets/clouds/cloud-a.png"), url("assets/clouds/cloud-c.png");
  -webkit-mask-size: 78% 88%, 128% 140%;
          mask-size: 78% 88%, 128% 140%;
  -webkit-mask-position: center, 28% 62%;
          mask-position: center, 28% 62%;
  -webkit-mask-composite: source-in;
          mask-composite: intersect;
  opacity: 1;
}
.step__wisp i:nth-child(2) {
  background:
    radial-gradient(30% 36% at 22% 78%, #4CE5C7 0%, rgba(76, 229, 199, 0.50) 36%, rgba(76, 229, 199, 0) 66%);
  -webkit-mask-image: url("assets/clouds/cloud-b.png"), url("assets/clouds/cloud-a.png");
          mask-image: url("assets/clouds/cloud-b.png"), url("assets/clouds/cloud-a.png");
  -webkit-mask-size: 92% 82%, 146% 120%;
          mask-size: 92% 82%, 146% 120%;
  -webkit-mask-position: center, 72% 30%;
          mask-position: center, 72% 30%;
  -webkit-mask-composite: source-in;
          mask-composite: intersect;
  opacity: 0.9;
}
.step__wisp i:nth-child(3) {
  background:
    radial-gradient(26% 32% at 92% 82%, #2DE8CC 0%, rgba(45, 232, 204, 0.46) 34%, rgba(45, 232, 204, 0) 64%);
  -webkit-mask-image: url("assets/clouds/cloud-c.png"), url("assets/clouds/cloud-b.png");
          mask-image: url("assets/clouds/cloud-c.png"), url("assets/clouds/cloud-b.png");
  -webkit-mask-size: 64% 72%, 112% 96%;
          mask-size: 64% 72%, 112% 96%;
  -webkit-mask-position: center, 40% 20%;
          mask-position: center, 40% 20%;
  -webkit-mask-composite: source-in;
          mask-composite: intersect;
  opacity: 0.85;
}

/* Only while the card is on screen — three masked, blended layers per card is
   real compositor work to leave running in a scrollback nobody is looking at. */
.step.is-visible .step__wisp i:nth-child(1) { animation: wisp-a 26s var(--ease-in-out) infinite alternate; }
.step.is-visible .step__wisp i:nth-child(2) { animation: wisp-b 34s var(--ease-in-out) infinite alternate; }
.step.is-visible .step__wisp i:nth-child(3) { animation: wisp-c 21s var(--ease-in-out) infinite alternate; }

/* Offset per card, so three cards on screen never drift in unison. */
.step:nth-child(2) .step__wisp i:nth-child(1) { animation-delay: -9s; }
.step:nth-child(2) .step__wisp i:nth-child(2) { animation-delay: -14s; }
.step:nth-child(2) .step__wisp i:nth-child(3) { animation-delay: -6s; }
.step:nth-child(3) .step__wisp i:nth-child(1) { animation-delay: -17s; }
.step:nth-child(3) .step__wisp i:nth-child(2) { animation-delay: -4s; }
.step:nth-child(3) .step__wisp i:nth-child(3) { animation-delay: -12s; }

@keyframes wisp-a {
  from { transform: translate3d(-7%, 5%, 0) scale(1.04) rotate(-2deg); }
  to   { transform: translate3d(8%, -6%, 0) scale(1.24) rotate(3deg); }
}
@keyframes wisp-b {
  from { transform: translate3d(6%, -4%, 0) scale(1.16) rotate(2deg); }
  to   { transform: translate3d(-8%, 6%, 0) scale(1.02) rotate(-3deg); }
}
@keyframes wisp-c {
  from { transform: translate3d(-4%, -6%, 0) scale(1.10) rotate(1deg); }
  to   { transform: translate3d(5%, 7%, 0) scale(1.30) rotate(-2deg); }
}

/* --- Wisps outside the cards, on the white page ----------------------------
   Same masked-cloud construction, opposite blend. `screen` is what makes the
   in-card wisps glow, and over white it is a no-op — white screened with
   anything stays white. `multiply` darkens toward the teal instead, so these
   read as soft smoke drifting across the page rather than as emitted light.
   That difference is inherent to the background colour, not a setting.

   Deliberately weak: at the cards' intensity this turns the section into a
   green wash and the white page stops being white.
   -------------------------------------------------------------------------- */
#process { position: relative; overflow: hidden; }
#process .rail { position: relative; z-index: 1; }

.process__wisp {
  position: absolute;
  inset: -6% -4%;
  z-index: 0;
  pointer-events: none;
  mix-blend-mode: multiply;
  opacity: 0.9;
}
.process__wisp i {
  position: absolute;
  inset: 0;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-composite: source-in;
          mask-composite: intersect;
  will-change: transform;
}
.process__wisp i:nth-child(1) {
  background: radial-gradient(52% 46% at 84% 18%, rgba(16, 198, 169, 0.95) 0%, rgba(20, 225, 193, 0) 72%);
  -webkit-mask-image: url("assets/clouds/cloud-a.png"), url("assets/clouds/cloud-c.png");
          mask-image: url("assets/clouds/cloud-a.png"), url("assets/clouds/cloud-c.png");
  -webkit-mask-size: 70% 60%, 120% 110%;
          mask-size: 70% 60%, 120% 110%;
  -webkit-mask-position: 90% 10%, 40% 40%;
          mask-position: 90% 10%, 40% 40%;
}
.process__wisp i:nth-child(2) {
  background: radial-gradient(48% 42% at 12% 62%, rgba(20, 225, 193, 0.90) 0%, rgba(45, 232, 204, 0) 72%);
  -webkit-mask-image: url("assets/clouds/cloud-b.png"), url("assets/clouds/cloud-a.png");
          mask-image: url("assets/clouds/cloud-b.png"), url("assets/clouds/cloud-a.png");
  -webkit-mask-size: 76% 66%, 130% 100%;
          mask-size: 76% 66%, 130% 100%;
  -webkit-mask-position: 5% 70%, 60% 30%;
          mask-position: 5% 70%, 60% 30%;
}
.process__wisp i:nth-child(3) {
  background: radial-gradient(44% 40% at 70% 92%, rgba(45, 232, 204, 0.85) 0%, rgba(76, 229, 199, 0) 72%);
  -webkit-mask-image: url("assets/clouds/cloud-c.png"), url("assets/clouds/cloud-b.png");
          mask-image: url("assets/clouds/cloud-c.png"), url("assets/clouds/cloud-b.png");
  -webkit-mask-size: 64% 56%, 110% 120%;
          mask-size: 64% 56%, 110% 120%;
  -webkit-mask-position: 75% 95%, 30% 50%;
          mask-position: 75% 95%, 30% 50%;
}
.process__wisp i:nth-child(1) { animation: wisp-a 38s var(--ease-in-out) infinite alternate; }
.process__wisp i:nth-child(2) { animation: wisp-b 46s var(--ease-in-out) infinite alternate; }
.process__wisp i:nth-child(3) { animation: wisp-c 33s var(--ease-in-out) infinite alternate; }

/* Oversized numeral bled into the corner: gives each card a different
   silhouette at a glance, before any of the copy is read. */
.step__ghost {
  position: absolute;
  z-index: 2;
  top: -0.35em;
  left: -0.06em;
  font-family: var(--font-display);
  font-weight: var(--fw-bold);
  font-size: clamp(7rem, 22vw, 13rem);
  line-height: 1;
  letter-spacing: var(--tracking-tighter);
  color: rgba(255, 255, 255, 0.045);
  pointer-events: none;
  user-select: none;
}

.step__copy {
  position: relative;
  z-index: 3;
  display: flex;
  flex-direction: column;
  gap: 0.875rem;
  align-self: center;
}
.step__copy h3 { max-width: 16ch; color: #fff; }
.step__copy .body-copy { max-width: 48ch; color: rgba(255, 255, 255, 0.68); }

.step__num {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-eyebrow);
  color: var(--accent);
}

.step__rule {
  display: block;
  width: 48px;
  height: 2px;
  margin-top: 0.5rem;
  border-radius: var(--radius-pill);
  background: var(--grad-rule);
  transition: width var(--dur-500) var(--ease-house);
}
.step:hover .step__rule { width: 84px; }

.step__visual {
  position: relative;
  z-index: 3;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

/* --- The drawn artefacts -------------------------------------------------
   Shared stage: a soft inset panel. Everything inside is divs and
   pseudo-elements, so the three visuals cost nothing to load and stay sharp
   at any density.
   ------------------------------------------------------------------------- */
.viz {
  position: relative;
  aspect-ratio: 16 / 10;
  border-radius: 16px;
  padding: 0.875rem;
  overflow: hidden;
  background: linear-gradient(160deg, rgba(255, 255, 255, 0.10) 0%, rgba(255, 255, 255, 0.03) 100%);
  border: 1px solid rgba(255, 255, 255, 0.14);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.16),
    0 14px 34px -22px rgba(0, 0, 0, 0.7);
}

/* 01 — research board ------------------------------------------------------ */
.viz--board {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(2, 1fr);
  gap: 0.625rem;
}
.viz__tile {
  position: relative;
  border-radius: 10px;
  background: rgba(10, 15, 13, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.12);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
  padding: 0.5rem;
  overflow: hidden;
  transition: transform var(--dur-500) var(--ease-house);
}
.step:hover .viz__tile { transform: translateY(-2px); }
.step:hover .viz__tile:nth-child(3) { transform: translateY(-4px); }
.step:hover .viz__tile:nth-child(6) { transform: translateY(-3px); }

.viz__tile--fill::after {
  content: "";
  position: absolute;
  inset: 0.4rem;
  border-radius: 6px;
  background: linear-gradient(150deg, var(--accent-top), var(--accent-dark));
  box-shadow: 0 0 22px -4px rgba(20, 225, 193, 0.55);
}
.viz__tile--lines { display: flex; flex-direction: column; gap: 0.3rem; justify-content: flex-start; }
.viz__tile--lines i { height: 3px; border-radius: 2px; background: rgba(255, 255, 255, 0.30); }
.viz__tile--lines i:nth-child(1) { width: 82%; }
.viz__tile--lines i:nth-child(2) { width: 64%; }
.viz__tile--lines i:nth-child(3) { width: 40%; background: rgba(20, 225, 193, 0.55); }
.viz__tile--stack::after {
  content: "";
  position: absolute;
  inset: 55% 0.4rem 0.4rem;
  border-top: 1px solid rgba(255, 255, 255, 0.14);
  background: rgba(255, 255, 255, 0.07);
}
.viz__tile--dot { display: grid; place-items: center; }
.viz__tile--dot i {
  width: 26%;
  aspect-ratio: 1;
  border-radius: var(--radius-pill);
  background: var(--accent);
  box-shadow:
    0 0 0 6px rgba(20, 225, 193, 0.14),
    0 0 20px 0 rgba(20, 225, 193, 0.55);
}
.viz__tile--quad {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  gap: 3px;
  padding: 0.4rem;
}
.viz__tile--quad i { border-radius: 3px; background: rgba(255, 255, 255, 0.10); }
.viz__tile--quad i:nth-child(2),
.viz__tile--quad i:nth-child(3) { background: rgba(20, 225, 193, 0.55); }
/* The teal base moves onto the tile itself, freeing ::after to be the plasma.
   Blending the cloud PNG into the fill was tried first and cannot work: the
   texture is white-with-alpha, so `overlay` (like `screen`) lightens toward
   white and bleached the swatch grey. Masking is the only way to get the
   texture's SHAPE without its colour. */
.viz__tile--grad {
  background:
    linear-gradient(180deg, rgba(20, 225, 193, 0.42), rgba(10, 143, 122, 0.12)),
    rgba(10, 15, 13, 0.55);
}
.viz__tile--grad::after {
  content: "";
  position: absolute;
  inset: 0.4rem;
  border-radius: 6px;
  mix-blend-mode: screen;
  background: radial-gradient(62% 62% at 42% 46%, #7BFFE6 0%, rgba(20, 225, 193, 0.42) 46%, rgba(20, 225, 193, 0) 76%);
  -webkit-mask-image: url("assets/clouds/cloud-c.png");
          mask-image: url("assets/clouds/cloud-c.png");
  -webkit-mask-size: 150% 150%;
          mask-size: 150% 150%;
  -webkit-mask-position: center;
          mask-position: center;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
}
.step.is-visible .viz__tile--grad::after { animation: wisp-c 19s var(--ease-in-out) infinite alternate; }

/* A slow pass of light over the board — "still being studied". */
.viz__scan {
  position: absolute;
  z-index: 2;
  top: 0;
  bottom: 0;
  left: -35%;
  width: 30%;
  pointer-events: none;
  background: linear-gradient(100deg,
              rgba(20, 225, 193, 0) 0%,
              rgba(20, 225, 193, 0.20) 50%,
              rgba(20, 225, 193, 0) 100%);
  animation: viz-scan 7s var(--ease-in-out) infinite;
}
@keyframes viz-scan {
  0%, 62%  { transform: translateX(0); }
  100%     { transform: translateX(450%); }
}

/* ==========================================================================
   Alive — the process cards, lit from within

   The page stays white; only the cards carry the charge. Everything here is
   transform/opacity/filter so it stays on the compositor, and every animation
   is gated behind `.step.is-visible` so nothing burns cycles off-screen.
   ========================================================================== */

/* --- The rim breathes ------------------------------------------------------
   The glow has to be INSET. `.step` is `overflow: hidden` so the sky stays
   inside its corners, which clips any outward box-shadow on a child — an
   outer halo drawn here would simply never appear. Lighting the inside edge
   gives the same lit-from-within read and survives the clip.
   -------------------------------------------------------------------------- */
.step::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;                 /* over the sky and aurora, under the content */
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  box-shadow:
    inset 0 0 0 1px rgba(20, 225, 193, 0.75),
    inset 0 0 18px -2px rgba(20, 225, 193, 0.55),
    inset 0 0 60px -14px rgba(20, 225, 193, 0.60);
}
.step.is-visible::before { animation: step-halo 6s var(--ease-in-out) infinite; }
.step:nth-child(2).is-visible::before { animation-duration: 7.4s; animation-delay: -2.1s; }
.step:nth-child(3).is-visible::before { animation-duration: 5.2s; animation-delay: -3.6s; }
@keyframes step-halo {
  0%, 100% { opacity: 0.30; }
  50%      { opacity: 0.85; }
}
/* Hover takes it over — the breathing stops and the rim simply holds lit, so
   pointing at a card reads as a response rather than a coincidence. */
.step:hover::before { animation: none; opacity: 1; }

/* --- Tiles: light moves across them --------------------------------------- */
/* A sheen that crosses each solid tile on its own delay, so the board looks
   like it is being read rather than lit all at once. */
.viz__tile--fill::before,
.viz__tile--grad::before {
  content: "";
  position: absolute;
  z-index: 1;
  top: -40%;
  bottom: -40%;
  left: -60%;
  width: 45%;
  pointer-events: none;
  transform: rotate(18deg);
  background: linear-gradient(90deg,
              rgba(255, 255, 255, 0) 0%,
              rgba(255, 255, 255, 0.45) 50%,
              rgba(255, 255, 255, 0) 100%);
}
.step.is-visible .viz__tile--fill::before { animation: tile-sheen 5.5s var(--ease-in-out) infinite; }
.step.is-visible .viz__tile--grad::before { animation: tile-sheen 5.5s var(--ease-in-out) 2.4s infinite; }
@keyframes tile-sheen {
  0%, 55% { transform: translateX(0) rotate(18deg); }
  100%    { transform: translateX(520%) rotate(18deg); }
}

/* The fill tile glows harder as the sheen crosses it. */
.step.is-visible .viz__tile--fill::after { animation: tile-breathe 5.5s var(--ease-in-out) infinite; }
@keyframes tile-breathe {
  0%, 100% { box-shadow: 0 0 18px -6px rgba(20, 225, 193, 0.45); }
  50%      { box-shadow: 0 0 30px 0 rgba(20, 225, 193, 0.75); }
}

/* The teal quarters of the quad tile trade places, slowly. */
.step.is-visible .viz__tile--quad i:nth-child(2) { animation: quad-fade 6.5s var(--ease-in-out) infinite; }
.step.is-visible .viz__tile--quad i:nth-child(3) { animation: quad-fade 6.5s var(--ease-in-out) -3.25s infinite; }
@keyframes quad-fade {
  0%, 100% { background: rgba(20, 225, 193, 0.55); }
  50%      { background: rgba(255, 255, 255, 0.10); }
}

/* The third line of the lines tile types itself out and back. */
.step.is-visible .viz__tile--lines i:nth-child(3) { animation: line-scrub 5s var(--ease-in-out) infinite; }
@keyframes line-scrub {
  0%, 100% { width: 40%; }
  50%      { width: 68%; }
}

/* --- The orb: a signal being read ----------------------------------------- */
.viz__wave {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  overflow: visible;
}
.viz__wave path {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.5;
  stroke-linecap: round;
  filter: drop-shadow(0 0 3px rgba(20, 225, 193, 0.9));
  opacity: 0.85;
}
/* Travels exactly one period (60 viewBox units), so the loop is seamless —
   a dash offset was tried first and only ever showed a stub of the curve. */
.step.is-visible .viz__wave path { animation: wave-flow 3.2s linear infinite; }
@keyframes wave-flow {
  from { transform: translateX(0); }
  to   { transform: translateX(-60px); }
}

/* Rings leaving the orb. The element is the ring; the orb itself is the <i>. */
.viz__tile--dot i { position: relative; z-index: 1; }
.viz__tile--dot i::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--radius-pill);
  border: 1px solid rgba(20, 225, 193, 0.7);
  opacity: 0;
}
.step.is-visible .viz__tile--dot i::after { animation: orb-ring 2.8s var(--ease-out) infinite; }
.step.is-visible .viz__tile--dot i { animation: orb-core 2.8s var(--ease-in-out) infinite; }
@keyframes orb-ring {
  0%   { transform: scale(1);   opacity: 0.9; }
  70%  { transform: scale(3.2); opacity: 0; }
  100% { transform: scale(3.2); opacity: 0; }
}
@keyframes orb-core {
  0%, 100% { box-shadow: 0 0 0 6px rgba(20, 225, 193, 0.12), 0 0 16px 0 rgba(20, 225, 193, 0.5); }
  50%      { box-shadow: 0 0 0 7px rgba(20, 225, 193, 0.20), 0 0 26px 2px rgba(20, 225, 193, 0.85); }
}

/* 02 — the viewport -------------------------------------------------------- */
.viz--frame { display: flex; flex-direction: column; gap: 0.5rem; }
.viz__chrome {
  display: flex;
  align-items: center;
  gap: 4px;
  flex: 0 0 auto;
}
.viz__chrome i {
  width: 6px;
  height: 6px;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.22);
}
.viz__chrome span {
  flex: 1;
  height: 8px;
  margin-left: 0.35rem;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.10);
}
.viz__page {
  position: relative;
  flex: 1 1 auto;
  border-radius: 8px;
  background: rgba(10, 15, 13, 0.6);
  border: 1px solid rgba(255, 255, 255, 0.12);
  padding: 0.6rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  overflow: hidden;
}
.viz__hero {
  position: relative;
  height: 42%;
  border-radius: 6px;
  overflow: hidden;
  background:
    radial-gradient(120% 120% at 20% 10%, rgba(255, 255, 255, 0.40), transparent 62%),
    linear-gradient(140deg, var(--accent), var(--accent-dark));
  box-shadow: 0 8px 26px -10px rgba(20, 225, 193, 0.6);
}
/* Plasma in the viewport, as in the reference — the same masked-cloud trick as
   .step__wisp, scaled down to the screen inside the browser frame. */
.viz__hero::after {
  content: "";
  position: absolute;
  inset: -30%;
  mix-blend-mode: screen;
  background: radial-gradient(60% 60% at 40% 45%, #7BFFE6 0%, rgba(20, 225, 193, 0.45) 48%, rgba(20, 225, 193, 0) 78%);
  -webkit-mask-image: url("assets/clouds/cloud-b.png");
          mask-image: url("assets/clouds/cloud-b.png");
  -webkit-mask-size: 130% 130%;
          mask-size: 130% 130%;
  -webkit-mask-position: center;
          mask-position: center;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  opacity: 0.85;
}
.step.is-visible .viz__hero::after { animation: wisp-a 17s var(--ease-in-out) infinite alternate; }
.viz__hl { height: 5px; border-radius: 3px; background: rgba(255, 255, 255, 0.18); }
.viz__hl--1 { width: 78%; background: rgba(255, 255, 255, 0.42); }
.viz__hl--2 { width: 52%; }
.viz__cta {
  margin-top: auto;
  width: 44%;
  height: 14px;
  border-radius: var(--radius-pill);
  background: var(--grad-button);
  box-shadow: 0 3px 10px -3px rgba(20, 225, 193, 0.7);
  animation: viz-cta 3.2s var(--ease-in-out) infinite;
}
@keyframes viz-cta {
  0%, 70%, 100% { box-shadow: 0 3px 10px -3px rgba(20, 225, 193, 0.7); }
  85%           { box-shadow: 0 4px 16px 0 rgba(20, 225, 193, 0.95); }
}
/* The visitor's pointer, arriving at the one action on the page. */
.viz__pointer {
  position: absolute;
  left: 34%;
  bottom: 0.55rem;
  width: 12px;
  height: 12px;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.85);
  box-shadow: 0 0 0 5px rgba(255, 255, 255, 0.14);
  animation: viz-pointer 3.2s var(--ease-house) infinite;
}
@keyframes viz-pointer {
  0%   { transform: translate(26px, -34px) scale(1); opacity: 0; }
  25%  { opacity: 1; }
  70%  { transform: translate(0, 0) scale(1); opacity: 1; }
  82%  { transform: translate(0, 0) scale(0.7); }
  100% { transform: translate(0, 0) scale(1); opacity: 0; }
}
/* Where the screen ends — the reason the CTA sits where it does. */
.viz__fold {
  position: absolute;
  right: 0.875rem;
  bottom: 1.9rem;
  display: flex;
  align-items: center;
  gap: 0.35rem;
  font-family: var(--font-mono);
  font-size: 8px;
  letter-spacing: var(--tracking-eyebrow-sm);
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.40);
}
.viz__fold::before {
  content: "";
  width: 26px;
  height: 1px;
  background: repeating-linear-gradient(to right,
              rgba(255, 255, 255, 0.30) 0 4px, transparent 4px 8px);
}
.viz__fold em { font-style: normal; }

/* 03 — form to inbox ------------------------------------------------------- */
.viz--feed {
  display: grid;
  grid-template-columns: 0.85fr 1fr;
  grid-template-rows: 1fr auto;
  gap: 0.625rem;
}
.viz__form {
  border-radius: 8px;
  background: rgba(10, 15, 13, 0.6);
  border: 1px solid rgba(255, 255, 255, 0.12);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
  padding: 0.6rem;
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  justify-content: center;
}
.viz__field {
  height: 12px;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.12);
}
.viz__field--short { width: 72%; }
.viz__send {
  margin-top: 0.15rem;
  width: 54%;
  height: 12px;
  border-radius: var(--radius-pill);
  background: var(--grad-button);
}
.viz__inbox { display: flex; flex-direction: column; gap: 0.4rem; justify-content: center; }
.viz__note {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.4rem 0.5rem;
  border-radius: 7px;
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.14);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.10);
  opacity: 0;
  transform: translateX(14px);
}
/* Held until the card is in view, so the inquiries land as you read the step. */
.step.is-visible .viz__note { animation: viz-note-in 0.6s var(--ease-quint) forwards; }
.step.is-visible .viz__note:nth-child(1) { animation-delay: 0.35s; }
.step.is-visible .viz__note:nth-child(2) { animation-delay: 0.55s; }
.step.is-visible .viz__note:nth-child(3) { animation-delay: 0.75s; }
@keyframes viz-note-in {
  to { opacity: 1; transform: none; }
}
.viz__note span {
  width: 12px;
  height: 12px;
  flex: 0 0 auto;
  border-radius: var(--radius-pill);
  background: linear-gradient(150deg, var(--accent-top), var(--accent-deep));
}
.viz__note i { flex: 1; height: 4px; border-radius: 2px; background: rgba(255, 255, 255, 0.26); }
.viz__note:nth-child(2) i { max-width: 70%; }
.viz__note:nth-child(3) i { max-width: 84%; }

.viz__bars {
  grid-column: 1 / -1;
  display: flex;
  align-items: flex-end;
  gap: 0.35rem;
  height: 26px;
}
.viz__bars i {
  flex: 1;
  border-radius: 3px 3px 2px 2px;
  background: linear-gradient(180deg, var(--accent), var(--accent-deep));
  opacity: 0.55;
  height: 20%;
  transition: height var(--dur-700) var(--ease-quint);
}
.viz__bars i:nth-child(5) { opacity: 1; }
.step.is-visible .viz__bars i:nth-child(1) { height: 26%; transition-delay: 0.30s; }
.step.is-visible .viz__bars i:nth-child(2) { height: 40%; transition-delay: 0.38s; }
.step.is-visible .viz__bars i:nth-child(3) { height: 58%; transition-delay: 0.46s; }
.step.is-visible .viz__bars i:nth-child(4) { height: 74%; transition-delay: 0.54s; }
.step.is-visible .viz__bars i:nth-child(5) { height: 100%; transition-delay: 0.62s; }


/* --------------------------------------------------------------------------
   CTA band
   -------------------------------------------------------------------------- */
.cta-band {
  width: 100%;
  border-radius: var(--radius-card);
  padding: 3rem 1.5rem;
  text-align: center;
  position: relative;
  overflow: hidden;
  background:
    radial-gradient(80% 120% at 50% 0%, rgba(20, 225, 193, 0.18), transparent 70%),
    var(--surface-band);
  border: 1px solid var(--accent-15);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.5rem;
}

/* --------------------------------------------------------------------------
   FAQ accordion (Radix-equivalent: 0.2s ease-out height)
   -------------------------------------------------------------------------- */
.faq { width: 100%; position: relative; z-index: 1; }
.faq__item { border-bottom: 1px solid var(--rule-hairline); }
.faq__trigger {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 1.5rem 0;
  text-align: left;
  font-family: var(--font-display);
  font-weight: var(--fw-semibold);
  font-size: var(--text-lg);
  letter-spacing: var(--tracking-tight);
  line-height: var(--leading-tight);
  transition: color var(--dur-300) var(--ease-house);
}
.faq__trigger:hover { color: var(--accent-deep); }
.faq__chevron {
  flex-shrink: 0;
  width: 1rem;
  height: 1rem;
  position: relative;
  transition: transform var(--dur-300) var(--ease-house);
}
.faq__chevron::before,
.faq__chevron::after {
  content: "";
  position: absolute;
  background: currentColor;
  border-radius: var(--radius-pill);
  transition: opacity var(--dur-200) var(--ease-out);
}
.faq__chevron::before { top: 50%; left: 0; width: 100%; height: 1.5px; transform: translateY(-50%); }
.faq__chevron::after  { left: 50%; top: 0; height: 100%; width: 1.5px; transform: translateX(-50%); }
.faq__item[data-open="true"] .faq__chevron { transform: rotate(90deg); }
.faq__item[data-open="true"] .faq__chevron::after { opacity: 0; }

.faq__panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--dur-200) var(--ease-out);
}
.faq__item[data-open="true"] .faq__panel { grid-template-rows: 1fr; }
.faq__panel > div { overflow: hidden; }
.faq__panel p {
  padding-bottom: 1.5rem;
  max-width: 62ch;
  font-size: var(--text-sm);
  line-height: var(--leading-relaxed);
  color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   Stat strip (LandingShowcase stand-in)
   -------------------------------------------------------------------------- */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1px;
  width: 100%;
  background: var(--rule-hairline);
  border: 1px solid var(--rule-hairline);
  border-radius: var(--radius-card);
  overflow: hidden;
}
.stat {
  background: var(--surface-stat);
  padding: 1.75rem 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
  align-items: center;
  text-align: center;
  transition: background-color var(--dur-500) var(--ease-house);
}
.stat:hover { background: var(--surface-tint-2); }
.stat b {
  font-family: var(--font-display);
  font-weight: var(--fw-semibold);
  font-size: var(--text-4xl);
  line-height: var(--leading-none);
  letter-spacing: var(--tracking-tighter);
}

/* --------------------------------------------------------------------------
   Footer — dark, full viewport height
   -------------------------------------------------------------------------- */
.footer {
  position: relative;
  width: 100%;
  /* svh, not dvh. `dvh` tracks the CURRENT viewport, so on iOS it changes on
     every scroll as the URL bar hides and shows — which relaid-out and
     repainted this whole blended footer continuously. `svh` is the smallest
     viewport height and never changes, so the footer is laid out once. */
  min-height: 100svh;
  overflow: hidden;
  color: #fff;
  /* Starts fully transparent so the page field bleeds into the footer, then
     ramps to the dark surface. This is the only light→dark boundary on the
     page, so it gets the longest blend. Padding-top clears the ramp before
     any white text lands on it. */
  /* A two-stop ramp reaching 0.55 by 5% put a visible horizon across the
     page — the eye reads any fast alpha change as an edge. These stops
     approximate an ease-in-out instead, so there is no point where the
     rate of darkening jumps. Padding-top clears the ramp before any white
     text lands on it. */
  background-image: linear-gradient(
    to bottom,
    rgba(10, 15, 13, 0)     0%,
    rgba(10, 15, 13, 0.04)  3%,
    rgba(10, 15, 13, 0.12)  6%,
    rgba(10, 15, 13, 0.26)  9%,
    rgba(10, 15, 13, 0.45) 12%,
    rgba(10, 15, 13, 0.66) 15%,
    rgba(10, 15, 13, 0.85) 18%,
    var(--bg-dark)         22%,
    var(--bg-dark-deep)   100%
  );
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 9rem var(--gutter) 1rem;
}

/* --------------------------------------------------------------------------
   Footer particle field

   Cloud textures were tried here first and could not give this look: fractal
   noise produces soft billows, and the reference is particulate — thousands
   of discrete points strung into filaments. That needs a simulation, so the
   plume is drawn per-frame in main.js (initFooterField).

   The canvas paints its own black and fades it each frame to leave trails;
   `screen` then drops that black out over the footer and keeps only the
   light. The top is masked so nothing shows against the pale ramp above,
   where screen has nothing to lighten.
   -------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------
   The plume

   This is the reference render's own smoke, so the texture matches exactly
   rather than approximating it. It was shot on near-black, which is what lets
   `screen` drop the background and leave only the smoke — no cut-out needed.

   It is a still, so it breathes by transform instead: a slow scale and drift
   on a 9s cycle, plus a much longer counter-drift so the movement never
   repeats on an obvious beat.
   -------------------------------------------------------------------------- */
.footer__plume {
  position: absolute;
  inset: -8%;
  z-index: 0;
  pointer-events: none;
  mix-blend-mode: screen;
  background-image: url("assets/footer-plume.jpg");
  background-repeat: no-repeat;
  background-position: center 58%;
  background-size: cover;
  opacity: 0.92;
  transform-origin: 50% 55%;
  will-change: transform;
  animation: plume-breath 9s var(--ease-in-out) infinite;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 20%, #000 90%, transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0%, #000 20%, #000 90%, transparent 100%);
}
@keyframes plume-breath {
  0%   { transform: scale(1)    translate3d(0, 0, 0);      opacity: 0.82; }
  45%  { transform: scale(1.055) translate3d(-0.6%, -1.2%, 0); opacity: 1; }
  100% { transform: scale(1)    translate3d(0, 0, 0);      opacity: 0.82; }
}

.footer__field {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
  mix-blend-mode: screen;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 18%, #000 94%, transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0%, #000 18%, #000 94%, transparent 100%);
}
.footer::before {
  content: "";
  position: absolute;
  bottom: -20%;
  left: 50%;
  transform: translateX(-50%);
  width: 120%;
  height: 60%;
  background: radial-gradient(50% 50% at 50% 50%, rgba(20, 225, 193, 0.18), transparent 70%);
  pointer-events: none;
}
.footer__top { position: relative; z-index: 1; display: flex; flex-direction: column; gap: 2.5rem; }
.footer__word {
  font-family: var(--font-display);
  font-weight: var(--fw-semibold);
  font-size: 13vw;
  line-height: var(--leading-tight-x);
  letter-spacing: var(--tracking-tighter);
}
.footer__word em {
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: var(--fw-normal);
  color: var(--accent);
}
.footer__cols {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 2rem;
}
.footer__col h4 {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
  color: rgba(255, 255, 255, 0.4);
  margin-bottom: 0.875rem;
}
.footer__col li + li { margin-top: 0.5rem; }
.footer__col a {
  font-size: var(--text-sm);
  color: rgba(255, 255, 255, 0.75);
  transition: color var(--dur-300) var(--ease-house);
}
.footer__col a:hover { color: var(--accent); }

.footer__bottom {
  position: relative;
  z-index: 1;
  margin-top: 3rem;
  padding-top: 1.5rem;
  border-top: 1px solid rgba(255, 255, 255, 0.1);
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  font-family: var(--font-mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow-sm);
  color: rgba(255, 255, 255, 0.4);
}

/* --------------------------------------------------------------------------
   Breakpoints — sm 640 · md 768 · lg 1024 · xl 1280
   -------------------------------------------------------------------------- */
@media (min-width: 640px) {
  h1, .h1 { font-size: var(--text-5xl); }
  h2, .h2 { font-size: var(--text-5xl); }
  h3, .h3 { font-size: 26px; }
  .hero__steps { font-size: 10px; gap: 0.75rem; letter-spacing: var(--tracking-eyebrow); }
  .eyebrow { font-size: 10px; }

  .stat-grid { grid-template-columns: repeat(4, 1fr); }
  .footer__bottom { flex-direction: row; justify-content: space-between; align-items: center; }
}

@media (min-width: 768px) {
  h1, .h1 { font-size: 3.75rem; }
  h2, .h2 { font-size: 3.75rem; }
  h3, .h3 { font-size: 28px; }

  .section--rhythm-a { padding-block: 8rem; }
  .section--rhythm-b { padding-top: 2.5rem; padding-bottom: 6rem; }
  .section--rhythm-c { padding-top: 6rem; padding-bottom: 4rem; }
  .section--rhythm-d { padding-bottom: 2rem; }

  .rail { padding-inline: var(--gutter-md); }
  .rail--4xl { max-width: calc(var(--rail-4xl) + var(--gutter-md) * 2); }
  .rail--5xl { max-width: calc(var(--rail-5xl) + var(--gutter-md) * 2); }
  .rail--6xl { max-width: calc(var(--rail-6xl) + var(--gutter-md) * 2); }
  .rail--7xl { max-width: calc(var(--rail-7xl) + var(--gutter-md) * 2); }

  .nav { padding: 1.5rem var(--gutter-md); }
  .nav__logo { font-size: var(--text-3xl); }
  .nav__toggle { padding: 1rem; margin-right: -1rem; }
  .nav__icon { width: 26px; height: 26px; }
  .nav-menu { padding: 8rem var(--gutter-md) var(--gutter-md); }
  .nav-menu a { font-size: 7vw; }

  .hero { padding-top: var(--nav-offset-md); }
  .hero__steps { font-size: var(--text-xs); gap: 1.5rem; }
  .hero__status span { font-size: 11px; }

  /* Hamburger gives way to inline links */
  .nav__links { display: flex; }
  .nav__toggle { display: none; }

  .btn { padding: 22px 4rem; }
  .btn__label { font-size: 13px; }

  .section-head { gap: 1rem; margin-bottom: 4rem; }
  .quote-grid { gap: 1.5rem; grid-template-columns: repeat(3, 1fr); }
  .compare { grid-template-columns: repeat(2, 1fr); gap: 1.5rem; }

  /* Copy and artefact side by side, and the middle card mirrored so the
     three don't stack into one repeated L-shape down the page. */
  .process { gap: 2rem; }
  .step { grid-template-columns: minmax(0, 1.02fr) minmax(0, 0.98fr); gap: 3rem; padding: 2.75rem; align-items: center; }
  .step--flip .step__copy   { order: 2; }
  .step--flip .step__visual { order: 1; }
  /* Mirror the track widths too, so the copy keeps the wider column in both
     directions and all three cards come out the same height. */
  .step--flip { grid-template-columns: minmax(0, 0.98fr) minmax(0, 1.02fr); }
  .step__num { font-size: var(--text-base); }
  .viz { padding: 1.125rem; }

  .cta-band { padding: 5rem 3rem; }
  .faq__trigger { font-size: var(--text-xl); padding: 1.75rem 0; }
  .faq__panel p { font-size: var(--text-base); }

  .footer { padding: 8rem var(--gutter-md) 1rem; }
  .footer__cols { grid-template-columns: repeat(4, 1fr); }
  .footer__word { font-size: 9vw; }
  .stat b { font-size: var(--text-5xl); }
}

@media (min-width: 1024px) {
  h1, .h1 { font-size: 68px; }
  h2, .h2 { font-size: 76px; }
  h3, .h3 { font-size: 36px; line-height: var(--leading-h3); }
  .lede { font-size: var(--text-xl); font-weight: var(--fw-semibold); }
  .eyebrow { font-size: 13px; }
  /* Single centred column. Two columns pushed the carousel 261px left of
     page centre, which read as off-balance on a wide screen. */
  .case { grid-template-columns: minmax(0, 1fr); gap: 3.5rem; justify-items: center; }
  .nav-menu a { font-size: 6vw; }
  .lg-only { display: block; }

  /* From lg the meta row splits to the outer edges, clearing the centred
     cutout — matching the reference's "based in…" left / logos right. */
  .hero__stage { padding-inline: var(--gutter-md); max-width: calc(var(--rail-7xl) + var(--gutter-md) * 2); }

  .hero__meta {
    flex-direction: row;
    justify-content: space-between;
    align-items: flex-end;
    text-align: left;
    margin-top: 2rem;
  }
  .hero__meta > * { max-width: 30%; }
}

@media (min-width: 1280px) {
  h1, .h1 { font-size: 76px; }
  h2, .h2 { font-size: 84px; }
}
/* --------------------------------------------------------------------------
   Reduced motion
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0ms !important;
  }
  .reveal { opacity: 1; transform: none; }
  .grain, .cursor-dot { display: none; }

  /* The step artefacts must still read as finished objects once their
     animations are cut, rather than freezing on a start or end frame. */
  .viz__scan, .viz__pointer { display: none; }
  .viz__note { opacity: 1; transform: none; }

  /* Same rule for the "alive" layer: strip the motion, keep the lit look.
     The blanket 0.01ms above would otherwise leave the rim invisible and the
     wave frozen mid-dash. */
  .step::before { opacity: 0.55; }
  .viz__tile--fill::before,
  .viz__tile--grad::before,
  .viz__tile--dot i::after { display: none; }
  /* The wisps keep their look; only the drift stops. */
  .step__wisp i { transform: none; }
  /* main.js paints one still frame and stops; nothing to override here. */
  .viz__wave path { stroke-dasharray: none; stroke-dashoffset: 0; opacity: 0.55; }
}

/* ==========================================================================
   Mobile: shed compositing work

   iOS Safari has a hard per-tab memory ceiling and kills the tab when it is
   reached rather than degrading — the crash reported at the footer. Blend
   modes, masks and filters each force an offscreen buffer, and the cost is
   per-layer-area, so a phone pays nearly as much as a desktop for a
   full-viewport effect while having a fraction of the budget.

   Everything dropped here is atmosphere. Colour, layout and copy are
   untouched, so the design still reads — it just stops compositing a dozen
   full-screen buffers on a device that cannot afford them.
   ========================================================================== */
@media (max-width: 760px) {
  /* The three step cards: keep the aurora glow, drop the masked cloud plates
     and the dual-mask wisps. Two mask images composited per layer is the
     single most expensive thing in that section. */
  .step__sky,
  .step__wisp,
  .process__wisp { display: none; }

  /* Screen-blending a full-viewport plume is the footer's biggest cost.
     Painting it normally at lower opacity looks near-identical against a
     background this dark, and costs no offscreen buffer. */
  .footer__plume {
    mix-blend-mode: normal;
    opacity: 0.5;
  }

  /* The fog transition's five blurred, masked layers cover the whole screen
     at once — the worst case for a mobile GPU, and it fires on the very
     navigation a phone user is most likely to make. */
  .cloud-transition { display: none; }
}

/* ==========================================================================
   Cloud / fog transition
   Fires once as the carousel scrolls out of the top of the viewport: a bank of
   cloud wells up from the bottom, whites the viewport out completely, then
   lifts away to reveal what is underneath — a scene change rather than a fade.

   Built on real noise textures (assets/clouds/*.png), not gradients. Gradient
   blobs cannot produce wispy cloud structure; those PNGs are fractal-Brownian
   value noise put through a levels curve so they have dense cores AND real
   gaps, which is what makes the bank read as volume rather than as a scrim.

   The textures are white with an alpha channel and are used as MASKS, so the
   colour comes from `background-color` and every layer can be tinted
   independently — silver for the body, grey for the shadowed masses.

   Performance:
   · Only `transform` and `opacity` animate.
   · The texture supplies the softness, so blur stays tiny (10px, not 48px) —
     far cheaper than the gradient version this replaces.
   · `will-change` is applied ONLY while active.
   ========================================================================== */
.cloud-transition {
  position: fixed;
  inset: 0;
  z-index: 9990;              /* above content, below grain (9999) and cursor */
  pointer-events: none;       /* never blocks scrolling or clicks */
  opacity: 0;
  visibility: hidden;
  overflow: hidden;
  contain: strict;
}

.cloud-transition.is-active {
  visibility: visible;
  opacity: 1;
}

/* The wash. Textures alone leave pinholes; this guarantees a true whiteout at
   the peak, which is what sells it as a transition instead of an overlay. */
.cloud-transition::before {
  content: "";
  position: absolute;
  inset: -10%;
  /* Stays comparatively light on purpose. The darkened cloud layers paint on
     top of this, so the wash is the ground they read against — take it dark
     too and the whole thing flattens into one grey slab with no contrast.

     Transparent at BOTH ends. It was opaque at 0% (its bottom), and since it
     also translates upward, that opaque edge swept across the screen as a
     straight horizontal line — half of the "boxy" look at the end of the rise. */
  background: linear-gradient(to top,
              rgba(244, 245, 245, 0) 0%,
              #F4F5F5 16%,
              var(--blob-light-cool) 46%,
              var(--ink-250) 78%,
              rgba(163, 163, 163, 0) 100%);
  opacity: 0;
  transform: translate3d(0, 0, 0);
}
.cloud-transition.is-active::before {
  animation: fog-wash 2800ms cubic-bezier(0.42, 0, 0.3, 1) forwards;
}

@keyframes fog-wash {
  0%   { opacity: 0;    transform: translate3d(0, 46%, 0); }
  34%  { opacity: 0.96; transform: translate3d(0, 6%, 0); }
  46%  { opacity: 1;    transform: translate3d(0, 0, 0); }
  58%  { opacity: 1;    transform: translate3d(0, -4%, 0); }
  100% { opacity: 0;    transform: translate3d(0, -40%, 0); }
}

/* --- Texture layers ------------------------------------------------------- */
.cloud-blob {
  position: absolute;
  left: -25%;
  width: 150%;
  height: 130%;
  bottom: -30%;
  opacity: 0;
  /* Only a light blur now: the texture carries its own soft falloff. The 16px
     was there to hide hard threshold edges that no longer exist, and it was
     the most expensive part of this effect on mobile. */
  filter: blur(5px);
  transform: translate3d(0, 60%, 0);
  -webkit-mask-repeat: repeat-x;
          mask-repeat: repeat-x;
  -webkit-mask-position: center bottom;
          mask-position: center bottom;
}

.cloud-transition.is-active .cloud-blob { will-change: transform, opacity; }

/* Densest core, pure white — the body of the bank */
.cloud-blob:nth-child(1) {
  background-color: var(--ink-300);      /* dense body — was #FFFFFF */
  -webkit-mask-image: url("assets/clouds/cloud-c.png");
          mask-image: url("assets/clouds/cloud-c.png");
  -webkit-mask-size: 150% 100%;
          mask-size: 150% 100%;
}
.cloud-transition.is-active .cloud-blob:nth-child(1) { animation: fog-rise-1 2800ms cubic-bezier(0.36, 0, 0.28, 1) forwards; }
/* Silver mid-body, slower — parallax against layer 1 */
.cloud-blob:nth-child(2) {
  background-color: var(--ink-400);      /* mid — was --blob-light-cool */
  -webkit-mask-image: url("assets/clouds/cloud-b.png");
          mask-image: url("assets/clouds/cloud-b.png");
  -webkit-mask-size: 190% 118%;
          mask-size: 190% 118%;
}
.cloud-transition.is-active .cloud-blob:nth-child(2) { animation: fog-rise-2 3050ms cubic-bezier(0.3, 0, 0.32, 1) 70ms forwards; }
/* Wispy leading edge, fastest — arrives first */
.cloud-blob:nth-child(3) {
  background-color: var(--blob-light-cool); /* leading wisp stays light: it is what the dark masses read against */
  -webkit-mask-image: url("assets/clouds/cloud-a.png");
          mask-image: url("assets/clouds/cloud-a.png");
  -webkit-mask-size: 128% 88%;
          mask-size: 128% 88%;
}
.cloud-transition.is-active .cloud-blob:nth-child(3) { animation: fog-rise-3 2550ms cubic-bezier(0.42, 0, 0.24, 1) forwards; }
/* Shadowed mass, keeps the bank from reading as flat white */
.cloud-blob:nth-child(4) {
  background-color: var(--ink-600);      /* shadow mass — was --ink-300 */
  opacity: 0;
  -webkit-mask-image: url("assets/clouds/cloud-b.png");
          mask-image: url("assets/clouds/cloud-b.png");
  -webkit-mask-size: 165% 105%;
          mask-size: 165% 105%;
}
.cloud-transition.is-active .cloud-blob:nth-child(4) { animation: fog-rise-4 3200ms cubic-bezier(0.28, 0, 0.34, 1) 130ms forwards; }
/* Trailing wisps, last out — what you see clearing off the new section */
.cloud-blob:nth-child(5) {
  background-color: var(--ink-250);      /* trailing — was #FFFFFF */
  -webkit-mask-image: url("assets/clouds/cloud-a.png");
          mask-image: url("assets/clouds/cloud-a.png");
  -webkit-mask-size: 210% 130%;
          mask-size: 210% 130%;
}
.cloud-transition.is-active .cloud-blob:nth-child(5) { animation: fog-rise-5 3400ms cubic-bezier(0.25, 0, 0.36, 1) 190ms forwards; }

/* --- Arrival: the same animations, resumed at the point of full cover ------
   index.html covers the screen and navigates at PEAK_MS (main.js), which is
   where fog-wash reaches opacity 1. This page picks the timeline up there
   rather than replaying the rise, so the two halves join invisibly.

   A NEGATIVE animation-delay starts an animation partway through, which is
   what makes this work with no second set of keyframes to keep in sync. Each
   layer's own delay is subtracted, so they all resume at the same instant on
   the shared clock: -(PEAK - ownDelay).
   -------------------------------------------------------------------------- */
.cloud-transition.is-arriving::before,
.cloud-transition.is-arriving .cloud-blob { animation-play-state: running; }

.cloud-transition.is-active.is-arriving::before                  { animation-delay: -1290ms; }
.cloud-transition.is-active.is-arriving .cloud-blob:nth-child(1) { animation-delay: -1290ms; }
.cloud-transition.is-active.is-arriving .cloud-blob:nth-child(2) { animation-delay: -1220ms; }  /* own delay 70ms */
.cloud-transition.is-active.is-arriving .cloud-blob:nth-child(3) { animation-delay: -1290ms; }
.cloud-transition.is-active.is-arriving .cloud-blob:nth-child(4) { animation-delay: -1160ms; }  /* own delay 130ms */
.cloud-transition.is-active.is-arriving .cloud-blob:nth-child(5) { animation-delay: -1100ms; }  /* own delay 190ms */

/* Each rises further and at a different rate. The X drift stops the bank from
   looking like a single sheet being pushed up. */
@keyframes fog-rise-1 {
  0%   { opacity: 0;    transform: translate3d(0, 62%, 0) scale(1); }
  30%  { opacity: 1; }
  52%  { opacity: 1;    transform: translate3d(-2%, -2%, 0) scale(1.08); }
  100% { opacity: 0;    transform: translate3d(-5%, -62%, 0) scale(1.18); }
}
@keyframes fog-rise-2 {
  0%   { opacity: 0;    transform: translate3d(3%, 70%, 0) scale(1.02); }
  34%  { opacity: 0.92; }
  56%  { opacity: 0.88; transform: translate3d(1%, 2%, 0) scale(1.12); }
  100% { opacity: 0;    transform: translate3d(-3%, -54%, 0) scale(1.24); }
}
@keyframes fog-rise-3 {
  0%   { opacity: 0;    transform: translate3d(-4%, 54%, 0) scale(0.98); }
  26%  { opacity: 0.9; }
  50%  { opacity: 0.82; transform: translate3d(2%, -8%, 0) scale(1.1); }
  100% { opacity: 0;    transform: translate3d(6%, -74%, 0) scale(1.2); }
}
@keyframes fog-rise-4 {
  0%   { opacity: 0;    transform: translate3d(2%, 76%, 0) scale(1.04); }
  36%  { opacity: 0.58; }
  58%  { opacity: 0.5;  transform: translate3d(-2%, 6%, 0) scale(1.14); }
  100% { opacity: 0;    transform: translate3d(-7%, -48%, 0) scale(1.26); }
}
@keyframes fog-rise-5 {
  0%   { opacity: 0;    transform: translate3d(-6%, 82%, 0) scale(1.06); }
  40%  { opacity: 0.78; }
  62%  { opacity: 0.7;  transform: translate3d(3%, 10%, 0) scale(1.16); }
  100% { opacity: 0;    transform: translate3d(8%, -44%, 0) scale(1.3); }
}

/* --- Mobile: cheaper, but keep full coverage ------------------------------ */
@media (max-width: 480px) {
  .cloud-blob { filter: blur(3px); }
  .cloud-blob:nth-child(5) { display: none; }   /* one less layer to composite */
}

/* --- Reduced motion: no rising bank, just a soft wash --------------------- */
@media (prefers-reduced-motion: reduce) {
  .cloud-transition.is-active .cloud-blob { animation: none; opacity: 0; will-change: auto; }
  .cloud-transition.is-active::before {
    animation: fog-fade 800ms linear forwards;
    /* The global reduced-motion reset lower down forces
       `animation-duration: 0.01ms !important` on every element. Without a
       matching !important the fallback collapses to an instant blink. */
    animation-duration: 800ms !important;
    transform: none;
  }
  @keyframes fog-fade {
    0%   { opacity: 0; }
    45%  { opacity: 0.92; }
    100% { opacity: 0; }
  }
}
