/* ─── Reset & base ─────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
[hidden] { display: none !important; }
:root {
  /* ── Brand palette ──────────────────────────────────────────────
     Raw brand colours. Nothing below this block restates a hex or an
     rgba triplet; if a value is needed that is not here, add it here. */
  --bg:            #08091D;
  --bg-footer:     #000000;
  --accent:        #3C1B5D;
  --cyan:          #16D6D1;
  --cyan-bright:   #1AC9E6;  /* lighter stop of the primary-button gradient */
  --card-from:     #0D0D26;
  --card-to:       #3C1B5D;  /* same hex as --accent today, but it is a
                                gradient stop rather than the brand colour,
                                so the two can diverge independently */
  --cta-to:        #16323A;
  --white:         #FFFFFF;

  /* ── Alpha ramps ────────────────────────────────────────────────
     Named channel + opacity, so a tint gets picked rather than invented. */
  --white-04:      rgba(255,255,255,.04);
  --white-05:      rgba(255,255,255,.05);
  --white-07:      rgba(255,255,255,.07);
  --white-08:      rgba(255,255,255,.08);
  --white-10:      rgba(255,255,255,.1);
  --white-12:      rgba(255,255,255,.12);
  --white-30:      rgba(255,255,255,0.3);
  --cyan-08:       rgba(22,214,209,.08);
  --cyan-10:       rgba(22,214,209,.1);
  --cyan-15:       rgba(22,214,209,.15);
  --cyan-25:       rgba(22,214,209,.25);
  --cyan-40:       rgba(22,214,209,.4);
  --cyan-55:       rgba(22,214,209,.55);
  --accent-25:     rgba(60,27,93,.25);
  --accent-55:     rgba(60,27,93,.55);
  --accent-60:     rgba(60,27,93,.6);
  --accent-80:     rgba(60,27,93,.8);
  --bg-85:         rgba(8,9,29,.85);
  --black-40:      rgba(0,0,0,.4);

  /* ── Semantic colour ────────────────────────────────────────────
     Role names. Some resolve to the same tint today; they stay separate
     so restyling a border can never silently move a surface. */
  --text-light:     rgba(255,255,255,0.80);
  --text-muted:     rgba(255,255,255,0.55);
  --border-hairline: var(--white-07);  /* nav underline, section rules */
  --border-card:    var(--white-08);
  --border-strong:  var(--white-10);
  --border-input:   var(--white-12);
  --border-btn:     var(--white-30);
  --border-divider: var(--white-30);   /* dashed connector between steps */
  --surface-raised: var(--white-07);   /* footer social chips */
  --surface-input:  var(--white-05);
  --surface-sheen:  var(--white-04);   /* top-light overlay on cards */

  /* ── Type ───────────────────────────────────────────────────────
     The three clamped title sizes are the fluid display steps; the
     numbered ramp is everything that does not scale with viewport. */
  --font-sans:     'Nunito Sans', sans-serif;
  --fs-2xs:        11px;
  --fs-xs:         12px;
  --fs-sm:         13px;
  --fs-md:         14px;
  --fs-lg:         15px;
  --fs-xl:         16px;
  --fs-2xl:        17px;
  --fs-3xl:        18px;
  --fs-4xl:        22px;
  --fs-5xl:        26px;
  --fs-6xl:        32px;
  --fs-7xl:        48px;
  --fs-title-cta:     clamp(24px, 3vw, 38px);
  --fs-title-section: clamp(28px, 4vw, 44px);
  --fs-title-hero:    clamp(34px, 5vw, 58px);

  --fw-semibold:   600;
  --fw-bold:       700;
  --fw-extrabold:  800;

  --lh-flush:      0;      /* badge wrappers, to kill inline descender gap */
  --lh-none:       1;
  --lh-tight:      1.1;
  --lh-title:      1.15;
  --lh-snug:       1.3;
  --lh-body:       1.6;
  --lh-relaxed:    1.7;

  --tracking-sm:   .05em;
  --tracking-md:   .08em;
  --tracking-lg:   .1em;
  --tracking-xl:   .12em;

  /* ── Space ──────────────────────────────────────────────────────
     Named by value: a 4px step scale with the handful of odd sizes the
     design already uses. Value-naming keeps substitution checkable at a
     glance; the rhythm aliases below are the dials worth turning. */
  --space-4:   4px;    --space-5:   5px;    --space-6:   6px;
  --space-8:   8px;    --space-10:  10px;   --space-11:  11px;
  --space-12:  12px;   --space-14:  14px;   --space-16:  16px;
  --space-20:  20px;   --space-24:  24px;   --space-26:  26px;
  --space-28:  28px;   --space-32:  32px;   --space-36:  36px;
  --space-40:  40px;   --space-44:  44px;   --space-48:  48px;
  --space-60:  60px;   --space-80:  80px;   --space-100: 100px;

  /* Vertical rhythm. Change these to re-pace the whole page. */
  --section-y:          var(--space-100);
  --section-y-sm:       var(--space-80);
  --section-header-gap: var(--space-60);
  --grid-gap:           var(--space-32);

  /* ── Radius ─────────────────────────────────────────────────────── */
  --radius-xs:     2px;
  --radius-sm:     8px;
  --radius-btn:    10px;
  --radius-md:     12px;
  --radius-lg:     20px;
  --radius-xl:     24px;
  --radius-pill:   100px;
  --radius-circle: 50%;

  /* ── Shadow ─────────────────────────────────────────────────────── */
  --shadow-hero:  drop-shadow(0 40px 80px var(--cyan-15));
  --shadow-split: drop-shadow(0 30px 60px var(--black-40));

  /* ── Motion ─────────────────────────────────────────────────────
     Durations and easings for interaction only. Scroll-reveal timings
     live in the motion layer's own tuning block, not here. */
  --focus-w:       2px;
  --focus-offset:  3px;

  --hover-lift:    -6px;   /* card */
  --hover-lift-sm: -2px;   /* caption, store badge */
  --hover-lift-xs: -1px;   /* button */
  --hover-dim:     .85;    /* icon under a card hover */
  --hover-opacity: .88;

  --reveal-dur:     .9s;
  --reveal-stagger: .09s;
  --reveal-shift:   32px;
  --ease-reveal:    cubic-bezier(0.22, 1, 0.36, 1);
  --line-dur:       1.5s;
  --ease-line:      cubic-bezier(0.45, 0, 0.55, 1);
  --sweep-from:     -20%;
  --sweep-to:       100%;

  --dur-fast:      .2s;
  --dur-base:      .25s;
  --dur-slow:      .3s;
  --ease-default:  ease;
  --ease-emphasis: cubic-bezier(.4,0,.2,1);

  /* ── Layout ─────────────────────────────────────────────────────── */
  --veil-dur:      .55s;

  /* Hero illustration. The source PNG used to carry ~27% transparent
     padding, which made the artwork read far smaller than the box holding
     it. That padding is cropped out of the asset now, so the element width
     and the visible artwork are the same thing and this is a plain cap. */
  --hero-img-max:   560px;

  /* Card rail (phones). Width of one card, and the gap between them. The
     card width is what decides how much of the next one peeks in at the
     right edge - that peek is the only affordance telling you the row
     scrolls, so keep it well under 100vw. */
  --rail-card-w:    min(76vw, 300px);
  --rail-gap:       var(--space-16);
  --rail-btn-size:  40px;

  /* Full-screen mobile menu. --menu-fs is the size of a nav item; the two
     durations are the panel's fade and an item's rise, and the stagger is
     the delay added per item as they come in. */
  --menu-fs:         clamp(1.5rem, 7.5vw, 2.125rem);
  --menu-dur:        .42s;
  --menu-item-dur:   .62s;
  --menu-item-shift: 1.4rem;
  --menu-stagger:    .055s;

  /* The space the eyebrow badge used to occupy above the headline - its own
     rendered height plus its bottom margin. The badge is gone, but the
     hero's vertical rhythm was composed around it, so the space stays and
     nothing rides up. Mobile had the larger gap of the two. Measured, not
     guessed: removing the badge shortened the content column by exactly
     these amounts. */
  --hero-lead:      59px;

  /* Split-section illustrations. --split-img-max is the shared cap; the
     right-column pair (.split-image--sm) renders at --split-img-scale of
     its column, with the cap scaled by the same factor so the two cannot
     drift apart if either is retuned. */
  --split-img-max:   520px;
  --split-img-scale: 80%;

  /* ── Headline highlight dials ───────────────────────────────────────
     Two separate jobs, kept apart on purpose.

     The pads SIZE the ring: each one grows the ring box on that edge, so
     changing them changes how tightly the ink wraps the word.

     The nudges MOVE it: they shift the ring without resizing it, which is
     what you want for "a few px to the left". Either accepts any CSS
     length - 4px, -0.1em, whatever reads best. Positive x moves right,
     positive y moves down. */
  --hl-pad-x:      0.30em;
  --hl-pad-top:    0.42em;
  --hl-pad-bottom: 0.08em;
  --hl-nudge-x:    0px;
  --hl-nudge-y:    8px;

  /* The arrows' own position dials. Negative left pulls them back over the
     word; positive bottom lifts them further above it. */
  --hl-arrow-w:    1.25em;
  --hl-arrow-x:    -0.45em;
  --hl-arrow-y:    0.10em;

  --max-w:         1140px;
  --nav-h:         68px;
  --container-pad: var(--space-24);
}
body {
  font-family: var(--font-sans);
  background: var(--bg);
  color: var(--white);
  line-height: var(--lh-body);
  overflow-x: hidden;
}

/* ─── Utility ──────────────────────────────────── */
.container { max-width: var(--max-w); margin: 0 auto; padding: 0 var(--space-24); }
.btn-primary {
  display: inline-flex; align-items: center; gap: var(--space-8);
  background: linear-gradient(90deg, var(--cyan-bright), var(--cyan));
  color: var(--white);
  font-family: var(--font-sans);
  font-weight: var(--fw-bold); font-size: var(--fs-lg);
  padding: var(--space-12) var(--space-28); border-radius: var(--radius-btn);
  border: none; cursor: pointer; text-decoration: none;
  transition: opacity var(--dur-fast), translate var(--dur-fast);
}
.btn-primary:hover { opacity: var(--hover-opacity); translate: 0 var(--hover-lift-xs); }
.btn-outline {
  display: inline-flex; align-items: center; gap: var(--space-8);
  background: transparent; color: var(--white);
  font-family: var(--font-sans);
  font-weight: var(--fw-semibold); font-size: var(--fs-lg);
  padding: var(--space-11) var(--space-28); border-radius: var(--radius-btn);
  border: 1.5px solid var(--border-btn);
  cursor: pointer; text-decoration: none;
  transition: border-color var(--dur-fast), background var(--dur-fast);
}
.btn-outline:hover { border-color: var(--cyan); background: var(--cyan-08); }
.section-label {
  font-size: var(--fs-xs); font-weight: var(--fw-bold); letter-spacing: var(--tracking-xl);
  text-transform: uppercase; color: var(--cyan);
  margin-bottom: var(--space-12); display: block;
}
.section-title {
  font-size: var(--fs-title-section); font-weight: var(--fw-extrabold);
  line-height: var(--lh-title); margin-bottom: var(--space-16);
}
.section-body { font-size: var(--fs-xl); color: var(--text-light); max-width: 520px; }

/* ─── Nav ──────────────────────────────────────── */
/* One height, always: the compact bar. The header sits in normal flow and
   pins at the top immediately.

   It was previously taller at rest, with that extra block scrolling away
   before the bar pinned - a negative sticky offset measured in JS. With a
   single height there is nothing to travel, so the offset and its
   measurement are gone rather than left in place computing zero.

   Sticky rather than fixed: the header occupies real flow space, so the
   hero sizes around it instead of padding itself clear of an overlay. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  /* Transparent over the hero, taking its background once the page has
     scrolled - .is-solid is set in JS past a small threshold.

     The background and the blur both live on ::before rather than here,
     and that is load-bearing rather than tidiness: backdrop-filter makes
     an element a containing block for its position:fixed descendants, so
     with the filter on the header itself the full-screen menu sized
     itself to the header - 430x156 instead of 430x900. On a pseudo-
     element the filter still paints, but the panel resolves against the
     viewport as intended.

     The blur stays on in both states rather than switching with the
     colour: at rest the header sits in flow with nothing behind it, so it
     has nothing to act on, and animating a filter on and off is the one
     part of this that janks. Only the colour animates. */
  background-color: transparent;
}
.site-header::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-color: transparent;
  backdrop-filter: blur(16px);
  transition: background-color var(--dur-base) var(--ease-default);
}
/* An open menu needs the solid bar whatever the scroll position, or the
   panel hangs off a transparent strip with the hero showing through it. */
.site-header.is-solid::before,
.site-header.is-menu-open::before {
  background-color: var(--bg-85);
}
.nav-inner {
  display: flex; align-items: center; justify-content: space-between;
  height: 68px; gap: var(--space-32);
}
.nav-logo img { height: 22px; width: auto; display: block; }
.nav-links {
  display: flex; align-items: center; gap: var(--space-32);
  list-style: none;
}
.nav-links a {
  color: var(--text-light); text-decoration: none;
  font-size: var(--fs-md); font-weight: var(--fw-semibold);
  transition: color var(--dur-fast);
}
.nav-links a:hover { color: var(--white); }
.nav-cta { display: flex; align-items: center; gap: var(--space-12); }
.nav-hamburger { display: none; background: none; border: none; cursor: pointer; padding: var(--space-4); }
.nav-hamburger span {
  display: block; width: 22px; height: 2px;
  background: var(--white); margin: var(--space-5) 0;
  transition: transform var(--dur-slow) var(--ease-emphasis), opacity var(--dur-fast) var(--ease-default), margin var(--dur-slow) var(--ease-default);
  transform-origin: center;
}
.site-header.is-menu-open .nav-hamburger span:nth-child(1) {
  transform: translateY(7px) rotate(45deg);
}
.site-header.is-menu-open .nav-hamburger span:nth-child(2) {
  opacity: 0;
}
.site-header.is-menu-open .nav-hamburger span:nth-child(3) {
  transform: translateY(-7px) rotate(-45deg);
}

/* ─── Hero ─────────────────────────────────────── */
.hero {
  /* The header used to be fixed, so the hero padded itself clear of it.
     Now the header occupies real flow space and the hero only has to fill
     what is left of the first screen. --header-h is measured in JS. */
  /* svh, not vh. On mobile Safari and Chrome 100vh is the LARGE viewport -
     the height with the URL bar hidden - but the bar is showing on load, so
     a vh-sized hero is taller than what is actually on screen and the
     bottom of it is cut off until the user scrolls. svh is the small
     viewport: the height with the bar visible, which is the one that has to
     fit. The vh line above stays as the fallback for engines without it. */
  min-height: calc(100vh - var(--header-h, 0px));
  min-height: calc(100svh - var(--header-h, 0px));
  display: flex; align-items: center;
  padding-top: var(--space-32); padding-bottom: var(--space-80);
  position: relative; overflow: hidden;
}
.hero-inner {
  display: grid;
  /* minmax(0, 1fr), not 1fr: a bare 1fr carries an automatic minimum, so the
     oversized illustration would inflate its own column and squeeze the
     headline into an extra line. The columns stay even and the image
     overflows past them instead. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-60); align-items: center; width: 100%;
}
/* Holds the space the eyebrow badge used to occupy, so removing the badge
   did not pull the whole hero up. See --hero-lead. */
.hero-content { padding-top: var(--hero-lead); }
.hero-title {
  font-size: var(--fs-title-hero); font-weight: var(--fw-extrabold);
  line-height: var(--lh-tight); margin-bottom: var(--space-20);
}
.hero-sub {
  font-size: var(--fs-2xl); color: var(--text-light);
  margin-bottom: var(--space-36); max-width: 420px;
}
.hero-badges {
  display: flex; gap: var(--space-12); flex-wrap: wrap;
  margin-bottom: var(--space-48);
}
.store-badge-img {
  display: inline-flex; line-height: var(--lh-flush);
  transition: translate var(--dur-fast), opacity var(--dur-fast);
}
.store-badge-img:hover { translate: 0 var(--hover-lift-sm); opacity: var(--hover-opacity); }
.store-badge-img img { height: 48px; width: auto; display: block; }
@media (max-width: 480px) {
  .store-badge-img img { height: 42px; }
}
.hero-image { display: flex; align-items: center; justify-content: flex-end; }
.hero-image img {
  width: 100%;
  max-width: var(--hero-img-max);
  filter: drop-shadow(0 40px 80px var(--cyan-15));
}

/* ─── Steps ─────────────────────────────────────── */
.steps {
  padding: var(--space-26) 0;
  background: linear-gradient(90deg, var(--accent-55), var(--accent-25));
  border-top: 1px solid var(--border-hairline);
  border-bottom: 1px solid var(--border-hairline);
}
.steps-inner {
  display: grid; grid-template-columns: auto 1fr auto 1fr auto;
  align-items: center; gap: var(--space-16);
}
.step { text-align: center; white-space: nowrap; display: flex; align-items: center; gap: var(--space-10); }
.step-num {
  font-size: var(--fs-5xl); font-weight: var(--fw-extrabold); color: var(--white);
  line-height: var(--lh-none);
}
.step-label { font-size: var(--fs-md); font-weight: var(--fw-semibold); color: var(--text-light); }
.step-divider {
  height: 0; width: 100%; min-width: 40px;
  border-top: 1.5px dashed var(--border-divider);
}

/* ─── Features grid ─────────────────────────────── */
.features { padding: var(--section-y) 0; }
.features-header { text-align: center; margin-bottom: var(--space-60); }
.features-header .section-body { margin: 0 auto; }
.features-grid {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: var(--space-32);
}
.feature-card,
.why-card {
  background: linear-gradient(160deg, var(--card-from), var(--card-to));
  border: 1px solid var(--border-card);
  border-radius: var(--radius-lg);
  padding: var(--space-36) var(--space-24) var(--space-32);
  text-align: center;
  aspect-ratio: 1 / 1.52;
  display: grid;
  grid-template-rows: 90px 56px 1fr;
  row-gap: var(--space-20);
  align-items: start;
  /* The hover lift uses the independent `translate` property rather than
     `transform`, because the reveal animates transform on these same
     cards. Sharing one property would mean this transition smears every
     inline write the tween makes; as separate properties they compose
     and transition independently. */
  transition: translate var(--dur-base);
  position: relative;
  overflow: hidden;
}
.feature-card::before,
.why-card::before {
  content: '';
  position: absolute; inset: 0;
  border-radius: var(--radius-lg);
  background: linear-gradient(180deg, var(--surface-sheen) 0%, transparent 60%);
  pointer-events: none;
}
/* ─── Card hover ───────────────────────────────────
   Adapted from a media card: this card's icon stands in for the image and
   its name for the caption. The card lifts, its icon dims slightly and the
   name rises with it.

   Hover effects are gated on a real pointer. On touch these states latch
   after a tap and stay on, which on a card that is not a link reads as
   broken. */
.feature-icon img,
.why-icon img { transition: opacity var(--dur-base) var(--ease-default); }
.feature-name,
.why-title { transition: translate var(--dur-base) var(--ease-default); }

@media (hover: hover) and (pointer: fine) {
  .feature-card:hover,
  .why-card:hover { translate: 0 var(--hover-lift); }
  .feature-card:hover .feature-icon img,
  .why-card:hover .why-icon img { opacity: var(--hover-dim); }
  .feature-card:hover .feature-name,
  .why-card:hover .why-title { translate: 0 var(--hover-lift-sm); }
}
.feature-icon {
  display: flex; align-items: center; justify-content: center;
}
.feature-icon img {
  width: 80px; height: 80px;
}
.feature-name { font-size: var(--fs-3xl); font-weight: var(--fw-extrabold); line-height: var(--lh-snug); align-self: start; }
.feature-desc { font-size: var(--fs-md); color: var(--text-muted); line-height: var(--lh-relaxed); }

/* ─── Split sections ─────────────────────────────── */
.split { padding: var(--section-y-sm) 0; }
.split-inner {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: var(--space-80); align-items: center;
}
.split-inner.reverse { direction: rtl; }
.split-inner.reverse > * { direction: ltr; }
.split-image img {
  width: 100%; max-width: var(--split-img-max);
  filter: drop-shadow(0 30px 60px var(--black-40));
}
.split-image { display: flex; justify-content: center; }
/* Shrunk against its column and pinned to the column's right edge, so the
   reduction comes off the inner side only and the outer edge stays level
   with the content above and below it. */
.split-image--sm { justify-content: flex-end; }
.split-image--sm img {
  width: var(--split-img-scale);
  max-width: calc(var(--split-img-max) * 0.8);
}

/* ─── Card rail controls ───────────────────────────
   The rails scroll natively under a thumb. With a mouse there is no way
   in at all: the scrollbar is hidden, and a wheel only sends deltaY,
   which a box that overflows horizontally ignores. These controls are
   the way in, and they exist only where a real pointer does - a phone
   keeps exactly the rail it had and renders none of this.

   Below the rail rather than overlaying its edges: at these widths the
   cards are nearly the full screen, so edge buttons would sit on top of
   the content they are there to reveal. */
.rail-nav { display: none; }

.rail-nav-btn {
  width: var(--rail-btn-size);
  height: var(--rail-btn-size);
  border-radius: var(--radius-circle);
  background: var(--surface-raised);
  border: 1px solid var(--border-card);
  color: var(--white);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 0;
  transition: background var(--dur-fast) var(--ease-default),
              border-color var(--dur-fast) var(--ease-default),
              opacity var(--dur-fast) var(--ease-default);
}
.rail-nav-btn svg { width: 16px; height: 16px; display: block; }
.rail-nav-btn:disabled { opacity: .3; cursor: default; }

@media (hover: hover) and (pointer: fine) {
  .rail-nav-btn:not(:disabled):hover {
    background: var(--cyan-10);
    border-color: var(--cyan-25);
  }
}

/* Shown only below the rail breakpoint AND only for a real pointer. */
@media (max-width: 768px) and (hover: hover) and (pointer: fine) {
  .rail-nav {
    display: flex;
    justify-content: center;
    gap: var(--space-12);
    margin-top: var(--space-24);
  }
  .features-grid,
  .why-grid,
  .testimonials-grid { cursor: grab; }
  /* Snapping fights a drag in progress, so it is lifted for the duration
     and restored on release, where it settles the rail onto a card. */
  .features-grid.is-dragging,
  .why-grid.is-dragging,
  .testimonials-grid.is-dragging {
    cursor: grabbing;
    scroll-snap-type: none;
    user-select: none;
  }
}

/* ─── Why Bumpy ──────────────────────────────────── */
.why { padding: var(--section-y) 0; }
.why-header { margin-bottom: var(--space-48); }
.why-grid {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: var(--space-32);
}

.why-icon {
  display: flex; align-items: center; justify-content: center;
}
.why-icon img { width: 70px; height: 70px; }
.why-title { font-size: var(--fs-3xl); font-weight: var(--fw-extrabold); line-height: var(--lh-snug); align-self: start; }
.why-desc { font-size: var(--fs-md); color: var(--text-muted); line-height: var(--lh-relaxed); }

/* ─── Pricing ────────────────────────────────────── */
.pricing { padding: var(--section-y) 0; }
.pricing-header { text-align: center; margin-bottom: var(--space-60); }
.pricing-grid {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: var(--space-24); align-items: start;
}
.pricing-card {
  background: linear-gradient(160deg, var(--card-from), var(--card-to));
  border: 1px solid var(--border-card);
  border-radius: var(--radius-lg);
  padding: var(--space-40) var(--space-32);
  position: relative; overflow: hidden;
}
.pricing-card.featured {
  border-color: var(--cyan);
  background: linear-gradient(160deg, var(--cyan-08), var(--accent-80));
}
.pricing-card.featured::before {
  content: 'Most Popular';
  position: absolute; top: 20px; right: 20px;
  background: var(--cyan); color: var(--bg);
  font-size: var(--fs-2xs); font-weight: var(--fw-bold); letter-spacing: var(--tracking-sm);
  padding: var(--space-4) var(--space-12); border-radius: var(--radius-pill);
}
.pricing-tier { font-size: var(--fs-sm); font-weight: var(--fw-bold); color: var(--text-muted); letter-spacing: var(--tracking-lg); text-transform: uppercase; margin-bottom: var(--space-8); }
.pricing-price { font-size: var(--fs-7xl); font-weight: var(--fw-extrabold); line-height: var(--lh-none); margin-bottom: var(--space-4); }
.pricing-price sup { font-size: var(--fs-4xl); vertical-align: super; }
.pricing-period { font-size: var(--fs-sm); color: var(--text-muted); margin-bottom: var(--space-28); }
.pricing-divider { height: 1px; background: var(--border-strong); margin: var(--space-28) 0; }
.pricing-features { list-style: none; display: flex; flex-direction: column; gap: var(--space-12); margin-bottom: var(--space-36); }
.pricing-features li {
  display: flex; align-items: center; gap: var(--space-10);
  font-size: var(--fs-md); color: var(--text-light);
}
.pricing-features li::before {
  content: '✓'; flex-shrink: 0;
  width: 20px; height: 20px; border-radius: var(--radius-circle);
  background: var(--cyan-15); color: var(--cyan);
  display: flex; align-items: center; justify-content: center;
  font-size: var(--fs-xs); font-weight: var(--fw-bold);
}
.pricing-cta { width: 100%; text-align: center; }

/* ─── Market API ──────────────────────────────────── */
.market-api { padding: var(--section-y-sm) 0; }
.market-api-inner {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: var(--space-80); align-items: center;
}
.market-api-image img { width: 100%; max-width: 480px; }

/* ─── Testimonials ────────────────────────────────── */
.testimonials { padding: var(--section-y) 0; }
.testimonials-header { text-align: center; margin-bottom: var(--space-60); }
.testimonials-grid {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: var(--space-24);
}
.testimonial-card {
  background: linear-gradient(145deg, var(--card-from), var(--card-to));
  border: 1px solid var(--border-card);
  border-radius: var(--radius-lg); padding: var(--space-32);
}
.testimonial-quote {
  font-size: var(--fs-6xl); color: var(--cyan); line-height: var(--lh-none);
  margin-bottom: var(--space-16); font-weight: var(--fw-extrabold);
}
.testimonial-text { font-size: var(--fs-lg); color: var(--text-light); line-height: var(--lh-relaxed); margin-bottom: var(--space-24); }
.testimonial-author { display: flex; align-items: center; gap: var(--space-12); }
.testimonial-avatar {
  width: 40px; height: 40px; border-radius: var(--radius-circle);
  background: linear-gradient(135deg, var(--accent), var(--cyan));
  display: flex; align-items: center; justify-content: center;
  font-weight: var(--fw-bold); font-size: var(--fs-md); flex-shrink: 0;
}
.testimonial-name { font-size: var(--fs-md); font-weight: var(--fw-bold); }
.testimonial-role { font-size: var(--fs-sm); color: var(--text-muted); }

/* ─── CTA Banner ──────────────────────────────────── */
.cta-banner {
  padding: var(--section-y-sm) 0;
}
.cta-banner-inner {
  background:
    radial-gradient(circle at 75% 50%, var(--cyan-55) 0%, transparent 60%),
    linear-gradient(160deg, var(--card-from), var(--cta-to));
  border: 1px solid var(--cyan-40);
  border-radius: var(--radius-xl);
  padding: var(--space-44) var(--space-60);
  display: flex; justify-content: space-between; align-items: center;
  gap: var(--space-40); position: relative; overflow: hidden;
}
.cta-title { font-size: var(--fs-title-cta); font-weight: var(--fw-extrabold); max-width: 480px; }
.cta-sub { font-size: var(--fs-xl); color: var(--text-light); margin-top: var(--space-10); }
.cta-right { display: flex; flex-direction: column; gap: var(--space-12); align-items: flex-end; flex-shrink: 0; }

/* ─── Contact ─────────────────────────────────────── */
.contact { padding: var(--section-y) 0; }
.contact-header { text-align: center; margin-bottom: var(--space-60); }
.contact-form-wrap {
  max-width: 640px; margin: 0 auto;
  background: linear-gradient(145deg, var(--card-from), var(--card-to));
  border: 1px solid var(--border-card);
  border-radius: var(--radius-lg);
  padding: var(--space-48) var(--space-40);
}
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-16); }
.form-group { display: flex; flex-direction: column; gap: var(--space-8); margin-bottom: var(--space-20); }
.form-group label { font-size: var(--fs-md); font-weight: var(--fw-semibold); color: var(--text-light); }
.form-group input, .form-group textarea, .form-group select {
  background: var(--surface-input);
  border: 1px solid var(--border-input);
  border-radius: var(--radius-sm);
  padding: var(--space-14) var(--space-16); color: var(--white);
  font-family: var(--font-sans);
  font-size: var(--fs-lg);
  transition: border-color var(--dur-fast);
  width: 100%;
}
.form-group input:focus, .form-group textarea:focus, .form-group select:focus {
  border-color: var(--cyan);
}
.form-group input::placeholder, .form-group textarea::placeholder {
  color: var(--text-muted);
}
.form-group textarea { resize: vertical; min-height: 120px; }
.form-group select { appearance: none; cursor: pointer; }
.form-submit { width: 100%; padding: var(--space-16); font-size: var(--fs-xl); }
.form-success {
  text-align: center; padding: var(--space-20);
  color: var(--cyan); font-weight: var(--fw-bold); font-size: var(--fs-xl);
}

/* ─── Footer ──────────────────────────────────────── */
footer {
  background: var(--bg-footer);
  border-top: 1px solid var(--border-hairline);
  padding: var(--space-40) 0 var(--space-28);
}
.footer-top {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--space-40); margin-bottom: var(--space-28);
}
.footer-brand img { height: 20px; width: auto; margin-bottom: var(--space-12); display: block; }
.footer-tagline { font-size: var(--fs-md); color: var(--text-muted); max-width: 260px; line-height: var(--lh-body); }
.footer-col h4 { font-size: var(--fs-sm); font-weight: var(--fw-bold); letter-spacing: var(--tracking-md); text-transform: uppercase; color: var(--text-muted); margin-bottom: var(--space-14); }
.footer-col ul { list-style: none; display: flex; flex-direction: column; gap: var(--space-10); text-align: right; }
.footer-col ul li a { text-decoration: none; color: var(--text-light); font-size: var(--fs-md); transition: color var(--dur-fast); }
.footer-col ul li a:hover { color: var(--white); }
.footer-bottom {
  display: flex; align-items: center; justify-content: space-between;
  border-top: 1px solid var(--border-hairline);
  padding-top: var(--space-24);
}
.footer-copy { font-size: var(--fs-sm); color: var(--text-muted); }
.footer-socials { display: flex; gap: var(--space-14); }
.footer-socials a {
  width: 32px; height: 32px; border-radius: var(--radius-circle);
  background: var(--surface-raised);
  display: flex; align-items: center; justify-content: center;
  text-decoration: none; color: var(--text-light);
  transition: background var(--dur-fast); flex-shrink: 0;
}
.footer-socials a:hover { background: var(--cyan); }
.footer-socials a img { width: 13px; height: auto; display: block; }
.footer-socials a.social-facebook img { width: 8px; }
.footer-socials a.social-instagram img,
.footer-socials a.social-linkedin img { width: 12px; }
.footer-socials a.social-twitter img { width: 14px; }

/* ─── Headline highlight ───────────────────────────
   A hand-drawn ring and two arrows that stay attached to the word at every
   viewport width, including when the line rewraps and the word moves.
   Everything is anchored to the word's own box and measured in em, so it
   tracks both position and type size with no JS and no fixed pixels.

   The ring is the original hand-drawn artwork: a filled outline, which is
   where its uneven, inked character comes from. preserveAspectRatio="none"
   stretches it to whatever box the word occupies, and its viewBox is the
   ink's own tight bounding box so the drawing fills that box edge to edge.

   Stretching a filled outline does skew its thickness, since there is no
   stroke to hold constant - but only by however far the box's aspect ratio
   departs from the artwork's. The drawing is 178.42 x 62.45 (2.857) and the
   ring box runs about 3.054, which works out at 1.069: under 7%, and
   invisible on a line that is hand-drawn and already uneven. Re-drawing it
   as a uniform stroked path did make the geometry exact, and lost the
   character that made it worth having.

   The arrows keep their aspect ratio and hang off the word's top-right
   corner, pointing at it from the same angle at any size. */
.hl {
  position: relative;
  display: inline-block;
  /* the ring must wrap one unbroken word, never half of it across a line */
  white-space: nowrap;
  /* Establishes a stacking context so .hl-ring's negative z-index puts the
     ink behind the word without dropping it behind the hero background. */
  z-index: 0;
}
.hl-ring {
  position: absolute;
  /* Breathing room around the word; em keeps it proportional to the type.
     Both axes are sized explicitly rather than left to auto: an SVG with a
     viewBox carries an intrinsic aspect ratio, and with height:auto that
     ratio wins over the bottom inset - the ring came out 141px tall around
     a 64px word. Stating both removes the ambiguity. */
  /* Proportions taken off the reference: the ring sits higher than it is
     deep, clearing the cap height generously while only just passing under
     the descenders. */
  top: calc(var(--hl-nudge-y) - var(--hl-pad-top));
  left: calc(var(--hl-nudge-x) - var(--hl-pad-x));
  width: calc(100% + 2 * var(--hl-pad-x));
  height: calc(100% + var(--hl-pad-top) + var(--hl-pad-bottom));
  overflow: visible;
  pointer-events: none;
  z-index: -1;        /* the word reads on top of the ink */
}
.hl-arrows {
  position: absolute;
  /* Anchored to the word's top-right corner, so they keep pointing at it
     from the same angle however the line rewraps. height:auto is wanted
     here, unlike the ring: the arrows must keep their aspect ratio. */
  left: 100%;
  bottom: 100%;
  width: var(--hl-arrow-w);
  height: auto;
  margin-left: var(--hl-arrow-x);
  margin-bottom: var(--hl-arrow-y);
  overflow: visible;
  pointer-events: none;
}

/* Both illustrations now size to their own aspect ratio. They previously
   inherited a fixed height from the inline <svg> height attribute, which
   left the artwork letterboxed inside a taller box - about 330px of dead
   space under the private illustration at mobile widths. Phase 1 reproduced
   that verbatim to keep the extraction visually inert; this is where it is
   actually fixed. */

/* ─── Focus ────────────────────────────────────────
   There were no author focus styles at all before this: links and buttons
   relied on the UA ring, and the form fields cleared it outright. Restyling
   would have quietly dropped the only indicator keyboard users had.

   :focus-visible rather than :focus, so a mouse click does not leave a ring
   behind. The box-shadow fills the gap the outline-offset opens with the page
   background, which guarantees the cyan ring is always read against a dark
   surface - including on the cyan primary button and inside the CTA banner's
   cyan wash, where a bare cyan ring would otherwise vanish. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible {
  outline: var(--focus-w) solid var(--cyan);
  outline-offset: var(--focus-offset);
  box-shadow: 0 0 0 var(--focus-offset) var(--bg);
}

/* Skip link: the first thing a keyboard user reaches, visible only once
   focused. It sits above the veil so it cannot be covered on load. */
.skip-link {
  position: fixed;
  top: var(--space-8);
  left: var(--space-8);
  z-index: 10000;
  padding: var(--space-10) var(--space-16);
  background: var(--cyan);
  color: var(--bg);
  font-size: var(--fs-md);
  font-weight: var(--fw-bold);
  border-radius: var(--radius-btn);
  text-decoration: none;
  transform: translateY(calc(-100% - var(--space-16)));
}
/* :focus rather than :focus-visible - a skip link has to appear whenever it
   receives focus, including focus moved programmatically, or it can be
   tabbed onto while still off-screen. */
.skip-link:focus {
  transform: translateY(0);
}

/* ─── Page transition veil ─────────────────────────
   A fixed sheet painted in the page background: opaque on load, faded out
   once the first frames have rendered, and faded back in before an internal
   navigation leaves the page.

   It is transparent by default and only opaque under .js, so a visitor with
   JS off never meets it. Every path that gives up on motion clears it too -
   including the 4s failsafe in <head> - because a veil that never lifts is
   an entirely blank page, which is the worst thing this file could do. */
.page-veil {
  position: fixed;
  inset: 0;
  z-index: 9999;
  background: var(--bg);
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--veil-dur) var(--ease-reveal);
}
.js .page-veil { opacity: 1; }
.js .page-veil.is-clear { opacity: 0; }
.motion-off .page-veil { opacity: 0 !important; }

/* ─── Lenis ────────────────────────────────────────
   Lenis ships these five rules as a separate stylesheet. Inlined here
   instead of pulling another CDN file: it is five rules, and every extra
   CDN request is one more thing that can fail independently of the script
   that needs it. Kept verbatim so it stays recognisable against upstream.

   The scroll-behavior override matters even though the native smooth
   scroll was removed above - a UA or extension default would otherwise
   animate the same scroll position Lenis is already driving. */
html.lenis,
html.lenis body { height: auto; }

/* Locked while the full-screen menu is open. This takes both paths,
   because neither closes the door alone: overflow:hidden stops the
   browser's own scrolling but, per spec, still permits programmatic
   scrolling - which is exactly what Lenis does on every frame. The panel
   therefore also carries data-lenis-prevent, so Lenis ignores gestures
   over it and hands them to the browser, where this rule stops them.

   Scoped to the breakpoint that owns the menu, which makes the lock
   self-releasing: the panel only exists below 768px, so above it the rule
   stops applying and scrolling returns on its own. Without that, a
   teardown that failed to run would strand the page unscrollable with the
   hamburger already hidden - no gesture left to undo it. JS still tears
   the state down properly on resize; this is the floor under it. */
@media (max-width: 768px) {
  html.is-menu-locked,
  html.is-menu-locked body { overflow: hidden; }

  /* A root that cannot scroll also stops driving position:sticky, so the
     header resolves to its static position - which, if the menu was opened
     part-way down the page, is far above the viewport. The panel stays put
     (it is fixed) but the close button rides away with the header, leaving
     no visible way out. Pinning the header for the duration keeps the logo
     and X exactly where the user reached for them.

     Fixing it also pulls it out of flow, so body takes on its measured
     height as padding. Without that the page behind would shrink by the
     header's height on open and spring back on close - invisible under the
     panel, but a jump showing through it as it fades. */
  html.is-menu-locked .site-header {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
  }
  html.is-menu-locked body { padding-top: var(--header-h, 68px); }
}
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-smooth [data-lenis-prevent] { overscroll-behavior: contain; }
.lenis.lenis-stopped { overflow: hidden; }
.lenis.lenis-smooth iframe { pointer-events: none; }

/* ─── Scroll reveals ───────────────────────────────
   Three kinds, and deliberately only three:
     .reveal              text  - fades in, never moves
     .reveal.reveal--up   media - fades in and rises --reveal-shift
     .reveal.reveal--line rules - a mask sweeps along the line so it fades
                                  up progressively, left end first, without
                                  ever changing width or position

   Reveals start hidden, so every path that cannot animate them has to put
   them back: no JS at all, reduced motion, or a motion layer that threw or
   never reported in. Those all land on .motion-off / .no-js below.

   The transitions are armed only under .motion-fallback - the path taken
   when the GSAP CDN did not load. When GSAP is driving it writes inline
   styles every frame, and a CSS transition on the same properties would
   smear them. */

@property --sweep {
  syntax: '<percentage>';
  inherits: false;
  initial-value: 0%;
}

.reveal { opacity: 0; }
.reveal.reveal--up { transform: translateY(var(--reveal-shift)); }
.reveal.reveal--line {
  opacity: 1;                      /* the mask does the work, not opacity */
  --sweep: var(--sweep-from);
  -webkit-mask-image: linear-gradient(90deg, #000 0%, #000 var(--sweep), transparent calc(var(--sweep) + 18%));
          mask-image: linear-gradient(90deg, #000 0%, #000 var(--sweep), transparent calc(var(--sweep) + 18%));
}

/* A finished reveal holds its end state from CSS rather than from GSAP's
   inline styles. That is what lets those inline styles be cleared on
   completion: GSAP pins translate/rotate/scale to "none" while it owns
   transform, and until they are released a component cannot animate its own
   translate - the card hover lift simply would not move.
   Equal specificity to .reveal--up, so source order decides: keep it below. */
.reveal.is-revealed { opacity: 1; transform: none; }
.reveal.reveal--line.is-revealed { --sweep: var(--sweep-to); }

.motion-fallback .reveal {
  transition: opacity var(--reveal-dur) var(--ease-reveal),
              transform var(--reveal-dur) var(--ease-reveal);
}
.motion-fallback .reveal.reveal--line {
  transition: --sweep var(--line-dur) var(--ease-line);
}

.no-js .reveal,
.motion-off .reveal {
  opacity: 1;
  transform: none;
  --sweep: var(--sweep-to);
  transition: none;
}

/* ─── Responsive ──────────────────────────────────── */
/* The hero stacks from tablet width down. Two columns only really work once
   there is room for a readable measure beside the illustration; below this
   the headline is squeezed into a narrow ragged column while the hero's
   full-height rule leaves a large empty band under it. */
@media (max-width: 1024px) {
  /* row-gap folded into --hero-m-below-figure, so that one dial owns the
     whole figure-to-headline gap rather than half of it. */
  .hero-inner { grid-template-columns: 1fr; text-align: center; row-gap: 0; }

  /* ══ MOBILE HERO DIALS ══════════════════════════════════════════════
     Every vertical gap in the stacked hero, in order down the screen.
     Edit these six; the rules underneath only consume them.

       --hero-m-above-figure    header   → figure      (read the note)
       --hero-m-below-figure    figure   → headline
       --hero-m-headings        headline → sub-copy
       --hero-m-above-buttons   sub-copy → buttons
       --hero-m-below-buttons   buttons  → bottom of hero
       --hero-m-figure-max      size of the figure itself

     NOTE on --hero-m-above-figure. The hero fills the screen and is
     bottom-aligned, so on a tall phone the gap you actually see above the
     figure is this padding PLUS whatever height is left over - 132px at
     390x844 against a 20px dial. Turning this down changes nothing there;
     it only bites on short screens, where there is no leftover. To close
     that gap on a tall phone, grow --hero-m-figure-max instead.

     FLOOR on --hero-m-below-figure: the headline's arrows sit above the
     text and start colliding with the figure below about 34px.

     Why bottom-aligned at all: it puts the leftover height above the
     figure rather than splitting it evenly, which is what keeps the gap
     under the buttons equal to --hero-m-below-buttons instead of
     ballooning. Safe on short screens - min-height is a minimum, so when
     the content is taller the hero grows to fit and there is no leftover
     for the alignment to act on, and nothing gets pushed out of the clip.

     The figure is capped on viewport height as well as width, so it gives
     way on short screens instead of pushing the headings off. */
  :root {
    --hero-m-above-figure:  0px;
    --hero-m-below-figure:  clamp(56px, 12.8svh, 120px);
    --hero-m-headings:      clamp(14px, 2.6svh,  26px);
    --hero-m-above-buttons: clamp(26px, 5.9svh,  56px);
    --hero-m-below-buttons: clamp(22px, 5.9svh,  56px);
    --hero-m-figure-max:    min(422px, 44.9svh);
    --hero-m-title-fs:      clamp(26px, 7.4vw,   44px);
  }

  .hero {
    align-items: flex-end;
    padding-top: var(--hero-m-above-figure);
    padding-bottom: var(--hero-m-below-buttons);
  }
  /* one dial, not two: the figure's own margin is folded into the lead */
  .hero-image { order: -1; margin-bottom: 0; justify-content: center; }
  .hero-image img { width: 100%; max-width: var(--hero-m-figure-max); }
  .hero-content { --hero-lead: var(--hero-m-below-figure); }
  .hero-title { font-size: var(--hero-m-title-fs); margin-bottom: var(--hero-m-headings); }
  .hero-sub { font-size: var(--fs-lg); margin: 0 auto var(--hero-m-above-buttons); }
  .hero-badges { justify-content: center; margin-bottom: 0; }

  .features-grid { grid-template-columns: repeat(2, 1fr); margin: 0; }
  .why-grid { grid-template-columns: repeat(2, 1fr); }
  .pricing-grid { grid-template-columns: repeat(2, 1fr); }
  .footer-top { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 768px) {
  /* ── Full-screen menu ───────────────────────────────────────────────
     The panel is fixed to the viewport rather than hung off the header,
     so it covers the page completely. Its state is still the single
     .is-menu-open class on the header.

     visibility (rather than display) is what lets the fade play at all,
     and pairing it with a zero-length transition delayed by the fade is
     what keeps the panel hit-testable on the way out instead of
     vanishing the instant the class is removed.

     The logo and the hamburger are lifted above it: both are siblings of
     the panel inside the header's stacking context, and the logo would
     otherwise be painted over by a panel that comes after it. */
  .nav-links {
    position: fixed;
    inset: 0;
    z-index: 0;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
    gap: 0;
    text-align: left;
    background: var(--bg);
    padding: calc(var(--header-h, 68px) + var(--space-40))
             var(--container-pad) var(--space-48);
    overflow-y: auto;
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--menu-dur) var(--ease-reveal),
                visibility 0s linear var(--menu-dur);
  }
  .site-header.is-menu-open .nav-links {
    opacity: 1;
    visibility: visible;
    transition: opacity var(--menu-dur) var(--ease-reveal);
  }
  .nav-logo,
  .nav-hamburger { position: relative; z-index: 1; }

  .nav-links li { width: 100%; }
  .nav-links a {
    display: block;
    font-size: var(--menu-fs);
    font-weight: var(--fw-extrabold);
    line-height: var(--lh-snug);
    letter-spacing: -0.01em;
    color: var(--white);
    padding-block: var(--space-6);
  }

  /* Each item rises as it fades. The delays live on the open state only,
     so removing the class drops them and the whole panel leaves together
     - a staggered exit reads as hesitation rather than intent. */
  .nav-links li {
    opacity: 0;
    transform: translateY(var(--menu-item-shift));
    transition: opacity var(--menu-item-dur) var(--ease-reveal),
                transform var(--menu-item-dur) var(--ease-reveal);
  }
  .site-header.is-menu-open .nav-links li {
    opacity: 1;
    transform: none;
  }
  .site-header.is-menu-open .nav-links li:nth-child(1) { transition-delay: calc(var(--menu-stagger) * 1); }
  .site-header.is-menu-open .nav-links li:nth-child(2) { transition-delay: calc(var(--menu-stagger) * 2); }
  .site-header.is-menu-open .nav-links li:nth-child(3) { transition-delay: calc(var(--menu-stagger) * 3); }
  .site-header.is-menu-open .nav-links li:nth-child(4) { transition-delay: calc(var(--menu-stagger) * 4); }

  /* A rule under the header row, as in the reference. Only while the menu
     is open - the header's own divider was removed on purpose. */
  .site-header.is-menu-open .nav-inner { border-bottom: 1px solid var(--border-strong); }

  .nav-cta { display: none; }
  .nav-hamburger { display: block; }
  .steps-inner { grid-template-columns: 1fr; gap: var(--space-14); }
  .step { justify-content: center; }
  .step-divider { display: none; }
  /* ── Card rails ─────────────────────────────────────────────────────
     Stacking four tall cards on a phone makes the section enormous and
     buries the last one. They become one horizontal rail instead.

     Full-bleed: negative margins pull the rail out to the screen edges so
     cards can run off the right, while the matching padding keeps the
     first card lined up with everything else in the container, and
     scroll-padding makes snapped cards land on that same line rather than
     flush against the glass.

     Snapping is mandatory, so the rail always comes to rest on a card
     rather than half way between two. */
  .features-grid,
  .why-grid,
  .testimonials-grid {
    display: flex;
    gap: var(--rail-gap);
    overflow-x: auto;
    overflow-y: hidden;
    margin-inline: calc(-1 * var(--container-pad));
    padding-inline: var(--container-pad);
    scroll-padding-inline: var(--container-pad);
    /* The cards are .reveal--up, so they sit --reveal-shift lower than their
       final position until the reveal plays - far enough to be cut off by
       the clipping this rail needs on the vertical axis. The padding gives
       them that room and the negative margin takes it back out of the
       layout, so the section's rhythm is unchanged. Tied to the same token
       as the reveal so the two cannot drift apart. */
    padding-block: var(--reveal-shift);
    margin-block: calc(-1 * var(--reveal-shift));
    scroll-snap-type: x mandatory;
    /* Only trap the horizontal axis, so a swipe that runs off the end of
       the rail cannot turn into a browser back-navigation, while a vertical
       swipe starting on the rail still scrolls the page.

       These rails deliberately carry no data-lenis-prevent. Lenis only
       preventDefaults a wheel with vertical delta, and with syncTouch off
       it never preventDefaults touch at all, so the attribute bought the
       rails nothing - but it opted them into Lenis's own
       `.lenis.lenis-smooth [data-lenis-prevent] { overscroll-behavior:
       contain }`, which out-specifies this rule (0,3,0 against 0,1,0) and
       contained the vertical axis too. Lenis adds lenis-smooth only while a
       smooth scroll is actually running, so the page stuck intermittently
       over the rails rather than failing outright. Re-adding the attribute
       here would bring the bug straight back. */
    overscroll-behavior-x: contain;
    overscroll-behavior-y: auto;
    /* The peeking next card is the affordance; a scrollbar across the
       middle of the section is not. */
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .features-grid::-webkit-scrollbar,
  .why-grid::-webkit-scrollbar,
  .testimonials-grid::-webkit-scrollbar { display: none; }

  .feature-card,
  .why-card,
  .testimonial-card {
    aspect-ratio: auto;
    flex: 0 0 var(--rail-card-w);
    scroll-snap-align: start;
  }
  .pricing-grid { grid-template-columns: 1fr; }
  .split-inner, .split-inner.reverse { grid-template-columns: 1fr; direction: ltr; }
  .split-image { order: -1; }
  /* Stacked and centred here, so there is no right edge to pin against. */
  .split-image--sm { justify-content: center; }
  .market-api-inner { grid-template-columns: 1fr; }
  .cta-banner-inner { flex-direction: column; text-align: center; padding: var(--space-48) var(--space-32); }
  .cta-right { flex-direction: row; align-items: center; flex-wrap: wrap; justify-content: center; gap: var(--space-10); }
  .cta-right .store-badge-img img { height: 38px; }
  .footer-top { flex-direction: column; gap: var(--space-24); }
  .footer-col ul { text-align: left; }
  .form-row { grid-template-columns: 1fr; }
  .contact-form-wrap { padding: var(--space-32) var(--space-24); }
}

/* A phone held sideways has almost no height to compose into, and forcing a
   full screen there squeezes the hero into a strip. Let it size to its own
   content and scroll like any other section. Placed after the mobile block
   so it overrides it rather than interrupting it. */
@media (max-width: 1024px) and (max-height: 480px) {
  .hero { min-height: 0; }
}
@media (max-width: 480px) {
  /* Phones keep the figure size tuned by hand; the 20% increase above is
     for tablet widths, where the illustration was adrift in white space. */
  :root { --hero-m-figure-max: min(352px, 37.4svh); }
  .footer-bottom { flex-direction: column; gap: var(--space-20); text-align: center; }
}
@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
  /* Killing transitions is not enough: reveals start at opacity:0, so they
     have to be resolved to their finished state or the page reads blank. */
  .page-veil { display: none; }
  .features-grid, .why-grid, .testimonials-grid { scroll-behavior: auto; }
  .reveal {
    opacity: 1 !important;
    transform: none !important;
    --sweep: var(--sweep-to) !important;
  }
}
