/* Foresight Mobile — page-specific layer over the shared styles.css.
   Sections follow the Sales Enablement deck (Figma JTK6dswRQU02oxsOFq6Qs0):
   The Promise hero, See/Build Your Game, Thousands of Local Courses
   (690:1005), Keeps Getting Better. Strategy-doc sections (tiers, sync,
   FAQ, closer) keep their own patterns. */

/* ---------- no wordmark on this page (user 2026-08-04) ----------
   The nav is [brand][links][actions] with space-between; the empty spacer
   mirrors the actions cluster so the links stay truly centered. */

.localnav__brand--spacer { flex: 1; }
.localnav__actions { flex: 1; justify-content: flex-end; }

/* ---------- hero: deck "The Promise" over a black stage with the
     landscape phone render (Figma 461:11384) ---------- */

/* wireframe layout (user 2026-08-06): copy left, device duo right; on
   phones the grid restacks head → image → lede → CTAs via display:contents.
   The beam/dot atmosphere is unchanged. */
.mhero {
  position: relative;
  /* full viewport minus the in-flow gnav strip above */
  height: calc(100svh - 64px);
  display: flex;
  align-items: center;
  padding: calc(var(--nav-h) + 32px) 0 32px;
  overflow: clip;
  background: var(--black);
}
.mhero__video {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.mhero__scrim { /* copy-side darken so the type reads over the footage */
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(90deg, rgba(0, 0, 0, .45), rgba(0, 0, 0, .18) 40%, transparent 62%);
  pointer-events: none;
}
/* the beam/halftone background retires behind the video (user 2026-08-19) */
.mhero::before, .mhero::after, .mhero__shader { display: none !important; }
.mhero__brand {
  display: block;
  height: clamp(20px, 2vw, 30px);
  width: auto;
}
.mhero__brand + .hero__title { margin-top: 1.4rem; }
.mhero--legacy-before {
  content: "";
  position: absolute;
  inset: -12%;
  background:
    linear-gradient(112deg, transparent 6%, rgba(96, 150, 255, .75) 16%, transparent 28%),
    linear-gradient(112deg, transparent 26%, rgba(23, 82, 224, .65) 38%, transparent 50%),
    linear-gradient(112deg, transparent 56%, rgba(23, 82, 224, .6) 70%, transparent 84%);
  filter: blur(clamp(18px, 2.6vw, 40px));
  -webkit-mask-image:
    radial-gradient(70% 75% at 0% 100%, #000 30%, transparent 78%),
    radial-gradient(60% 66% at 100% 0%, #000 25%, transparent 78%);
  mask-image:
    radial-gradient(70% 75% at 0% 100%, #000 30%, transparent 78%),
    radial-gradient(60% 66% at 100% 0%, #000 25%, transparent 78%);
  pointer-events: none;
}
/* Animated dot-field canvas (js/dot-field.js), user 2026-08-18 — replaces the
   static halftone below wherever WebGL exists. Explicit z-index: a positioned
   canvas would otherwise paint over ::before (the beams). Pools are the hero's
   own, bottom-left + top-right, unchanged. */
.mhero__shader {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
  -webkit-mask-image:
    radial-gradient(42% 48% at 6% 98%, #000, transparent 72%),
    radial-gradient(36% 42% at 94% 2%, #000, transparent 72%);
  mask-image:
    radial-gradient(42% 48% at 6% 98%, #000, transparent 72%),
    radial-gradient(36% 42% at 94% 2%, #000, transparent 72%);
}
/* only once a GL context actually exists — no WebGL keeps the static dots */
.mhero.has-dots::after { display: none; }

.mhero::after { /* static halftone — now the no-WebGL fallback */
  content: "";
  position: absolute;
  inset: 0;
  background-image: radial-gradient(rgba(140, 180, 255, .9) 1px, transparent 1.4px);
  background-size: 16px 16px;
  opacity: .45;
  -webkit-mask-image:
    radial-gradient(42% 48% at 6% 98%, #000, transparent 72%),
    radial-gradient(36% 42% at 94% 2%, #000, transparent 72%);
  mask-image:
    radial-gradient(42% 48% at 6% 98%, #000, transparent 72%),
    radial-gradient(36% 42% at 94% 2%, #000, transparent 72%);
  pointer-events: none;
}
.mhero__grid {
  position: relative;
  z-index: 1;
  /* left rail matches the See-your-game card's text edge: 3vw card inset
     + its clamp(36px, 4vw, 72px) padding (user 2026-08-18) */
  --rail: calc(3vw + clamp(36px, 4vw, 72px));
  width: min(1204px, calc(100vw - var(--rail) * 2));
  margin-inline: var(--rail) auto;
  display: grid;
  grid-template-columns: minmax(320px, .85fr) minmax(0, 1.15fr);
  align-items: center;
  gap: clamp(32px, 5vw, 96px);
}
.mhero__copy { text-align: left; position: relative; z-index: 2; } /* above the art */
.mhero .eyebrow { justify-content: flex-start; }
.mhero .hero__title {
  font-size: clamp(3.2rem, 6.2vw, 5.75rem);
  line-height: 1.04;
  margin-top: .5rem;
}
.mhero__copy .hero__lede {
  margin: clamp(20px, 2.5vh, 32px) 0 0;
  max-width: 44ch;
}
.mhero__ctas {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: clamp(28px, 4vh, 44px);
}
.mhero__ctas img { display: block; height: 52px; width: auto; }
.mhero__media { justify-self: end; z-index: 1; } /* behind the copy */
.mhero__media img {
  display: block;
  /* ~2/3 of the viewport (user 2026-08-18), right-anchored — bleeds into
     the grid's capped right margin, out to the viewport rail */
  width: min(50vw, 980px);
  max-width: none;
  height: auto;
  margin-left: auto;
  margin-right: calc(-1 * max(0px, 100vw - var(--rail) * 2 - 1204px));
}

/* mobile-only line break (hero H1: one line on desktop, two on phones) */
.br-mobile { display: none; }
@media (max-width: 700px) {
  .mhero {
    height: auto;
    min-height: calc(100svh - 64px);
    padding-top: calc(64px + clamp(28px, 4.5vh, 52px));
    padding-bottom: clamp(32px, 5vh, 56px);
  }
  .mhero__grid { grid-template-columns: 1fr; gap: 0; }
  /* the copy container dissolves so its children interleave with the image:
     eyebrow, headline, image, lede, CTAs (wireframe order) */
  .mhero__copy { display: contents; }
  .mhero .eyebrow { order: 1; justify-content: center; }
  .mhero__brand { order: 1; margin-inline: auto; height: 22px; }
  /* portrait crop: pull the window left so the video's baked tablet slides
     right, out from under the copy (user 2026-08-19) */
  .mhero__video { object-position: 28% 50%; }
  .mhero__scrim {
    background:
      linear-gradient(to top, rgba(0, 0, 0, .45), transparent 45%),
      rgba(0, 0, 0, .22);
  }
  .mhero .hero__title {
    order: 2;
    text-align: center;
    font-size: clamp(2.8rem, 12.5vw, 3.5rem);
  }
  .mhero__media { order: 3; margin: clamp(16px, 2.6vh, 28px) 0 clamp(10px, 1.6vh, 18px); }
  .mhero__media img { margin-inline: auto; width: min(100%, 440px); }
  .mhero__copy .hero__lede {
    order: 4;
    text-align: center;
    margin: 0 auto;
    font-size: .95rem;
  }
  .mhero__ctas { order: 5; justify-content: center; margin-top: clamp(18px, 2.8vh, 28px); }
  .mhero__ctas img { height: 48px; }
  /* no local section bar on phones — the gnav burger covers navigation */
  .localnav { display: none; }
}

/* two-tone headline: the payoff phrase runs at half strength */
.h2-dim { opacity: .5; }

/* phones: the full trio reads tiny, so crop in — the render runs ~210% wide,
   anchored to its left edge (left iPhone + part of the tablet visible), and
   the hero grows past the viewport to give it room (user 2026-08-05) */


/* ---------- deck sections: shared chassis ---------- */

.deck {
  position: relative;
  min-height: 100svh;
  display: flex;
  overflow: clip;
  padding: calc(var(--nav-h) + clamp(40px, 7vh, 84px)) 0 clamp(40px, 6vh, 72px);
  background: var(--black);
}
.deck__bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 0;
}
.deck__scrim {
  position: absolute;
  inset: 0;
  z-index: 0;
  background: linear-gradient(180deg, rgba(0, 0, 0, .42), rgba(0, 0, 0, .08) 34%, rgba(0, 0, 0, .12) 58%, rgba(0, 0, 0, .78) 100%);
}
.deck__frame {
  position: relative;
  z-index: 1;
  width: min(1840px, 94vw);
  margin-inline: auto;
  display: flex;
  flex-direction: column;
}
/* the giant two-tone display: lead word solid white, the rest 30% white.
   Deck scale: 287.68px at 1920 = 15vw, leading .8, tracking −5% */
.deck__display {
  font-size: clamp(4rem, 15vw, 18rem);
  font-weight: 700;
  line-height: .8;
  letter-spacing: -.05em;
  color: rgba(255, 255, 255, .3);
}
.deck__display b { color: #fff; font-weight: inherit; }
.deck__display--solid { color: #fff; }
.deck__display-indent { margin-left: .9em; }
.deck__title {
  font-size: clamp(2.4rem, 4.6vw, 4.25rem);
  font-weight: 700;
  line-height: 1.04;
  letter-spacing: -.02em;
  color: #fff;
}
.deck__title--caps {
  text-transform: uppercase;
  font-size: clamp(1.9rem, 3.4vw, 3.25rem);
  letter-spacing: -.01em;
}
.deck__lede {
  margin-top: clamp(18px, 3vh, 32px);
  font-size: clamp(1.35rem, 2vw, 2.25rem); /* deck ~40px at 1920 */
  font-weight: 584;
  line-height: 1.3;
  color: #fff;
  max-width: 24ch;
}
.deck__lede--quiet {
  margin-top: 12px;
  font-size: clamp(1.05rem, 1.4vw, 1.25rem);
  font-weight: 492;
  color: rgba(255, 255, 255, .64);
  max-width: none;
}
.deck__blocks {
  margin-top: auto;
  padding-top: clamp(28px, 5vh, 64px);
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(24px, 3.2vw, 64px);
}
.deck__block h3 {
  font-size: clamp(1.125rem, 1.67vw, 1.75rem); /* deck 32px at 1920 */
  font-weight: 584;
  line-height: 1.3;
  color: #fff;
}
.deck__block p {
  margin-top: 10px;
  font-size: clamp(.9375rem, 1.25vw, 1.1875rem); /* deck 24px at 1920 */
  line-height: 1.5;
  color: rgba(255, 255, 255, .6);
  max-width: 44ch;
}

/* ---------- deck: see your game — Shot Analysis view selector (the Apple
   feature-pill pattern: pill list left, device right; clicking a pill swaps
   the render in the device) ---------- */

/* tight: the card hugs its content instead of chasing the viewport; the
   band behind the card runs white with equal breathing room above and
   below (user 2026-08-05) */
.deck--see {
  min-height: 0;
  background: #fff;
  padding: clamp(48px, 7vh, 96px) 0;
}

/* One rounded card on the gradient plate: headline left, big transparent
   device center, text-only view list right (user 2026-08-05, per mock). */
.seesel-card {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  border-radius: 28px;
  overflow: clip;
  background:
    linear-gradient(rgba(4, 9, 16, .52), rgba(4, 9, 16, .52)),
    url("../assets/seesel-stage-bg.webp") center / cover no-repeat;
  padding: clamp(36px, 4vw, 72px);
}
.seesel-card .eyebrow { justify-content: flex-start; }
.seesel-card { position: relative; }
/* supporting line lives under the headline at lede scale (user 2026-08-20,
   per Figma — it was a small note pinned to the card's bottom-left) */
.seesel-card__lede {
  margin-top: clamp(14px, 2vh, 24px);
  max-width: 30ch;
  /* one step down from .lede (= the deck's quiet-lede ramp) */
  font-size: clamp(1.05rem, 1.4vw, 1.25rem);
}
/* halftone corners (DOTS.webp, user 2026-08-20): bottom-left as exported,
   flipped 180 for the top-right; the card's overflow: clip crops them */
.seesel-card__dots {
  position: absolute;
  width: clamp(220px, 22vw, 400px);
  height: auto;
  opacity: .5;
  pointer-events: none;
}
.seesel-card__dots--bl { left: 0; bottom: 0; }
.seesel-card__dots--tr { top: 0; right: 0; rotate: 180deg; }
@media (max-width: 700px) { .seesel-card__dots { display: none; } }
/* full .h2 scale (same as Build your game). The <br> sets the two lines;
   nowrap keeps "like never before" whole — it may run a few px into the
   column gap, which is open air */
.seesel-card__head .h2 { white-space: nowrap; }
@media (max-width: 1100px) { .seesel-card__head .h2 { white-space: normal; } }
@media (min-width: 1001px) and (max-width: 1100px) {
  .seesel-card__head .h2 br { display: none; }
}

.seesel {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(280px, 1fr) minmax(0, 1.6fr) auto;
  grid-template-rows: 1fr;
  align-items: center;
  gap: clamp(28px, 3.5vw, 64px);
}
/* per mock: eyebrow above the headline, and the text group holds the
   card's top-left while the list stays centered */
.seesel-card__head {
  grid-column: 1;
  grid-row: 1;
  align-self: start;
}
.seesel__listbg { display: none; }
.seesel__list {
  grid-column: 3;
  grid-row: 1;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 6px;
}
/* No rules, no descriptions — each row is just icon + label. The active
   row's icon circle carries the rotation timer: a stroke ring sweeps 0-100%
   around it over --seesel-dur, then the selection auto-advances. */
.seesel__item { position: relative; }

.seesel .seesel__ico { display: none !important; } /* labels only in this
   composition — outranks the icon block below */
.seesel__row {
  position: relative; /* anchors the tracing pill ring */
  display: block;
  width: auto;
  padding: clamp(6px, .9vh, 9px) clamp(16px, 1.4vw, 22px);
  border: 0;
  border-radius: 999px;
  background: none;
  color: rgba(255, 255, 255, .55);
  font: inherit;
  font-size: clamp(1.05rem, 1.25vw, 1.25rem);
  font-weight: 584;
  line-height: 1.25;
  text-align: right;
  cursor: pointer;
  transition: color .3s var(--ease), background .3s var(--ease);
}
.seesel__row:hover { color: rgba(255, 255, 255, .8); }
.seesel__item { align-self: flex-end; }
/* the timer ring traces the active pill's border (built in main.js) */
.seesel__pillring {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  color: rgba(255, 255, 255, .9);
}
.seesel__pillring rect {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-dasharray: 100;
  stroke-dashoffset: 100;
}
@media (prefers-reduced-motion: reduce) { .seesel__pillring { display: none; } }
/* active view: quiet pill (per mock) */
.seesel__item.is-open .seesel__row { background: rgba(255, 255, 255, .1); color: #fff; }
/* view icons from the app's Change View sheet (Figma asOBiqsGun6…):
   glyph on a 10%-white circle; the open row's circle fills solid white */
.seesel__ico {
  position: relative;
  flex: 0 0 auto;
  width: clamp(38px, 3.2vw, 48px);
  height: clamp(38px, 3.2vw, 48px);
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: rgba(255, 255, 255, .1);
  color: rgba(255, 255, 255, .75);
  transition: background .35s var(--ease), color .35s var(--ease);
}
.seesel__ico > svg:not(.seesel__ring) { width: 45%; height: 45%; display: block; }
.seesel__row:hover .seesel__ico { color: #fff; }
/* the selected view keeps the same 10%-white fill — the animating stroke on
   the circle's own border is the active indicator (user 2026-08-05) */
.seesel__item.is-open .seesel__ico { color: #fff; }
/* the timer ring: a pathLength-100 circle on the icon's edge, swept by
   stroke-dashoffset from 12 o'clock */
.seesel__ring {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  transform: rotate(-90deg);
  color: #fff;
  opacity: 0;
  pointer-events: none;
}
.seesel__ring circle {
  stroke-dasharray: 100;
  stroke-dashoffset: 100;
}
.seesel__item.is-open .seesel__ring { opacity: 1; }
/* the sweep itself runs on WAAPI (main.js): a CSS animation on the SVG child
   reports "running" with a frozen clock in Chromium, so its animationend
   never fires */
@media (prefers-reduced-motion: reduce) { .seesel__ring { display: none; } }

/* The stage floats openly on the card. The renders still carry a baked
   backdrop, so their edges are soft-masked to dissolve into the card
   gradient — approximating transparent exports until those land. The canvas
   is the shader swap between renders; the <img> is the no-WebGL fallback. */
.seesel__stage {
  grid-column: 2;
  grid-row: 1;
  align-self: stretch;
  position: relative;
  /* capped (user 2026-08-20, per Figma): the card was chasing tall viewports
     and the device drifted into a floaty center — this holds the ~2:1 card */
  min-height: clamp(420px, 52vh, 580px);
  /* the render stays fully inside the card — the mask's bottom fade
     dissolves the wrist instead of a hard crop (user 2026-08-06); the side
     extension keeps wide compositions from clipping at the column edges */
  margin-inline: calc(-1 * clamp(70px, 9vw, 200px));
  pointer-events: none;
}
.seesel__gl,
.seesel__shot {
  /* fade only the right edge (protects the view buttons) — the left runs
     clean now that the renders are transparent */
  -webkit-mask-image: linear-gradient(90deg, #000, #000 88%, transparent),
    linear-gradient(180deg, #000, #000 78%, transparent 98%);
  -webkit-mask-composite: destination-in;
  mask-image: linear-gradient(90deg, #000, #000 88%, transparent),
    linear-gradient(180deg, #000, #000 78%, transparent 98%);
  mask-composite: intersect;
}
.seesel__gl,
.seesel__shot {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}
.seesel__shot { object-fit: contain; transform: scale(1.62); }   /* in step with .seesel__dev */
.seesel__stage.is-gl .seesel__shot { display: none; }

/* ---- the clips play inside the hand device (Nat's PR #20 plate, ported
   into this card at every width): seeglass.js mounts .seesel__dev with the
   cut-out plate over a pooled pair of <video>s; the screen shows through
   the plate's hole, clipped by the app's own screen path. The WebGL still
   swap and its <img> fallback stand down while it is live. The wrapper
   carries the art's 1362x1530 aspect so the clip's percentages land. ---- */
.seesel__dev {
  position: absolute;
  left: 50%; top: 50%;
  height: 100%;
  aspect-ratio: 1362 / 1530;
  transform: translate(-50%, -50%);
  -webkit-mask-image: linear-gradient(180deg, #000, #000 78%, transparent 98%);
          mask-image: linear-gradient(180deg, #000, #000 78%, transparent 98%);
}
.seesel__stage.has-video .seesel__gl,
.seesel__stage.has-video .seesel__shot { display: none; }
.seesel__dev .sg__vid {
  position: absolute;
  left: 30.910%; top: 4.463%; width: 38.399%; height: 73.725%;
  object-fit: cover;
  opacity: 0;
  transition: opacity .45s var(--ease);
  background: #05070c;
  z-index: 0;
  -webkit-mask-image: url("../assets/video/screen-mask.svg");
          mask-image: url("../assets/video/screen-mask.svg");
  -webkit-mask-size: 100% 100%;
          mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
}
.seesel__dev .sg__vid.is-live { opacity: 1; }
.seesel__dev .sg__plate {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: contain;
  opacity: 1;
  z-index: 1;
  pointer-events: none;
}
/* same right-of-center anchor the still had (see the shot rule below) */
@media (min-width: 1001px) {
  /* 1.05 -> 1.26: the hand reads small against the card at this width
     (user 2026-08-25). The card's own bottom mask still dissolves the wrist,
     so the extra size crops nothing. */
  .seesel__dev { transform: translate(calc(-50% + 6%), calc(-50% + 9%)) scale(1.26); }
}

@media (max-width: 1000px) {
  /* plan-your-game: bottom 0 -> 50% darken so the stacked copy reads (user 2026-08-18) */
  .ctabs__scrim {
    background:
      linear-gradient(180deg, rgba(0, 0, 0, .26), rgba(0, 0, 0, 0) 34%),
      linear-gradient(to top, rgba(0, 0, 0, .5), rgba(0, 0, 0, 0) 50%);
  }
  .seesel { grid-template-columns: 1fr; grid-template-rows: auto auto auto; row-gap: 18px; }
  .seesel-card__head { grid-column: 1; grid-row: 1; align-self: start; text-align: center; }
  .seesel-card__head .eyebrow, .seesel-card .eyebrow { justify-content: center; }
  .seesel-card__lede { margin-inline: auto; text-align: center; }
  /* the render runs big and clips at the card's bottom edge (per mock) */
  .seesel__stage {
    grid-column: 1;
    grid-row: 3;
    margin-top: 0;
    /* full-bleed: the glove spans the card edge to edge and the wrist runs
       to the card bottom, where the frosted bar rides over it (redline) */
    margin-inline: calc(-1 * clamp(40px, 5vw, 96px));
    margin-bottom: calc(-1 * clamp(40px, 5vw, 96px));
    min-height: 114vw; /* ≈ the contain-fit height at full width — no letterbox */
  }
  /* frosted view bar floating over the render near the card bottom: 20%
     white on heavy backdrop blur, labels scroll inside, sides fade to show
     there's more. No timer visual on phones — the rotation just ticks. */
  .seesel__listbg {
    display: block;
    position: absolute;
    left: clamp(20px, 3.5vw, 44px);
    right: clamp(20px, 3.5vw, 44px);
    bottom: clamp(20px, 3.5vw, 44px);
    height: 54px;
    z-index: 1;
    border-radius: 999px;
    background: rgba(255, 255, 255, .2);
    -webkit-backdrop-filter: blur(28px);
    backdrop-filter: blur(28px);
    pointer-events: none;
  }
  .seesel__list {
    position: absolute;
    left: clamp(20px, 3.5vw, 44px);
    right: clamp(20px, 3.5vw, 44px);
    bottom: clamp(20px, 3.5vw, 44px);
    height: 54px;
    z-index: 2;
    width: auto;
    flex-direction: row;
    flex-wrap: nowrap;
    align-items: center;
    gap: 4px;
    margin: 0;
    padding: 8px 18px;
    border-radius: 999px;
    background: none;
    overflow-x: auto;
    scrollbar-width: none;
    /* only the text fades at the ends — the frosted plate behind stays solid */
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
    mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
  }
  .seesel__list::-webkit-scrollbar { display: none; }
  .seesel__item { align-self: auto; flex: 0 0 auto; }
  .seesel__row { white-space: nowrap; padding: 8px clamp(12px, 3.4vw, 18px); color: rgba(255, 255, 255, .28); }
  /* the timer on phones: white fills across the active WORD over the cycle —
     clipped to the label span so the sweep spends zero time in row padding */
  .seesel__item.is-open .seesel__row { background: none; }
  .seesel__item.is-open .seesel__label {
    display: inline-block;
    background-image: linear-gradient(90deg, #fff 50%, rgba(255, 255, 255, .28) 50%);
    background-size: 200% 100%;
    background-position: 94% 0; /* visible white from frame one */
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    animation: seesel-textfill 4.5s linear forwards;
  }
  /* 701-1000: the view bar hugs its labels and centers (user 2026-08-18);
   phones keep the full-width scrolling bar */
@media (min-width: 701px) and (max-width: 1000px) {
  .seesel__listbg { display: none; }
  .seesel__list {
    left: 50%;
    right: auto;
    translate: -50% 0;
    width: max-content;
    max-width: calc(100% - 2 * clamp(20px, 3.5vw, 44px));
    background: rgba(255, 255, 255, .2);
    -webkit-backdrop-filter: blur(28px);
    backdrop-filter: blur(28px);
    -webkit-mask-image: none;
    mask-image: none;
  }
}

@keyframes seesel-textfill {
    from { background-position: 94% 0; }
    to { background-position: 0 0; }
  }
  /* the fill waits for the section like the rotation timer does — otherwise
     it runs at page load and the first view sits fully white */
  .seesel:not(.in-view) .seesel__item.is-open .seesel__label { animation-play-state: paused; }
  @media (prefers-reduced-motion: reduce) {
    .seesel__item.is-open .seesel__label { animation: none; color: #fff; background-image: none; }
  }
  .seesel__pillring { display: none; }
}

/* Plan-your-game selector: labels stacked bottom-right, one live at a
   time — its description unfolds and the bar on its left fills as the
   rotation timer (user 2026-08-05) */
.ctabs__stack {
  position: absolute;
  left: clamp(24px, 3.4vw, 64px);
  bottom: clamp(24px, 4.5svh, 52px);
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: clamp(12px, 1.8vh, 20px);
  width: min(380px, 42%);
}
.ctabs__stack .ctabs__tab {
  display: grid;
  grid-template-columns: 3px 1fr;
  gap: 0 16px;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  color: rgba(255, 255, 255, .55);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: color .3s var(--ease);
}
.ctabs__stack .ctabs__tab.is-active { color: #fff; }
.ctabs__stack .ctabs__tab:hover { color: #fff; background: none; }
.ctabs__bar {
  position: relative;
  border-radius: 2px;
  background: rgba(255, 255, 255, .22);
  overflow: hidden;
}
.ctabs__bar i {
  position: absolute;
  left: 0;
  top: 0; /* the fill sweeps downward */
  width: 100%;
  height: 0%;
  background: #fff;
}
.ctabs__stack-label {
  display: block;
  font-size: clamp(1.1rem, 1.4vw, 1.4rem);
  font-weight: 584;
  line-height: 1.3;
}
/* the description slot trades between FIXED heights (0 ↔ 5.6em) with one
   shared duration, so the outgoing slot shrinks exactly as fast as the
   incoming grows — the stack glides instead of lurching, and inactive items
   stay compact with no copy (user 2026-08-05) */
.ctabs__stack-desc {
  display: block;
  height: 0;
  overflow: hidden;
  opacity: 0;
  margin-top: 0;
  transition: height .5s var(--ease), opacity .3s var(--ease), margin-top .5s var(--ease);
  font-size: clamp(.9rem, 1vw, 1rem);
  line-height: 1.5;
  color: rgba(255, 255, 255, .72);
}
.ctabs__stack { interpolate-size: allow-keywords; }
.ctabs__stack .ctabs__tab.is-active .ctabs__stack-desc {
  height: auto;
  opacity: 1;
  margin-top: 8px;
}

@media (max-width: 700px) {
  .ctabs__stack { width: calc(100% - 48px); }
  .ctabs__stack-desc { font-size: .95rem; }
  .ctabs__mark { font-size: clamp(1.7rem, 8vw, 2.2rem); }
}

/* the ctabs card floats on a white band here, and the mark carries a
   two-line two-tone headline at h2 scale */
.ctabs { background: #fff; }
.ctabs__mark {
  max-width: none;
  font-size: clamp(2.2rem, 3.6vw, 3.75rem);
}

/* desktop: the DEVICE (which sits high in the glove asset) is the focal
   center — nudged down and up a touch in size so spacing above and below
   the phone reads equal (user 2026-08-06 redline) */
@media (min-width: 1001px) {
  /* nudged right + slightly smaller (user 2026-08-20): the device reads as
     the card's right-of-center anchor, not a centered float */
  .seesel__gl,
  .seesel__shot { transform: translate(6%, 9%) scale(1.05); }
}

/* phones: the two showcase sections run edge to edge — no inset card, no
   band (user 2026-08-06) */
@media (max-width: 1000px) {
  .deck--see { padding: 0; }
  .deck--see .deck__frame { width: 100%; }
  .seesel-card { border-radius: 0; padding-top: clamp(64px, 9vh, 96px); }
  .ctabs { padding: 0; }
  .ctabs__frame { width: 100%; border-radius: 0; }
}

/* ---------- Training & Drills (same structure as the Foresight+ coaches
   section — centered head, three .why-card tiles from the shared sheet) ---------- */

/* white band, spacing matched to the card sections around it so the three
   beats read as one flow (user 2026-08-05) */
.train {
  padding: clamp(48px, 7vh, 96px) 0 clamp(64px, 9vh, 120px);
  background: #fff;
}
.train .why__grid { margin-top: clamp(32px, 5vh, 56px); }
.train .why-cap h3 { font-size: clamp(1.15rem, 1.4vw, 1.4rem); }
.train .why-cap p { color: rgba(10, 12, 16, .45); }
.train .container { text-align: center; }

/* stacked cards: twice the air between a caption and the next card (user
   2026-08-27). Below 1068 the grid drops to one column (styles.css
   BREAKPOINTS) and those 20px stop being a gutter — they become the vertical
   rhythm between the three beats, which is why they read as one block.
   row-gap only: above the breakpoint 20px is still the right gutter. */
@media (max-width: 1068px) {
  .train .why__grid { row-gap: 40px; }
}

/* ---------- deck: local courses (Figma 690:1005) ---------- */

/* tablets scaled to the deck's presence: each image runs ~150% of its
   column and bleeds past the section edges (the flattened black padding
   vanishes on the black bg); the cards ride ON the tablets */
.local__grid {
  margin-top: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0;
  align-items: start;
}
.local__item { margin: 0; }
.local__item img {
  display: block;
  width: 172%;
  max-width: none;
  height: auto;
}
.local__item:first-child img { margin-left: -40%; }
.local__item:last-child img { margin-left: -26%; }
/* the images are opaque black plates — the lede stacks above them */
.deck--local .deck__lede--quiet { position: relative; z-index: 2; }
.local__card {
  position: relative;
  z-index: 1;
  margin: -30% 0 0 6%;
  width: min(86%, 560px);
  background: rgba(22, 22, 24, .74);
  -webkit-backdrop-filter: blur(16px);
  backdrop-filter: blur(16px);
  border: 1px solid rgba(255, 255, 255, .1);
  border-radius: 18px;
  padding: clamp(18px, 2vw, 26px) clamp(18px, 2.2vw, 28px);
}
.local__card h3 {
  font-size: .9375rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: #fff;
}
.local__card p {
  margin-top: 8px;
  font-size: .9375rem;
  line-height: 1.55;
  color: rgba(255, 255, 255, .66);
}
.local__count {
  margin-top: clamp(28px, 4.5vh, 48px);
  font-size: clamp(1rem, 1.3vw, 1.1875rem);
  line-height: 1.5;
  color: rgba(255, 255, 255, .6);
  max-width: 58ch;
}
/* the 172% bleed only works with wide columns — below 1069px the tablets
   stack, near-full and uncropped */
@media (max-width: 1068px) {
  .local__grid { grid-template-columns: 1fr; row-gap: 8px; }
  .local__item img,
  .local__item:first-child img,
  .local__item:last-child img {
    width: 116%;
    margin: 0 0 0 -8%;
  }
  .local__card { margin: -14% auto 0; width: min(92%, 500px); }
}

/* ---------- tiers, light treatment (strategy copy) ---------- */

.mtiers { padding: clamp(96px, 14vh, 170px) 0; }
.mtiers__head { text-align: center; }
.mtiers__grid {
  margin-top: clamp(40px, 6vh, 64px);
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
}
@media (max-width: 820px) { .mtiers__grid { grid-template-columns: 1fr; max-width: 560px; margin-inline: auto; } }
.mtier {
  background: #fff;
  border: 1px solid rgba(6, 9, 15, .08);
  border-radius: 28px;
  padding: clamp(28px, 3vw, 40px);
}
.mtier h3 {
  font-size: 1.25rem;
  font-weight: 700;
  letter-spacing: -.01em;
}
.mtier__price {
  margin-top: 6px;
  font-size: 2.1rem;
  font-weight: 700;
  letter-spacing: -.02em;
}
.mtier__price span { font-size: 1rem; font-weight: 500; color: var(--ink-l2, rgba(6, 9, 15, .6)); }
.mtier ul {
  margin: 18px 0 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 10px;
  font-size: .9375rem;
  color: var(--ink-l2, rgba(6, 9, 15, .68));
}
.mtier ul li { padding-left: 22px; position: relative; }
.mtier ul li::before {
  content: "";
  position: absolute;
  left: 0;
  top: .42em;
  width: 12px;
  height: 7px;
  border-left: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
  opacity: .55;
}
.mtier__line { margin-top: 20px; font-size: .9375rem; font-weight: 600; }
.mtiers__cta { margin-top: clamp(32px, 5vh, 48px); text-align: center; }

/* ---------- sync band (strategy copy) ---------- */

/* more air above the membership eyebrow, and the tiers → ecosystem seam
   tightened from both sides (user 2026-08-05) */
.tiers { padding-top: clamp(110px, 16vh, 200px); padding-bottom: clamp(56px, 8vh, 100px); }
.sync { padding: clamp(48px, 7vh, 90px) 0 clamp(96px, 14vh, 170px); }
.sync__inner { text-align: center; }
.sync__inner .lede { margin-inline: auto; max-width: 640px; }
.sync .cta-row { justify-content: center; margin-top: clamp(24px, 4vh, 40px); }

/* ---------- closer badges ---------- */

.closer__badges {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 14px;
  margin-top: clamp(24px, 4vh, 36px);
}
.closer__badges img { display: block; height: 56px; width: auto; }

/* ---------- store badges: desktop hover (user 2026-08-19) ----------
   Covers BOTH placements — the hero pair and this footer pair. They were the
   only clickable things on the page giving no pointer feedback.

   The lift is the whole effect on purpose: Apple's and Google's badge
   guidelines forbid altering the artwork, so nothing here touches the SVG —
   the <a> moves and casts a shadow, the art is untouched. (Sheen and glow
   variants were benched in lab/shaders/badge-hover.html and dropped.)

   drop-shadow, not box-shadow: the art is a rounded rect whose radius scales
   with height (rx 17.92 on a 91 box ≈ 19.7%) and it renders at three sizes
   (52 / 56 / 48px), so a box-shadow would need a matching radius per case and
   would drift. drop-shadow traces the artwork's own alpha instead. It reads as
   depth over the hero's blue pool; over the footer's pure black the movement
   alone carries it, which is expected.

   The <a>s are flex items, so they are blockified and the transform applies
   without needing an explicit display. */
@media (hover: hover) {
  .mhero__ctas a,
  .closer__badges a {
    display: block;
    transition: transform .34s var(--ease), filter .34s var(--ease);
  }
  .mhero__ctas a:hover,
  .closer__badges a:hover {
    transform: translateY(-3px);
    /* brightness BEFORE drop-shadow, or the shadow gets lifted too — its
       rgba is explicit and should stay where it is.
       The badge plate is a flat #272727, so 1.15 walks it to ~#2d2d2d: the
       dark plate has the headroom, while the white lettering is already at
       255 and simply clips, so the logos barely move. This is a LUMINANCE
       shift, not a hue shift, which is the only colour change worth risking
       here — Apple's and Google's guidelines both call out recolouring the
       badge, and a tint that shifted the hue would read as exactly that.
       Measured off the real asset, plate #272727 (level 39) goes to:
         1.10 → #2a (+3)   1.15 → #2c (+5)   1.22 → #2f (+8)
         1.30 → #32 (+11)  1.40 → #36 (+15)
       1.22 is the pick: +5 was below the threshold to register in motion,
       +11 starts reading as a different swatch rather than a hover state. */
    filter: brightness(1.22) drop-shadow(0 12px 22px rgba(0, 0, 0, .55));
  }
}
@media (prefers-reduced-motion: reduce) {
  .mhero__ctas a,
  .closer__badges a { transition: none; }
}

/* ---------- narrow screens ---------- */

@media (max-width: 820px) {
  .deck { min-height: 0; }
  .deck__display { font-size: clamp(3.2rem, 17vw, 5rem); }
  .deck__display-indent { margin-left: .5em; }
  .deck--see .deck__art {
    position: relative;
    right: auto;
    top: auto;
    width: 74vw;
    margin: 28px auto 8px;
    transform: rotate(10deg);
  }
  .deck--see .deck__art::after { display: none; }
  .deck__blocks { grid-template-columns: 1fr; gap: 24px; padding-top: 32px; }
  .mhero__media img { transform: none; } /* no room to bleed on phones */
  .mhero__badges img { height: 48px; }
  .closer__badges img { height: 48px; }
}

/* phones: extra air above the Training & Drills eyebrow (kept at the end of
   the sheet — it must outrank the base .train rule above) */
@media (max-width: 1000px) {
  .train { padding-top: 120px; }
}

/* the selector's reveal fades only — its translateY made the transformed
   grid the containing block for the absolutely-positioned view bar, which
   snapped to a new anchor when the animation released (first-view glitch) */
.seesel.reveal { transform: none; }

/* ---------- practice cards: heavier title, regular body (user 2026-08-07 —
   both previously ran the caption's shared 600 weight) ---------- */

#practice .why-cap h3 { font-weight: 700; }
#practice .why-cap p { font-weight: 450; }

/* ---------- clear hero nav (user 2026-08-24) ----------
   The bar rides over the render instead of sitting on a black strip above it:
   transparent, with a light white hairline as the only separation. Hovering
   (or tabbing into it) brings the black back, so the dropdowns and pills read
   the way they do everywhere else.
   Out of flow, so the hero starts at the document top and absorbs the bar's
   64px as padding instead of being pushed down by it. */
.gnav {
  position: absolute;
  inset: 0 0 auto;
  background: transparent;
  border-bottom: 1px solid rgba(255, 255, 255, .18);
  transition: background .28s var(--ease), border-color .28s var(--ease);
}
.gnav:hover,
.gnav:focus-within {
  background: #000;
  border-bottom-color: transparent;
}

/* ---------- hero variant B (user 2026-08-19, per mock): static course photo
   fading to black, everything centered, device duo below the fold line.
   Keyframes instead of .reveal so the intro replays on every A→B flip
   (animations restart when the section leaves display:none). ---------- */

.mhero--b {
  display: block;
  height: auto;
  /* content-driven, no viewport stretch: tall monitors were leaving a slab
     of dead black between the devices and the next section (user 2026-08-20) */
  /* FIT THE FOLD (user 2026-08-21): the copy and the devices should both be
     on screen at any size. svh not vh — vh on mobile is the tallest possible
     viewport, so the devices would sit under the browser chrome on load. */
  /* the bar is out of flow over us now, so this is the whole fold */
  min-height: 100svh;
  display: flex;
  /* stretch, not the base's center: the inner column has to be as tall as the
     section for its own flex to hand the leftover height to the devices */
  align-items: stretch;
  /* clears the bar, plus the cushion under it — the copy sits lower in the
     frame now (user 2026-08-25), which also hands height to the devices */
  padding: calc(64px + clamp(28px, 7vh, 104px)) 0 0;
}
/* photo sized by its own aspect so the fade tracks the image, not the
   section height; the section floor past it is plain black */
/* The render carries the devices, so it is anchored to the BOTTOM of the
   section: the copy gets the sky at the top, the devices sit low, and the two
   never collide the way they did when the art was pinned to the top edge.
   (user 2026-08-21) */
.mhero-b__bg {
  /* fills the whole section (no hard edge above the sky) while staying
     bottom-anchored, so the devices hold their place at the foot and the sky
     is what gets cropped as the viewport changes shape */
  position: absolute;
  inset: 0;
  /* z-index 0 (not auto) so this box OWNS its children's stacking: the scrim
     below is z-index 2 to sit over both video plates, and with `auto` here it
     escaped into the section's context and painted over the copy — which is
     why the white headline read grey (user 2026-08-21) */
  z-index: 0;
  /* THE ENVIRONMENT still, not the full scene: mhero-scene.webp has the
     devices baked in, and this box is `cover` over the whole section while the
     foreground plate is inset and contained — so the still's tablet and phone
     landed ~40px off the video's and read as a ghost behind them
     (Cole, 2026-08-25). The devices' own still now rides on the FG <video> as
     its poster, where it obeys the same object-fit and cannot drift. */
  background: url("../assets/mhero-bg-poster.webp") center bottom / cover no-repeat;
  overflow: hidden;
}
/* both renders share the frame: same box, same fit, stacked */
.mhero-b__plate {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center bottom;
}
.mhero-b__plate--fg {
  z-index: 1;                 /* devices ride over the setting */
  /* no will-change here: it promoted the plate to its own compositing
     layer, and an alpha video on a hardware overlay is where browsers
     drop the alpha. The parallax is a handful of px and reads fine
     without the hint. (user 2026-08-25) */
  transform-origin: 50% 70%;  /* pivots low so the drift reads at the devices */
  /* The foreground clip is CROPPED to the devices (900x612 — the union of the
     alpha box across every frame), so it is all subject and no empty sky.
     That lets it be fitted on its own: contained in whatever the copy leaves,
     which is what keeps type and devices both on the fold at any size. It
     carries its own contact shadows, so it stays believable on the plate
     underneath even though the two are scaled independently. */
  /* height is stated, not `auto` + top/bottom: for an absolutely positioned
     REPLACED element (a <video>) `auto` resolves to the intrinsic aspect and
     ignores the bottom edge, which ran the devices straight past the fold. */
  /* the devices sit a touch higher off the fold (user 2026-08-25). There is
     ~40px of vertical slack in the box at this width — the render is
     width-bound once the sides are inset — so this lifts them without making
     them any smaller. */
  --fg-gap: clamp(20px, 5.5vh, 70px);
  /* The composition stays CENTRED (user 2026-08-25). What makes room for the
     corner card is the box being narrower than the frame — inset equally on
     both sides, so `contain` fits a slightly smaller render that still sits on
     the middle of the plate. Shifting the art sideways was the other way to
     free the corner, and it threw the whole hero off axis. */
  --fg-side: clamp(0px, 16vw, 230px);
  /* width is stated, like the height: for an absolutely positioned REPLACED
     element `auto` falls back to the intrinsic size and the opposite edge is
     dropped as over-constrained, so left+right+auto silently did nothing */
  top: var(--copy-h, 0px);
  left: var(--fg-side);
  width: calc(100% - 2 * var(--fg-side));
  height: calc(100% - var(--copy-h, 0px) - var(--fg-gap));
  object-fit: contain;
  object-position: center bottom;
}
/* Settled stills (js/main.js, user 2026-09-17): when a plate cannot play —
   iOS Low Power Mode refuses autoplay, or the clip never becomes ready — an
   <img> of the clip's final frame is inserted after it with the same plate
   classes, so it takes the same box and fit, and the <video> is hidden rather
   than removed so a later successful play() can swap straight back. Hidden,
   not display:none: a video that is not rendered stops loading in WebKit,
   which would defeat the swap-back. */
.mhero-b__plate.is-settled { visibility: hidden; }
.mhero-b__bg::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;   /* the scrim sits over both plates */
  /* a fraction of what it was: the old ramp existed to sit floating device
     plates on a photo, and at .88 it buried the devices baked into this one */
  background: linear-gradient(180deg, rgba(0,0,0,.55) 0%, rgba(0,0,0,.18) 26%,
                              rgba(0,0,0,0) 52%, rgba(0,0,0,.45) 88%, #000 100%);
}
.mhero-b__inner {
  position: relative;
  z-index: 1;
  /* auto basis, not flex:1 — this is the cross axis of a row container, so a
     grow factor would blow the column out to full width instead of its measure */
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  width: min(1100px, 92vw);
  margin-inline: auto;
}
/* Unchanged by the APP swap (user 2026-09-01): mobile.svg and app.svg both
   draw the mark at ~98% of the box height, so the same clamp keeps the mark
   exactly the size it has always been. Only the wordmark got shorter. */
.mhero--b .mhero__brand { height: clamp(17px, 1.55vw, 24px); }
.mhero--b .hero__title {
  /* tight to the wordmark — they read as one lockup (user 2026-08-24). The
     headline's own half-leading already sits above the caps, so this is the
     gap on top of that, not the whole optical gap. */
  margin-top: .35rem;
  /* same ramp as Premier's "Real data. Real golf." (Display 3xl/Bold) */
  /* min(vw, vh) is the point: on a short, wide window a vw-only clamp keeps
     the headline huge and pushes the devices off the fold */
  font-size: clamp(2rem, min(8.5vw, 9.2vh), 6rem);
  /* pure white, not the off-white ink token: this copy sits on a bright
     golden-hour sky and anything under #fff reads as grey (user 2026-08-21) */
  color: #fff;
}
.mhero--b .hero__lede {
  margin: clamp(10px, 1.8vh, 24px) auto 0;
  max-width: 46ch;
  font-size: clamp(15px, min(1.35vw, 2.1vh), 20px);
  color: #fff;
}
/* the sky behind the copy is the brightest part of the render, so the copy
   needs its own floor rather than relying on the plate scrim */
.mhero-b__inner { text-shadow: 0 1px 34px rgba(0, 0, 0, .45); }
.mhero--b .mhero__ctas { margin-top: clamp(20px, 3.2vh, 34px); }
/* the hero's store pills are the PHONE download (user 2026-09-01); above 700
   the corner card is, and two download prompts in one fold read as a mistake */
.mhero-b__stores { display: none; }
/* single-line store pills (user 2026-08-20, per mock): white capsules with
   the store glyphs pulled out of the badge SVGs (icon-*.svg) and the label
   set in the site face — replaces the two-line official badges */
.mhero--b .store-pill,
.closer__badges .store-pill {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  height: 48px;
  padding: 0 22px;
  border-radius: 999px;
  background: #fff;
  color: #0b0b0c;
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -.01em;
  text-decoration: none;
  transition: transform .25s var(--ease);
}
.mhero--b .store-pill:hover,
.closer__badges .store-pill:hover { transform: translateY(-1px); }
.mhero--b .store-pill img,
.closer__badges .store-pill img { display: block; height: 20px; width: auto; }

/* device duo: tablet anchors left, phone overlaps its right edge and hangs
   lower (align-end + translate; translate stays clear of the intro
   transform). The grid's padding-bottom absorbs the hang. */
/* holds open the space the render's devices occupy, so the copy never sits
   on top of the tablet */
.mhero-b__scenehold {
  width: 100%;
  /* takes whatever height the copy leaves, with a floor so the devices never
     get squeezed to a sliver on very short screens */
  flex: 1;
  min-height: min(42vw, 380px);
}
.mhero-b__tablet {
  grid-area: 1 / 1;
  justify-self: start;
  width: 86%;
  height: auto;
  --rise: 46px;
  animation: bpop .9s var(--ease) .3s both;
}
.mhero-b__phone {
  grid-area: 1 / 1;
  justify-self: end;
  align-self: end;
  width: 37%;
  height: auto;
  translate: 0 13%;
  --rise: 92px;
  animation: bpop 1.25s var(--ease) .5s both;
}
@keyframes bpop {
  from { opacity: 0; transform: translateY(var(--rise, 22px)); }
  to { opacity: 1; transform: none; }
}
.mhero-b__pop {
  --rise: 22px;
  animation: bpop .8s var(--ease) both;
  animation-delay: var(--bd, 0s);
}

/* the dark-page pass that came with this hero (user 2026-08-20, B chosen):
   See-your-game runs on a near-black plate with a frosted white-10% card,
   Training & Drills inverts to black/white ink, and the Plan-your-game card
   floats on black instead of the white band. */
.deck--see {
  background: linear-gradient(180deg, #000, #06070a);
}
.seesel-card { background: rgba(255, 255, 255, .1); }
.train { background: #000; color: var(--ink-d1); }
.train .eyebrow { color: #fff; }
.train .why-cap h3 { color: var(--ink-d1); }
.train .why-cap p { color: rgba(255, 255, 255, .55); }
.ctabs { background: #000; }

/* Hero fold, phones AND tablets (user 2026-08-31): ONE mechanism, two sets of
   inputs. Each range below defines the triple --hero-side / --hero-gap /
   --hero-art on the section; everything wired off the triple lives here once,
   so a tune cannot land in one range and silently miss the other. --hero-ar
   is the clip's 900:612 crop — the one place the ratio is stated. */
@media (max-width: 1068px) {
  .mhero--b {
    /* just the bar plus a hair: the copy centers itself in the leftover via
       the brand's auto margin, so a fat fixed cushion would double up */
    padding-top: calc(64px + 10px);
    --hero-ar: 1.4706;
  }
  /* auto top pairs with the auto top further down the column (the getcard's
     on phones, the scenehold's on tablets): the two split the free height
     equally, which centers the copy block */
  .mhero--b .mhero__brand { margin-top: auto; }
  /* the floor under the copy→card gap: the auto margins resolve to 0 the
     moment the column overflows (long localized copy, text zoom), and without
     a fixed term the card would butt flush against the lede */
  .mhero--b .hero__lede { margin-bottom: 14px; }
  /* fixed basis, not flex:1 — while the spacer grows it eats every spare px,
     so the auto margins that seat the copy and the card would collapse */
  .mhero-b__scenehold { flex: 0 0 auto; }
  .mhero-b__plate--fg {
    --fg-side: var(--hero-side);
    --fg-gap: var(--hero-gap);
  }
}

@media (max-width: 700px) {
  /* no min-height reset and no height on the plate box: the render is a single
     bottom-anchored layer now, and either one cropped the devices off the
     bottom of it. The fold fit is the same here as on desktop. */
  /* the side inset stops the devices growing past ~480px on wide phones
     (500-700px was cramming the copy against the card); --hero-art is the
     clip's rendered height, width-bound at (100vw - 2*inset). Declared on the
     section because the scenehold cannot read a sibling plate's props. */
  .mhero--b {
    --hero-side: max(clamp(24px, 8.5vw, 40px), (100vw - 480px) / 2);
    --hero-gap: clamp(28px, 6vh, 56px);
    --hero-art: calc((100vw - 2 * var(--hero-side)) / var(--hero-ar));
  }
  /* the shared phone rules reorder hero children (image-between-copy for A);
     B keeps its source order: logo, title, badges, devices */
  .mhero--b .mhero__brand,
  .mhero--b .hero__title { order: 0; }
  .mhero--b .hero__title { font-size: clamp(2.1rem, min(10vw, 7.4vh), 3.4rem); }
  .mhero--b .hero__lede { font-size: clamp(14px, 3.7vw, 17px); }
  /* sized to the ART, not the viewport: 86vw reserved far more than the
     render used on 500-700px phones and left a dead band under the card. The
     svh term pins the card's bottom edge near 54svh AND is the short-phone
     fit: it scales with the viewport, which is what let the old
     min(78vw, 460px) max-height override retire (user 2026-08-31) */
  .mhero-b__scenehold {
    min-height: max(calc(var(--hero-art) + var(--hero-gap)), 45svh);
  }
}

/* ==========================================================================
   Practice on 42000+ courses — Figma 12925:8986 / 12925:8987 (chris 2026-08-21)

   The comp is a 1725x1703 frame with everything absolutely placed and
   OVERLAPPING: the device art is 101.28% of frame width (bleeding past both
   edges), centred at 52.35% height, and the caption lands at 75.87% — on the
   device asset's own transparent lower margin, not below it. Flow layout
   spreads that apart and loses the comp, so desktop keeps absolute placement
   and only phones stack.

   ORDER MATTERS: the shared rules come first, the desktop media block last —
   a media query adds no specificity, so a later plain rule would beat it.
   ========================================================================== */
/* ONE composition, two layouts. Everything below is a fraction of --frame, so
   the head/tabs/plate/copy keep the same relationship to each other at every
   width; --frame stops at 1725 (the comp width) so an ultrawide window centres
   instead of inflating. The plate art carries 22.22% dead transparent margin
   above the tablet and 14.44% below (2400x1800 alpha bbox), so the negative
   margins below are DEAD-BAND + OVERLAP: the visible tablet always tucks 5% of
   its own height behind the tabs and 8% of its own height under the sub-copy,
   phones included (chris 2026-08-24). */
.ctabs {
  --frame: min(100vw, 1725px);
  position: relative;
  isolation: isolate;
  background: #000;
  overflow: clip;
  text-align: center;
  /* the vh terms are what let a short landscape window fit: on a tall screen
     the frame term wins and the band keeps the comp's generous margins */
  /* The padding is the runway the scrim fades over, so it is a variable the
     gradient can read: the clear window below is pinned to exactly these two
     values, which makes the content block — head, tabs, plate, copy — land on
     untouched environment at EVERY viewport, with the fades living entirely in
     the margins. Changing the padding moves the fades with it. */
  /* The band hugs the content (chris 2026-08-25): the section effectively
     starts where the headline starts and ends where the sub-copy ends, with
     only a normal section margin either side. The gradient still runs its
     traced curve over exactly this runway, so what fade remains lives entirely
     in these margins and the text stays on clear environment. */
  /* Doubled 2026-08-27 (user: "muy ajustado" — too tight): all three clamp
     terms x2 — floor 56->112, preferred .055->.11 of the frame and 9vh->18vh,
     ceiling 100->200 — so the air grows evenly at every width instead of
     flattening against the ceiling on desktop. The scrim follows on its own:
     its stops are written as fractions of these two variables. */
  --padT: clamp(112px, min(calc(var(--frame) * .11), 18vh), 200px);
  --padB: clamp(112px, min(calc(var(--frame) * .11), 18vh), 200px);
  /* The fade is deliberately LONGER than the padding it lives in. Tying the
     two together meant the only way to soften the ramp was to grow the band,
     and every trim made it harsh again. Running the gradient to 1.5x the
     padding gives it half as much slope for the same section height; the curve
     is shaped so it is already down to ~6% alpha by the time it reaches the
     headline, which is a 3-luminance veil — below the threshold where the copy
     reads as sitting "on the fade". (chris 2026-08-24) */
  --fadeT: calc(var(--padT) * .55);
  --fadeB: calc(var(--padB) * .55);
  padding: var(--padT) 0 var(--padB);
}
.ctabs__env {
  position: absolute;
  z-index: 0;
  left: 50%;
  top: 50%;
  /* scaled past the section on both axes so the blurred course reads as a big,
     open landscape rather than a tight crop (chris 2026-08-24) */
  width: 372%;
  height: 418%;
  transform: translate(-50%, -50%);
  pointer-events: none;
}
.ctabs__envimg {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 100%;
  /* retuned in the backdrop lab (user 2026-09-01, ctabs-bg.html): more blur
     so the aerial reads as pure depth, brightness back up a touch now that
     the dimmed clouds carry less white over it, and a whisper of contrast so
     the fairways still separate under the heavier blur. */
  filter: blur(6px) brightness(.72) contrast(1.04);
}
.ctabs__scrim {
  position: absolute;
  inset: 0;
  z-index: 0;                       /* over the plate, under head/device/cap */
  pointer-events: none;
  /* Long eased ramp rather than the comp's five stops. Five stops jumped
     alpha 1 -> .2 inside ~230px, so the green appeared to switch on: a hard
     line just inside an otherwise black seam. These stops trace an ease-in-out
     over 38% at the top and 30% at the bottom, leaving the middle 38-70% fully
     clear so the green band owns more of the frame (chris 2026-08-24) — the
     plate still emerges from and returns to the black of .train above and
     .compat below with no perceptible edge. The device sits above this layer,
     so the ramp costs it nothing. */
  /* the traced near-linear curve, renormalised to run over exactly --padT /
     --padB so the ramp finishes precisely where the content begins. Same shape
     as the approved fade — only its anchor changed. */
  background: linear-gradient(180deg,
    rgb(0, 0, 0)           0,
    rgba(0, 0, 0, .92)     calc(var(--padT) * 0.087),
    rgba(0, 0, 0, .82)     calc(var(--padT) * 0.174),
    rgba(0, 0, 0, .71)     calc(var(--padT) * 0.264),
    rgba(0, 0, 0, .62)     calc(var(--padT) * 0.351),
    rgba(0, 0, 0, .52)     calc(var(--padT) * 0.438),
    rgba(0, 0, 0, .41)     calc(var(--padT) * 0.526),
    rgba(0, 0, 0, .3)      calc(var(--padT) * 0.615),
    rgba(0, 0, 0, .21)     calc(var(--padT) * 0.703),
    rgba(0, 0, 0, .14)     calc(var(--padT) * 0.79),
    rgba(0, 0, 0, .08)     calc(var(--padT) * 0.846),
    rgba(0, 0, 0, .03)     calc(var(--padT) * 0.923),
    rgba(0, 0, 0, 0)       calc(var(--padT) * 1),
    rgba(0, 0, 0, 0)       calc(100% - var(--padB) * 1),
    rgba(0, 0, 0, .03)     calc(100% - var(--padB) * 0.923),
    rgba(0, 0, 0, .08)     calc(100% - var(--padB) * 0.846),
    rgba(0, 0, 0, .14)     calc(100% - var(--padB) * 0.79),
    rgba(0, 0, 0, .21)     calc(100% - var(--padB) * 0.703),
    rgba(0, 0, 0, .3)      calc(100% - var(--padB) * 0.615),
    rgba(0, 0, 0, .41)     calc(100% - var(--padB) * 0.526),
    rgba(0, 0, 0, .52)     calc(100% - var(--padB) * 0.438),
    rgba(0, 0, 0, .62)     calc(100% - var(--padB) * 0.351),
    rgba(0, 0, 0, .71)     calc(100% - var(--padB) * 0.264),
    rgba(0, 0, 0, .82)     calc(100% - var(--padB) * 0.174),
    rgba(0, 0, 0, .92)     calc(100% - var(--padB) * 0.087),
    rgb(0, 0, 0)           100%);
}
/* the cloud field (js/clouds.js): same box as the aerial plate, sitting over
   it and under the scrim, so the black ramps at the section's edges carry the
   clouds out with them. z-index 0 like its siblings — DOM order does the
   stacking inside .ctabs, which isolates. */
.ctabs__clouds {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
  /* backdrop lab (user 2026-09-01): the field dimmed on the element, not in
     the shader — brightness scales the rendered RGB and leaves alpha alone,
     so the clouds keep their coverage but stop reading as white glare */
  filter: brightness(.6);
}
.ctabs--noclouds .ctabs__clouds { display: none; }

.ctabs__head { position: relative; z-index: 3; text-align: center; }
.ctabs__mark {
  position: static;                 /* the shared sheet pins this top-left */
  top: auto; left: auto;
  /* an earlier .ctabs__mark rule in this file sets 3.75rem for the old pinned
     layout and would win on source order — restate .h2 in full (ramp, leading
     and tracking) so the head is byte-identical to "Build your game with
     training drills" at every width, phones included */
  font-size: clamp(2.4rem, 5.4vw, 4.5rem);
  line-height: 1.25;
  max-width: none;
  margin: 0 auto;
  text-align: center;
  font-weight: 700;
  letter-spacing: -.02em;
  color: #fff;
}
/* the same segmented pill as Foresight+ -> Course Pass on the plus page
   (.tiers__toggle in foresight-plus/css/plus.css): fixed 4px padding, 15px/584
   labels, .08 glass, and the travelling white thumb fed by --tx/--tw. Fixed
   size at EVERY width — the earlier vw ramp made the control grow and shrink
   against a control that is a constant on every other page. */
/* ---- headline intro: settle, calm take (lab ctabs-num v2, 2026-09-01) ----
   One class drives the whole choreography: .is-live lands on the mark when
   the band scrolls in, and per-piece transition-delays do the sequencing —
   "Practice on" rises (0s), then EVERYTHING else enters on the same .26s
   beat: the digits start their five-step roll, the comma fades in, the +
   springs up and "courses" rises. The plus and the word land quickly
   (~.76s), so "+ courses" reads whole while the number is still rolling,
   and the digits close the reading when the last column lands (~1.65s).
   JS builds the scaffolding from the plain markup, so with no JS the
   headline just stands. Strips are [blank, d-4 .. d] cells one line-box
   tall; the roll is one translateY, no per-frame JS and no smear. */
.ctabs__mline { display: block; }
.ctabs__mline--mask { overflow: hidden; }
.ctabs__mline-in { display: block; transform: translateY(112%); }
.ctabs__mark.is-live .ctabs__mline-in {
  transform: none;
  transition: transform .62s cubic-bezier(.22, .88, .3, 1);
}
.ctabs__num {
  display: inline-flex;
  align-items: flex-end;
  font-variant-numeric: tabular-nums;
}
.ctabs__reel {
  display: inline-block;
  overflow: hidden;
  height: 1.25em;                   /* == .ctabs__mark line-height */
  vertical-align: bottom;
}
.ctabs__strip {
  display: block;
  transform: translateY(0);
}
.ctabs__mark.is-live .ctabs__strip {
  transform: translateY(calc(var(--n) * -1.25em));
  transition: transform var(--dur) cubic-bezier(.22, .88, .3, 1) .26s;
}
.ctabs__g {
  display: block;
  height: 1.25em;
  line-height: 1.25em;
}
/* the comma is JS-built like the reels, so it can rest hidden without
   breaking the no-JS fallback (plain text keeps its own comma). It was the
   one lit glyph in the pre-roll state (user 2026-09-01) — now it fades in on
   the same beat the digits start rolling. */
.ctabs__sep { display: inline-block; opacity: 0; transform: translateY(.22em); }
.ctabs__mark.is-live .ctabs__sep {
  opacity: 1;
  transform: none;
  transition: opacity .32s ease-out .26s,
              transform .42s cubic-bezier(.22, .88, .3, 1) .26s;
}
/* the + is a two-cell reel with a soft edge fade so it flows in rather than
   pops; its 1.25em box equals the line box, so bottom-align keeps the glyph
   on the baseline (overflow:hidden would otherwise park it high). The
   linear() is a hand-sampled damped spring with ~3% overshoot; the bezier
   line before it is the fallback for engines without linear(). */
.ctabs__plusgrp { white-space: pre; }
.ctabs__preel {
  display: inline-block;
  overflow: hidden;
  height: 1.25em;
  vertical-align: bottom;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 16%, #000 84%, transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0, #000 16%, #000 84%, transparent 100%);
}
.ctabs__preel-strip { display: block; }
.ctabs__mark.is-live .ctabs__preel-strip {
  transform: translateY(-1.25em);
  transition: transform .5s cubic-bezier(.19, .8, .26, 1) .26s;
  transition-timing-function: linear(0, .213 8%, .543 16%, .789 24%, .93 32%,
    1.005 40%, 1.028 48%, 1.028 56%, 1.017 66%, 1.007 76%, 1.002 86%, 1);
}
.ctabs__mword { display: inline-block; overflow: hidden; vertical-align: bottom; }
.ctabs__mword-in { display: inline-block; transform: translateY(118%); }
.ctabs__mark.is-live .ctabs__mword-in {
  transform: none;
  transition: transform .5s cubic-bezier(.22, .88, .3, 1) .26s;
}
@media (prefers-reduced-motion: reduce) {
  .ctabs__mark.is-live .ctabs__mline-in,
  .ctabs__mark.is-live .ctabs__strip,
  .ctabs__mark.is-live .ctabs__preel-strip,
  .ctabs__mark.is-live .ctabs__sep,
  .ctabs__mark.is-live .ctabs__mword-in { transition: none; }
}
.ctabs__toggle {
  position: relative;
  z-index: 3;
  display: flex;
  gap: 4px;
  width: max-content;
  margin: clamp(18px, calc(var(--frame) * .014), 26px) auto 0;
  padding: 4px;
  border-radius: 999px;
  /* white alpha, not a dark plate (chris 2026-08-24, matching the comp): the
     control stays a light glass wash over the course. The backdrop blur is what
     keeps the labels legible where the tablet passes behind it. */
  background: rgba(255, 255, 255, .12);
  border: 1px solid rgba(255, 255, 255, .08);
  -webkit-backdrop-filter: blur(38.5px);
          backdrop-filter: blur(38.5px);
}
.ctabs__thumb {
  position: absolute;
  top: 4px;
  left: 4px;
  height: calc(100% - 8px);
  width: var(--tw, 0);
  border-radius: 999px;
  background: #fff;
  transform: translateX(var(--tx, 0));
  pointer-events: none;
  opacity: 0;
  transition: transform .46s var(--ease), width .46s var(--ease);
}
.ctabs__toggle.is-measured .ctabs__thumb { opacity: 1; }
.ctabs__toggle:not(.is-measured) .ctabs__thumb { transition: none; }
.ctabs__seg {
  position: relative;
  z-index: 1;
  padding: 10px 24px;
  border: 0;
  border-radius: 999px;
  background: none;
  font: inherit;
  font-size: 15px;
  font-weight: 584;
  color: rgba(255, 255, 255, .75);
  white-space: nowrap;
  cursor: pointer;
  transition: color .25s var(--ease);
}
.ctabs__seg:hover { color: #fff; }
.ctabs__seg.is-active { color: #0a0a0b; }
.ctabs__toggle:not(.is-measured) .ctabs__seg.is-active { background: #fff; }
/* stacking, explicitly: plate at 1, head/tabs/copy at 3. */
.ctabs__stage {
  position: relative;
  z-index: 1;
  margin-inline: auto;
  line-height: 0;
  /* the shadow lives on the PARENT so it follows the child's mask: CSS applies
     filter before mask, so a drop-shadow on the video itself would trace its
     rectangle instead of the chassis */
  filter: drop-shadow(0 26px 44px rgba(0, 0, 0, .5));
}
/* The white around the tablet is removed with a real alpha matte, not a
   border-radius and not a blend mode. A radius can only ever approximate the
   source's squircle corner — too small leaves a white crescent, too large eats
   the chassis — which is what read as sloppy. multiply/darken would drop the
   white correctly but they also composite the TABLET against the green plate
   behind it, so the screen loses its blacks and the white UI text disappears.
   ctabs-tee-mask.png is generated from the clip's own last frame: background
   pixels reachable from the border by flood fill become alpha 0 (so a matching
   colour INSIDE the UI is never punched out — the sniffed background is white
   on some mocks and black on others). The silhouette is then eroded 3.2px and
   feathered 1.4px. The erosion matters: the mock draws a 1px specular rim on
   the chassis that h.264 breaks into dashes, and the source's own edge pixels
   are premultiplied against the backdrop, so cutting at the true silhouette
   left a dark fringe AND a stippled highlight — measured, the green went
   42 -> 35 -> 69 across three pixels. Cutting just inside both makes it
   monotonic (42 -> 38 -> 63) and the edge reads smooth.
   The chassis never moves in the clip, so one static matte covers every frame.
   Rebuild with /root/dispbuild/rebuild.sh <source.mp4> (session-local). */
.ctabs__vid {
  display: block;
  width: 100%;
  height: auto;
}
.ctabs__vid[hidden] { display: none; }
.ctabs__vid {
  -webkit-mask-image: url("../assets/ctabs-tee-mask.png");
          mask-image: url("../assets/ctabs-tee-mask.png");
  -webkit-mask-size: 100% 100%;
          mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
}
/* Rise + settle as the band comes into view, matching the copy's reveal.
   Built on the INDEPENDENT translate/scale properties, not `transform`:
   .reveal animates transform, and the stage already spends transform on its
   own translateY(-50%) centring — sharing the property would cancel it. */
.ctabs__stage {
  translate: 0 34px;
  scale: .955;
  opacity: 0;
  transition: translate 1s var(--ease), scale 1s var(--ease), opacity .8s var(--ease);
}
.ctabs__stage.is-in { translate: 0 0; scale: 1; opacity: 1; }
/* NOTE: the plate deliberately has NO tab-swap animation (chris 2026-08-24).
   It was dipped and re-expanded on every switch for a while; the tablet
   holding still while the clip restarts under it reads cleaner. The sub-copy
   still carries the swap. */
@media (prefers-reduced-motion: reduce) {
  .ctabs__stage { transition: none; translate: none; scale: 1; opacity: 1; }
}
/* the sub-copy is a .lede in every respect but its centring — same ramp,
   leading, tracking, weight and dim-on-dark colour, so it sits on the site
   scale instead of the comp's bespoke 24/32 584 */
.ctabs__cap {
  position: relative;               /* z-index is inert on a static box, and the
                                       stage above it is z:1 — this is what puts
                                       the copy in front of the plate on phones */
  z-index: 3;
  font-weight: 400;
  line-height: 1.42;
  letter-spacing: -.013em;
  text-align: center;
  color: var(--ink-d2, #a8abb4);
}
/* Both captions share ONE grid cell, so the box is always as tall as the
   TALLER of the two at every width (user 2026-08-27). Each tab's copy runs a
   different length and wraps to 2 or 3 lines depending on the viewport, so
   switching tabs used to reflow the section — the JS already deferred the
   reflow by 220ms with a timer, but the jump was still there. Reserving the
   max kills it at the root, with no per-breakpoint min-height to re-tune.
   The font-size lives HERE and the <p>s inherit it: the breakpoints'
   `max-width: 42ch` is declared on this box, and ch is measured against ITS
   font-size. */
.ctabs__caps {
  position: relative;
  z-index: 3;
  display: grid;
  font-size: clamp(1.12rem, 1.9vw, 1.4rem);
}
.ctabs__caps > .ctabs__cap { grid-area: 1 / 1; }

/* Contrast veil behind the copy (user 2026-08-27). The cloud field
   (clouds.js, #dbe9ff up to alpha .72) lifts the background luminance right
   under this line: measured, at just 25% density the text drops from 6.35:1
   to 4.50:1, and in dense fog to 1.58:1 — well below AA.
   Lowering the clouds' alpha does not solve it (not even .30 clears dense
   fog) and it changes the whole band, so the veil goes local and ON TOP of
   them. At .66 the worst case is 4.9:1 and the best 7:1.
   Full-bleed across, fading only vertically: that is the same language as the
   section's top and bottom scrims, so it does not read as a smudge — a radial
   would be cut off against .ctabs' edges (overflow: clip). */
.ctabs__caps::before {
  content: "";
  position: absolute;
  z-index: -1;                      /* behind the text, in front of the clouds */
  left: 50%;
  top: 50%;
  width: 100vw;
  height: 380%;                     /* height is set by the TALLER caption */
  transform: translate(-50%, -50%);
  pointer-events: none;
  background: linear-gradient(to bottom,
    rgba(0, 0, 0, 0)    0%,
    rgba(0, 0, 0, .22) 18%,
    rgba(0, 0, 0, .55) 30%,
    rgba(0, 0, 0, .66) 38%,
    rgba(0, 0, 0, .66) 62%,
    rgba(0, 0, 0, .55) 70%,
    rgba(0, 0, 0, .22) 82%,
    rgba(0, 0, 0, 0)  100%);
}
/* visibility, not display: the inactive line keeps its box (and therefore the
   row's height) but still leaves the accessibility tree */
.ctabs__cap[hidden] { display: block; visibility: hidden; }
/* tab swap: the line lifts out and the next one rises in. Built on the
   independent translate property, not transform — .reveal animates transform
   and would cancel it. The out leg is 220ms (matched to the JS timer that
   un-flows the old line), the in leg 520ms so it settles.
   The two legs get different curves rather than the site ease. var(--ease) is
   .22,.61,.36,1 — it leaves the start line fast, which is what read as a jolt
   on the way in. The entry now uses a soft-start/soft-stop curve so the line
   gathers speed instead of snapping, and the exit uses an ease-IN so the old
   line accelerates away rather than dawdling.
   The line also EXPANDS as it fades in — scale .972 -> 1 — so the swap reads as
   the copy opening up rather than just sliding. Built on the independent scale
   property for the same reason translate is: transform belongs to .reveal.
   transform-origin sits at the top centre so the block grows downward from a
   fixed first baseline instead of drifting up into the tablet.
   (chris 2026-08-24) */
.ctabs__cap {
  transform-origin: 50% 0;
  transition: opacity .52s cubic-bezier(.33, 0, .2, 1),
              translate .52s cubic-bezier(.33, 0, .2, 1),
              scale .52s cubic-bezier(.33, 0, .2, 1);
}
.ctabs__cap.is-leaving {
  opacity: 0;
  translate: 0 -9px;
  scale: 1.012;                     /* keeps opening on the way out, so the two
                                       lines never look like one sliding block */
  transition: opacity .22s cubic-bezier(.4, 0, .85, .5),
              translate .22s cubic-bezier(.4, 0, .85, .5),
              scale .22s cubic-bezier(.4, 0, .85, .5);
}
.ctabs__cap.is-entering {
  opacity: 0;
  translate: 0 12px;
  scale: .972;
  transition: none;
}
@media (prefers-reduced-motion: reduce) {
  .ctabs__cap,
  .ctabs__cap.is-leaving,
  .ctabs__cap.is-entering {
    transition: none; translate: none; scale: 1; opacity: 1;
  }
}

/* ---------- corner card (user 2026-08-25) ----------
   A get-the-app prompt in the hero's bottom-right corner, in the site's own
   card language rather than the reference's: our --r-card radius, the hairline
   the dark surfaces use, and the store glyphs already pulled out of the badge
   SVGs (they are currentColor, so the buttons tint them). */
.getcard {
  position: absolute;
  z-index: 4;                       /* over the render, over the copy column */
  /* tight to the corner and compact on purpose: the render's phone reaches
     into this side of the frame, and every px the card gives back is a px of
     the phone's readouts it would otherwise cover.
     The card lives in the copy column (so it can flow with the copy on
     phones), but the corner it wants is the SECTION's — hence the negative
     right: it backs out of the column's own inset to reach the frame edge. */
  right: calc(clamp(16px, 2.6vw, 40px) - (100vw - min(1100px, 92vw)) / 2);
  bottom: clamp(18px, 3vh, 40px);
  /* one word and two buttons: let it be as small as its content, so the
     render keeps as much of the frame as possible */
  width: fit-content;
  padding: 18px 20px 20px;
  border: 1px solid rgba(255, 255, 255, .12);
  border-radius: var(--r-card, 28px);
  /* glass, not a flat slab: the aerial and the cloud field both move behind
     this, and a solid panel would read as a hole punched in them */
  background: rgba(9, 11, 10, .58);
  -webkit-backdrop-filter: saturate(150%) blur(18px);
  backdrop-filter: saturate(150%) blur(18px);
}
.getcard__copy {
  margin: 0 0 14px;
  font-size: clamp(14px, 1.05vw, 16px);
  line-height: 1.2;
  text-align: center;
  color: var(--ink-d1, #f5f5f6);
  opacity: .82;
}
/* the QR sits on a white tile: the card is near-black, and an inverted code
   (light modules on dark) is read by fewer scanners than the conventional
   dark-on-light. Same width as the button row below it. */
.getcard__qr {
  display: block;
  width: 120px;
  height: 120px;
  margin: 0 auto 12px;
  padding: 8px;
  border-radius: 12px;
  background: #fff;
}
.getcard__btns { display: flex; gap: 12px; }
.getcard__btn {
  display: grid;
  place-items: center;
  width: 54px;
  height: 54px;
  border: 1px solid rgba(255, 255, 255, .14);
  border-radius: var(--r-panel, 18px);
  background: rgba(255, 255, 255, .05);
  color: #fff;
  transition: background .25s var(--ease), border-color .25s var(--ease),
              transform .25s var(--ease);
}
.getcard__btn:hover {
  background: rgba(255, 255, 255, .11);
  border-color: rgba(255, 255, 255, .3);
  transform: translateY(-2px);
}
.getcard__btn img {
  display: block;
  width: 24px;
  height: auto;
  /* both glyphs pure white: icon-googleplay.svg ships the store's four-colour
     mark, and one coloured button next to a white one reads as a mistake */
  filter: brightness(0) invert(1);
}

/* phones: no corner to tuck into — a floating card would sit on the devices.
   It is the hero's only download now, so it joins the copy column as a block
   rather than dropping out. Has to come AFTER the rules above, not in the
   hero's own phone block further up the sheet, or the desktop card wins on
   source order. */
@media (max-width: 700px) {
  /* The card is out on phones and the pills under the copy are the download
     (user 2026-09-01). The card was already QR-less and static here, i.e. a
     dark slab in the copy column doing what two capsules do more plainly.
     The pills inherit the auto-margin trick the card had: `margin: auto auto 0`
     is what centers the copy block and drops the download toward the devices,
     so it moves onto the row that replaced it. */
  .getcard { display: none; }
  .mhero-b__stores {
    display: flex;
    justify-content: center;
    /* order:0 undoes the `order:5` the generic .mhero__ctas phone rule carries
       for the OTHER hero — it would drop the pills below the scenehold, i.e.
       under the devices and off the fold. 0 restores DOM order: under the
       lede, above the render. */
    order: 0;
    margin: auto auto 0;
  }
}

/* tablets (user 2026-08-31): the devices grow 15% (16vw side inset → 11vw) and
   ride 20% of their own height higher (the ~8vh added to the gap), and the
   copy centers between the bar and the ANIMATION's visible top — the corner
   card is out of flow here, so the second auto margin lands on the scenehold.
   For the centering to aim at the devices and not at an empty box, the
   scenehold is sized to exactly the rendered art plus the gap under it. */
@media (min-width: 701px) and (max-width: 1068px) {
  /* the art is sized by BOTH axes: 11vw of side inset is the wide-viewport
     look, and the svh cap reins it in on short windows — landscape tablets
     and squat desktop windows were growing the render taller than the space
     left under the bar + copy, gluing the tablet to the lede.
     --hero-copy ≈ the copy block's height; it is a hand-measured stand-in
     (the JS-measured --copy-h cannot be used here: it depends on the auto
     margins, which depend on the scenehold, which would depend on it). */
  .mhero--b {
    --hero-copy: 310px;
    /* the ×1.3 cap and the gap's 32px floor work as a pair: bigger art,
       seated lower, at the cost of a slight dip past the fold on the
       shortest windows. On tall windows neither term binds: gap stays
       13.5vh, side stays 11vw (user 2026-08-31) */
    --hero-artmax: max(364px, (78svh - var(--hero-copy)) * 1.3);
    --hero-side: max(11vw, (100vw - var(--hero-artmax) * var(--hero-ar)) / 2);
    --hero-art: calc((100vw - 2 * var(--hero-side)) / var(--hero-ar));
    --hero-gap: clamp(32px, min(13.5vh, 100svh - 74px - var(--hero-copy) - var(--hero-art)), 170px);
  }
  /* +20% optical air between the wordmark and the headline (user 2026-08-31;
     supersedes the 2026-08-27 note further down that kept tablets tight) */
  .mhero--b .hero__title { margin-top: 8px; }
  .mhero-b__scenehold {
    margin-top: auto;
    min-height: calc(var(--hero-art) + var(--hero-gap));
  }
  /* portrait tablets only: the corner card and the phone wanted the same
     ~83px (768x1024 covered the readout column). The 56px paint-time
     `translate` does nearly all the clearing (--copy-h and the copy centering
     are untouched, and it composes with the JS parallax, which writes
     `transform`); the tighter padding buys the last 8px — the card's width is
     set by its 120px button row, so shrinking the QR would buy nothing. What
     remains overlaps only the phone's bezel (verified 2026-08-31 at 768x1024:
     the readout column clears the card). Landscape stays centered: the card
     is clear there. */
  @media (orientation: portrait) {
    .mhero-b__plate--fg { translate: -56px 0; }
    .getcard { padding: 14px 16px 16px; }
  }
}

/* ---- stacked ----
   The switch is at 1068, NOT 1000: that is where .why__grid in the drills
   section above drops from three columns to one (styles.css BREAKPOINTS), so
   the two sections change shape on the same line.
   Every negative margin went with the plate PNG: the video is cropped flush to
   the chassis, so its box IS the tablet and the gaps are simply gaps. The
   tablet runs wider here than the reference's 49% — at 393px, 49% would be a
   thumbnail. */
@media (max-width: 1068px) {
  /* same two constraints as desktop — the plate is wider here, so without the
     vh term a short landscape window cut the head or the copy off */
  .ctabs__stage {
    width: clamp(62%, calc((100vh  - 470px) * 1.875), 88%);
    width: clamp(62%, calc((100svh - 470px) * 1.875), 88%);
    margin-top: clamp(18px, 4.3vw, 38px);
  }
  .ctabs__caps {
    margin: clamp(16px, 3.8vw, 35px) auto 0;
    max-width: 42ch;
    padding-inline: 24px;
  }
}

/* ---- desktop (>=1069, i.e. while the drills grid above is still a row) ----
   Flow layout, not absolute. The previous build pinned every element to a
   percentage of a fixed 1725/1150 box. That only held while the head's clamp
   was capped (>=1333px); below that the head grew with vw faster than its
   fixed slot and ran into the tabs. Here every element sits in normal flow and
   the gaps are proportional to --frame, measured off the element above — so
   the head can be any height and nothing can collide.
   Since the plate became a video cropped flush to the chassis there is no
   transparent art left to cancel, so these are plain positive gaps. */
@media (min-width: 1069px) {
  .ctabs__head { position: relative; z-index: 3; }
  /* Sized off the reference (49% of the frame) but stepped down to 42% and
     given a SECOND constraint: the height left over once the head, tabs and
     sub-copy have taken their share. Without it the band ran 1292px tall at
     the comp width, so on any normal laptop viewport either the headline or
     the copy was always cut off on the way past. The vh term keeps the whole
     composition inside the window; the frame term stops it inflating on tall
     ones. 1.306 is the plate's own aspect. (chris 2026-08-24) */
  .ctabs__stage {
    position: relative;
    width: clamp(calc(var(--frame) * .39),
                 calc((100vh  - 536px) * 1.875),
                 calc(var(--frame) * .51));
    width: clamp(calc(var(--frame) * .39),
                 calc((100svh - 536px) * 1.875),
                 calc(var(--frame) * .51));
    margin-top: calc(var(--frame) * .019);
  }
  .ctabs__caps {
    width: calc(var(--frame) * .4325);   /* 746/1725 */
    max-width: 100%;
    margin: calc(var(--frame) * .018) auto 0;
    padding: 0 24px;
  }
}
@media (prefers-reduced-motion: reduce) {
  .ctabs__thumb { transition: none; }
}

/* ======================================================================
   FAQ · "FAQs" composition            (Figma 1458:7888, shipped 2026-08-26)

   Replaces the light two-column FAQ with the dark one: the blurred studio
   plate as ground, the hand+phone mockup over it, "FAQs" set large and
   centred BEHIND that mockup, and the eight rows split into two columns.

   Everything is scoped to .faq--v3 so the base .faq / .qa rules in
   styles.css (2552-2609) stay exactly as they are — this is a layer on
   top, not a rewrite, and pulling the modifier off the section restores
   the old light block untouched.
   ====================================================================== */
.faq--v3 {
  position: relative;
  overflow: hidden;
  padding: 0 0 var(--faq-foot, 260px);
  background: var(--black);
  /* Igualado al padding vertical (user 2026-08-26). El
     inset no le cuesta ancho al texto — la card sangra hacia afuera lo
     mismo que el texto padea hacia adentro, así que la línea de la
     pregunta no se acorta. Lo que sí consume es aire entre columnas, y
     eso dejó de importar al pasar a solo-una-abierta: nunca hay dos
     cards visibles lado a lado. */
  --faq-inset: 22px;   /* = el padding vertical del summary (26-4), para que
                          el texto quede a la misma distancia de los cuatro
                          bordes de la card (user 2026-08-26) */
}

/* --- ground. NOT a gradient though it renders as one: it is a studio
   plate under a ~213px blur. The plate is 222%-wide in the Figma's own
   framing, hung off the left edge — that offset is what puts its bright
   side under the FAQ column and its dark side under the headline.
   Box and position are written by js/appfaq.js, because `top` and
   `height` in percent would resolve against the section's HEIGHT, which
   grows with every open row; these have to track its WIDTH. --- */
.faq--v3 .faq__bg { position: absolute; inset: 0; z-index: 0; pointer-events: none; }
.faq--v3 .faq__blur {
  position: absolute; top: var(--faq-bg-top, 0px); left: var(--faq-bg-left, 0px);
  width: var(--faq-bg-w, 152%); height: var(--faq-bg-h, auto);
  object-fit: cover; object-position: center;
  /* REQUIRED, not defensive: styles.css:87 ships `img, svg { max-width:
     100% }`, which caps this plate at the section's width and destroys the
     whole point of it — the 152%/-19% framing is what puts the photo's
     bright side under the FAQ column and its dark side under the headline.
     Measured before the fix: a 594px plate rendered 421px wide.
     The same trap is already documented in this file at .compat, where it
     capped a 135% lineup and shoved it off centre. */
  max-width: none;
  /* the radius has to SCALE with the section, not sit at a fixed 60px. A
     CSS blur fades the element's own edges over roughly 3x its radius, and
     the plate only overhangs by a fraction of the width — so at 1440 the
     484px of right-hand overhang swallows a 180px fade, while at 390 the
     overhang is 97px and a fixed 60px blur ate a visible black strip down
     the edge. Proportional keeps the fade a constant fraction. */
  filter: blur(var(--faq-blur-r, clamp(24px, 4.2vw, 60px)));
  /* the blur samples past the element's own edges, which would bleed the
     page's black in as a dark vignette; scaling up hides the fringe */
  transform: scale(1.08);
}
.faq--v3 .faq__bg::after {
  content: ""; position: absolute; inset: 0;
  /* Three layers, bottom-up: a flat 10% black (the Figma's own), a 28%
     scrim over it, and a black ramp down the top 62% (1112 of the Figma's
     1781) which is what sinks the hand into black while the FAQ column
     stays lit.
     The 28% is not decoration — with the Figma's values alone the rows
     measure 2.6:1 against the type, and AA wants 4.5:1 for 16px at weight
     584 (the "large text" exemption starts at 24px, or 18.66px at 700). */
  background:
    linear-gradient(180deg, #000 0%, rgba(0, 0, 0, 0) 62%),
    linear-gradient(0deg, rgba(0, 0, 0, .28), rgba(0, 0, 0, .28)),
    rgba(0, 0, 0, .1);
}

/* --- the hand plate. Right-aligned at the Figma's own 1353/1440, which is
   not a taste call: the word is centred in the section, so at a smaller
   plate the centre lands on its empty left side and the overlap that the
   whole composition depends on never happens. --- */
.faq--v3 .faq__hand {
  position: relative; z-index: 3;
  display: flex; justify-content: flex-end;
  pointer-events: none;
  padding-top: clamp(20px, 4vh, 56px);
}
.faq--v3 .faq__hand img {
  width: min(1353px, 94vw); height: auto; display: block;
  max-width: none;                      /* styles.css:87, same trap */
  /* scroll parallax, written by js/appfaq.js. No clipping window and no
     scale: the plate is an alpha cutout on black, so there is no window
     edge to protect and the travel is not capped by faked slack. */
  transform: translate3d(0, var(--faq-par, 0px), 0);
  will-change: transform;
}

/* --- head + list --- */
.faq--v3 .faq__grid {
  position: relative; z-index: 1;
  display: block;
  padding-top: var(--faq-head-pad, 0px);
}
.faq--v3 .faq__head {
  position: absolute; left: 0; right: 0;
  top: var(--faq-title-y, 0px);
  z-index: 2;                    /* over the ground, UNDER the hand */
  text-align: center;
  pointer-events: none;
}
.faq--v3 .faq__head .h2 {
  /* Sin overrides tipograficos (user 2026-08-26, "igual que Get started
     with Foresight+"): el titulo es el .h2 estandar de las secciones —
     72/700/1.25/-2% salen de la base. Del spec original del Figma
     (100px/.8/-5px) no queda nada. Solo el blanco y el max-width: la
     base lo capa y aca va centrado. OJO: los tres valores van explicitos
     igual — la base .faq__head .h2 (la FAQ clara vieja) es mas especifica
     que .h2 y sin esto el titulo caia a SU tamano (32px), no al estandar. */
  font-size: clamp(2.4rem, 5.4vw, 4.5rem);
  line-height: 1.25;
  letter-spacing: -.02em;
  max-width: none;
  color: #fff;
}
.faq--v3 .faq__list {
  /* was 0 while the "We got ya covered" CTA module sat between the title
     and the questions and carried the spacing. With that module removed
     this is THE title-to-questions gap, and it is Premier's rule verbatim
     (user 2026-09-04, "replicate the relationship"). On desktop appfaq.js
     reads this value, zeroes it inline and carries it as grid padding
     instead — a margin here would collapse through the block grid — or,
     when the head floats too far above the grid, slides the grid up under
     it; see place(). On phones the head is in flow and drops its own
     margin-bottom, so this margin is the whole gap. */
  margin-top: clamp(48px, 6vh, 72px);
  display: grid;
  /* two REAL containers, not CSS columns and not a shared grid: either of
     those couples the sides, and opening a row on the left would push or
     gap the right */
  grid-template-columns: 1fr 1fr;
  /* La calle se mide DESCONTANDO el sangrado de las cards: aire real =
     gap - 2 x inset(22). El gap anterior (43 a 1440) quedó en cero cuando
     el inset subió a 22 y los filetes de las dos columnas casi se tocaban
     (user 2026-08-26, "aca tiene que haber mas espacio"). Con esto el aire
     real es ~50px a 1440 y nunca baja de ~28. El costo: la pregunta larga
     ahora se parte en dos lineas por debajo de ~1250px (antes ~1205). */
  column-gap: clamp(72px, 6.5vw, 94px);
  align-items: start;
}

/* --- rows --- */
.faq--v3 .qa {
  border-top: 1px solid rgba(255, 255, 255, .1);
  border-radius: 12px;
  /* how far the card bleeds past the text. In two columns this is charged
     TWICE to the gutter, so it is the cheapest place to buy column width
     back. Text stays flush because summary and panel pad by the same
     number. Measured: the long second question needs 535px and fits on one
     line from ~1205px up. */
  margin-inline: calc(var(--faq-inset, 10px) * -1);
  /* a few px between EVERY row, so an open card never butts straight into
     the next row's rule. Closed it is invisible: the rule is a border-top,
     so it simply sits a few px lower. */
  margin-block-end: 6px;
  /* the full border exists while closed, transparent. Declaring the sides
     only on [open] flashes a white ring: their width jumps 0->1px at once
     and the colour transition then starts from the unset value, which is
     currentColor. */
  border: 1px solid transparent;
  border-top-color: rgba(255, 255, 255, .1);
  -webkit-backdrop-filter: saturate(100%) blur(0px);
  backdrop-filter: saturate(100%) blur(0px);
  transition: background .45s var(--ease), border-color .45s var(--ease),
              backdrop-filter .45s var(--ease), -webkit-backdrop-filter .45s var(--ease);
}
.faq--v3 .qa:last-child {
  /* sin regla de cierre (user 2026-08-26): la lista termina en el aire, no
     en una linea. `transparent` y no `0` para que la caja no cambie de alto
     respecto de sus hermanas. */
  border-bottom-color: transparent;
  margin-block-end: 0;
}
.faq--v3 .qa summary {
  padding: calc(var(--faq-pad, 26px) - 4px) var(--faq-inset, 10px);
  color: rgba(255, 255, 255, .9);
  transition: padding-bottom .3s var(--ease);
}
.faq--v3 .qa summary:hover { color: rgba(255, 255, 255, .9); }
/* The question-to-answer gap comes off the SUMMARY's bottom pad, never off
   a negative margin on the answer: .qa__panel is overflow:hidden (it has to
   be, the open/close animates its height) so pulling the <p> up moves its
   top edge outside the box and the paint stops there. Shrinking the pad
   below the question is equivalent and safe — it sits BELOW the text, so
   the question does not move; only the panel comes up.
   Why 10px less at all: the box gap already carries the question's
   half-leading ((24-16)/2 = 4px) plus the answer's (4.2px), so a "22px"
   pad optically reads as ~30. */
.faq--v3 .qa[open] summary { padding-bottom: calc(var(--faq-pad, 26px) - 14px); }
/* el + al 80% de blanco (user 2026-08-26). El #57575c del Figma daba ~2.4:1
   contra este fondo y AA pide 3:1 para elementos no textuales, asi que esto
   ademas cierra ese hueco. */
.faq--v3 .qa summary .qa__icon::before,
.faq--v3 .qa summary .qa__icon::after { background: rgba(255, 255, 255, .8); }
.faq--v3 .qa:hover { background: rgba(255, 255, 255, .035); }
/* Mix: the blur and the dim of the hero's Download card, but every rule
   stays where it was — the row does not detach, so the list's rhythm
   survives an open. Values lifted verbatim from .getcard. */
.faq--v3 .qa[open] {
  background: rgba(9, 11, 10, .58);
  border-color: rgba(255, 255, 255, .12);   /* width already there; colour only */
  -webkit-backdrop-filter: saturate(150%) blur(18px);
  backdrop-filter: saturate(150%) blur(18px);
}
.faq--v3 .qa__panel p {
  padding: 0 60px calc(var(--faq-pad, 26px) - 4px) var(--faq-inset, 10px);
  color: rgba(255, 255, 255, .62);
  max-width: none;
}
/* asterisk footnote under an answer (user 2026-09-04): a step smaller and
   quieter than the answer, tucked up under the paragraph it qualifies — the
   answer's own bottom padding would otherwise hold it ~22px away. */
.faq--v3 .qa__panel .qa__note {
  margin-top: -12px;
  font-size: 13px;
  line-height: 1.5;
  color: rgba(255, 255, 255, .45);
}

/* stacked, col 1's closing rule lands on col 2's opening one and the seam
   draws twice. `transparent` rather than `0` keeps the box the same height. */
@media (max-width: 860px) {
  .faq--v3 .faq__list { grid-template-columns: 1fr; }
  .faq--v3 .faq__col:first-child .qa:last-child { border-bottom-color: transparent; }
}

/* La card sangra --faq-inset por fuera del contenedor. En desktop ese
   sangrado muere en la calle interna y en los 43px de margen del 94vw;
   con los gutters chicos de tablet/teléfono se comía el margen del sitio
   entero (16px de gutter - 22 de sangrado = filete FUERA de la pantalla).
   El padding devuelve el sangrado adentro de la lista: la card y su
   hairline caen exacto en el borde del contenedor — el mismo borde que
   las why-cards — y el texto conserva sus 22px de aire interno. */
@media (max-width: 1024px) {
  .faq--v3 .faq__list { padding-inline: var(--faq-inset, 22px); }
}

/* On a phone the overlap is dropped: it only ever worked because the plate
   was wide enough for the word to land on the wrist. Stack it literally —
   a BIGGER hand on top, clear air, the word, the list. js/appfaq.js stops
   writing --faq-title-y and --faq-head-pad below 700 to match. */
@media (max-width: 700px) {
  .faq--v3 .faq__hand { justify-content: center; padding-top: 0; }
  .faq--v3 .faq__hand img { width: 132vw; max-width: none; }
  .faq--v3 .faq__grid { padding-top: clamp(28px, 7vw, 56px); }
  .faq--v3 .faq__head { position: static; margin-bottom: 0; } /* the list's margin-top owns the gap, same as Premier (user 2026-09-04) */
  .faq--v3 .qa summary { font-size: 17px; }
  .faq--v3 .qa__panel p { padding-right: var(--faq-inset, 10px); font-size: 16px; }
  /* the foot, -40% (user 2026-08-27): 260 -> 156. Phone block ONLY; from 701
     up there is already its own 182 (the -30% from 2026-08-26) and that
     decision stays untouched. */
  .faq--v3 { --faq-foot: 156px; }
}
@media (min-width: 701px) {
  /* two columns halve the measure, so 20px runs long */
  .faq--v3 .qa summary { font-size: 16px; line-height: 24px; }
  .faq--v3 .qa__panel p { font-size: 16px; line-height: 1.6; }

  /* the foot, 30% tighter (user 2026-08-26). 260 -> 182. */
  .faq--v3 { --faq-foot: 182px; }

  /* ...and the seam with the section above pulled in. Measured on the page:
     the 120px between the "Explore Foresight+" button and this section is
     ALL of .thisis's own padding-bottom — none of it is ours — and .thisis
     is shared with other pages, so it cannot be the thing that changes.
     A negative margin here is the scoped equivalent: it lifts this section
     over that empty padding without editing anyone else's rule.
     Verified safe first: the only thing living in that 120px band is the
     CTA's own <b>, which ends 15px in, leaving ~105px genuinely empty.
     The perceived gap is larger than the CSS one — 201px, because the hand
     plate's alpha is empty for its top 11% — so after the lift it still
     reads as ~105px of air, which is a normal section break. */
  .faq--v3 { margin-top: -96px; }
}
@media (prefers-reduced-motion: reduce) {
  .faq--v3 .faq__hand img { transform: none; }
}

/* ----------------------------------------------------------------------
   Módulo CTA bajo el título        (Figma Tests-email 389:3830)

   Base del Figma: píldora oscura al 20% de negro, radio completo, y un
   resplandor azul que sube del borde inferior — rgba(59,165,255,.3) al
   centro, rgba(30,109,255,.09) al 42%, transparente al 70%, elipse 130x39.

   El hover está calcado del gesto de la referencia (threeui sylva), mirado
   cuadro a cuadro con y sin hover. Lo que hace NO es un resplandor interno:
     · el BORDE se enciende como un filete especular, con un punto de luz
       que recorre el anillo;
     · un BLOOM sale hacia afuera, por debajo sobre todo;
     · la píldora crece apenas.
   Y casi no sigue al puntero — comparando dos posiciones de hover el filete
   es prácticamente el mismo. Por eso el puntero aquí sólo inclina el brillo,
   no lo arrastra: mandar la luz detrás del mouse se veía más "linterna" que
   vidrio.

   Su versión corre en WebGL (cada botón tiene su canvas .liquid-fx y el
   botón real lleva `background: none`). Esto no es su implementación: es el
   mismo gesto en CSS — un anillo enmascarado de 1px sobre un cónico que
   gira, más box-shadow para el bloom. Sin canvas ni dependencias.
   ---------------------------------------------------------------------- */
@property --faqcta-a { syntax: "<angle>"; inherits: false; initial-value: 0deg; }
@property --faqcta-x { syntax: "<percentage>"; inherits: false; initial-value: 50%; }

/* el módulo sube (user 2026-08-26): el aire sobre él baja un 80% — de 88 a
   18px, que lo escribe appfaq.js como padding del grid — y lo que se le
   quita arriba se le suma abajo, para separarlo de las preguntas. */
.faq--v3 .faq__cta { display: flex; justify-content: center; margin-bottom: clamp(72px, 10vh, 112px); }
/* El módulo se sube con margen, no con el padding del grid: appfaq.js ya
   clampea ese padding a 0 y aquí ya estaba en 0 — el título flota 95px por
   encima del grid, así que la cuenta daba negativa y el módulo ya estaba lo
   más arriba que la caja permite. Los -20 llevan el hueco visible de 38 a
   18, que es el 80% menos pedido. Sólo en desktop: en teléfono la cabecera
   vuelve al flujo y esto la pisaría. */
@media (min-width: 701px) { .faq--v3 .faq__cta { margin-top: -20px; } }

.faq--v3 .faqcta {
  position: relative;
  display: flex; align-items: center; gap: 16px;
  padding: 10px 10px 10px 19px;  /* left +20% (user 2026-08-26): el label respiraba menos que el boton */
  border-radius: 9999px;
  background: rgba(0, 0, 0, .2);
  border: 1px solid rgba(255, 255, 255, .10);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  isolation: isolate;
  /* magnético (user 2026-08-26): appfaq.js escribe --tx/--ty con la
     distancia del puntero al centro, y el transform los COMPONE con el
     scale del hover — si el JS escribiera element.style.transform pisaría
     al hover y viceversa. La transición con leve overshoot es el resorte:
     amortigua el arrastre mientras el puntero se mueve y devuelve la
     píldora con un rebotecito al salir. */
  transform: translate3d(var(--tx, 0px), var(--ty, 0px), 0);
  transition: transform .45s cubic-bezier(.3, 1.3, .5, 1),
              border-color .5s var(--ease),
              box-shadow .5s var(--ease), background-color .5s var(--ease);
}

/* EL FILETE GIRATORIO — estado por DEFECTO, no hover (user 2026-08-26).
   El anillo se pinta enmascarando la caja contra su propio content-box:
   queda sólo el 1px del borde, y ahí abajo va un cónico que gira. El
   `padding` define el grosor.
   Tirado al azul y no al blanco: sobre este fondo el blanco leía como un
   borde de UI, y el azul lo emparenta con el resplandor del Figma. */
.faq--v3 .faqcta::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  padding: 1px;
  background: conic-gradient(from var(--faqcta-a),
      rgba(120,180,255,0) 0deg,
      rgba(110,180,255,.45) 34deg,
      rgba(190,225,255,.92) 62deg,
      rgba(90,160,255,.55) 96deg,
      rgba(120,180,255,0) 150deg,
      rgba(120,180,255,0) 360deg);
  -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;
  animation: faqcta-spin 5.2s linear infinite;
  transition: opacity .45s var(--ease);
  pointer-events: none;
}
/* en hover no se enciende: ya estaba encendido. Se acelera y sube el brillo. */
.faq--v3 .faqcta:hover::before { animation-duration: 2.8s; }
@keyframes faqcta-spin { to { --faqcta-a: 360deg; } }

/* la luz de adentro. Dos capas: la del Figma, fija al pie, y la del hover.
   La del hover está calcada del close-up de la referencia (user 2026-08-26):
   NO son manchas radiales — es una BANDA que ocupa casi todo el ancho de la
   píldora, abrazada al borde inferior, con el núcleo casi blanco y el propio
   borde de abajo encendido como una línea. Tres capas, de arriba de todo
   hacia abajo: la línea del borde, el núcleo ancho y brillante, y el cuerpo
   que sube hasta media altura. Quieta y centrada, como en el ejemplo — el
   seguimiento del puntero se probó y leía a linterna, no a vidrio. En azul
   (pedido explícito), con el núcleo empujado hacia blanco como allá. */
.faq--v3 .faqcta__glow {
  position: absolute; inset: 0; border-radius: inherit; z-index: -1;
  overflow: hidden; pointer-events: none;
}
.faq--v3 .faqcta__glow::before,
.faq--v3 .faqcta__glow::after {
  background:
    /* la línea del borde inferior */
    linear-gradient(0deg, rgba(215,240,255,.75) 0%, rgba(215,240,255,0) 9%),
    /* el núcleo: ancho, bajo, casi blanco — su centro es --faqcta-x, que la
       animación mece de lado a lado (los rayos se probaron y no cerraron;
       en su lugar la luz VIVA es esto: la banda de siempre, respirando) */
    radial-gradient(82% 30% at var(--faqcta-x, 50%) 102%,
      rgba(205,232,255,.78) 0%, rgba(150,205,255,.30) 52%, rgba(120,185,255,0) 76%),
    /* el cuerpo la sigue a medio paso, para que no viaje como un bloque */
    radial-gradient(105% 68% at calc(50% + (var(--faqcta-x, 50%) - 50%) / 2) 106%,
      rgba(95,175,255,.40) 0%, rgba(45,125,255,.15) 48%, rgba(30,109,255,0) 78%);
  opacity: 0; transition: opacity .5s var(--ease);
}
/* el vaivén sólo corre en hover — a opacidad 0 sería pintar de gusto */
.faq--v3 .faqcta:hover .faqcta__glow::after {
  animation: faqcta-sway 4.2s ease-in-out infinite;
}
@keyframes faqcta-sway {
  0%   { --faqcta-x: 50%; }
  28%  { --faqcta-x: 43%; }
  72%  { --faqcta-x: 57%; }
  100% { --faqcta-x: 50%; }
}
.faq--v3 .faqcta:hover .faqcta__glow::after { opacity: 1; }

/* el bloom hacia afuera */
.faq--v3 .faqcta:hover {
  transform: translate3d(var(--tx, 0px), var(--ty, 0px), 0) scale(1.02);
  border-color: rgba(150,195,255,.22);
  background-color: rgba(90,150,255,.05);
  box-shadow: 0 12px 48px -10px rgba(70,150,255,.50),
              0 0 76px -16px rgba(120,185,255,.28);
}

/* El anillo que se expande — el "expandido" del botón de play de la
   referencia. Medido ahí: el botón es 57x57 y su canvas 255x255, o sea 4,5x,
   que es el aire que necesita para crecer.

   Crece por INSET, no por scale (user 2026-08-26). Escalar un pill deforma
   su radio: el `border-radius: 9999px` está capado a la mitad de la altura,
   así que al agrandarlo las esquinas se estiran y el anillo deja de ser
   paralelo al módulo. Animando el inset la línea se aleja en paralelo y
   queda proporcional a cualquier tamaño, en desktop y en teléfono.

   UN solo anillo, no dos: con dos desfasados se veían dos líneas a la vez y
   leía como un blanco de tiro. */
.faq--v3 .faqcta__rings {
  position: absolute; inset: 0; border-radius: inherit;
  pointer-events: none; z-index: -1;
}
.faq--v3 .faqcta__rings::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  border: 1px solid rgba(190, 220, 255, .55);
  opacity: 0;
}
/* el pulso corre SIEMPRE (user 2026-08-26): el módulo respira solo. Lo que
   queda reservado al hover es lo de adentro — la luz y el crecimiento. */
.faq--v3 .faqcta__rings::before {
  animation: faqcta-ring 3.2s var(--ease) infinite;
}
.faq--v3 .faqcta:hover .faqcta__rings::before { animation-duration: 2.4s; }
@keyframes faqcta-ring {
  0%   { inset: 0;      opacity: .55; }
  70%  { opacity: .10; }
  100% { inset: -26px;  opacity: 0; }
}

.faq--v3 .faqcta__label {
  font-size: 20px; line-height: 1.2; font-weight: 584; letter-spacing: -.02em;
  color: rgba(255, 255, 255, .9); white-space: nowrap;
}
.faq--v3 .faqcta__btn {
  flex: none;
  padding: 10px 16px; border-radius: 9999px;
  background: #fff; color: #000;
  font-size: 16px; line-height: 24px; font-weight: 584; white-space: nowrap;
  /* el botón tira un 35% extra hacia el puntero: dos capas moviéndose a
     velocidades distintas es lo que hace leer el conjunto como imán y no
     como un simple translate */
  transform: translate3d(calc(var(--tx, 0px) * .35), calc(var(--ty, 0px) * .35), 0);
  transition: transform .45s cubic-bezier(.3, 1.3, .5, 1), box-shadow .4s var(--ease);
}
.faq--v3 .faqcta:hover .faqcta__btn {
  transform: translate3d(calc(var(--tx, 0px) * .35), calc(var(--ty, 0px) * .35), 0) scale(1.03);
  box-shadow: 0 0 22px -4px rgba(255,255,255,.5);
}

/* En teléfono el módulo sigue en UNA línea, como en desktop (user
   2026-08-26) — antes se apilaba en columna. Se compacta lo justo para
   que label + botón entren en los 343px de un viewport de 375 con los
   gutters de 16; el nowrap del label queda, que es lo que garantiza la
   línea única. */
@media (max-width: 560px) {
  .faq--v3 .faqcta { gap: 12px; padding: 8px 8px 8px 17px; }
  .faq--v3 .faqcta__label { font-size: 16px; }
  .faq--v3 .faqcta__btn { padding: 8px 14px; font-size: 14px; line-height: 22px; }
}
/* el compacto mide 298px — en un viewport de 320 con gutters de 16 hay
   288 disponibles, así que falta un escalón más */
@media (max-width: 360px) {
  .faq--v3 .faqcta { gap: 10px; padding: 8px 8px 8px 14px; }
  .faq--v3 .faqcta__label { font-size: 15px; }
  .faq--v3 .faqcta__btn { padding: 8px 12px; font-size: 13px; }
}
@media (prefers-reduced-motion: reduce) {
  .faq--v3 .faqcta, .faq--v3 .faqcta__btn { transition: none; transform: none; }
  .faq--v3 .faqcta:hover { transform: none; }
  .faq--v3 .faqcta:hover::before { animation: none; }
}

/* ======================================================================
   AIR AROUND THE COURSES BAND, +40% ON PHONE / SMALL TABLET
   (user 2026-08-27)

   Neither of the two gaps comes from a single rule — each one is built by two
   neighbouring sections. Measured at 733px:

     top     = .train padding-bottom 64 + .ctabs --padT 112 = 176px
     bottom  = .ctabs --padB 112 + .compat padding-top 86   = 198px

   That is why the +40% is applied to all FOUR values rather than one: each gap
   grows by exactly 40% (176->246, 198->277) and the proportion between the
   scrim's runway and the neighbours' padding is preserved. .ctabs' scrim
   follows on its own, since its stops are fractions of --padT/--padB.
   ====================================================================== */
@media (max-width: 820px) {
  .train { padding-bottom: clamp(90px, 12.6vh, 168px); }        /* x1.4 of 64/9vh/120 */
  .ctabs {
    --padT: clamp(157px, min(calc(var(--frame) * .154), 25.2vh), 280px);
    --padB: clamp(157px, min(calc(var(--frame) * .154), 25.2vh), 280px);
  }
  .compat { padding-top: clamp(118px, 16.8vh, 224px); }         /* x1.4 of 84/12vh/160 */
}
/* styles.css drops .compat to 64/9vh/92 below 640 — that one x1.4 too */
@media (max-width: 640px) {
  .compat { padding-top: clamp(90px, 12.6vh, 129px); }
}

/* The air between the MOBILE wordmark and the headline, TRIPLED and PHONE
   ONLY (user 2026-08-27): .35rem -> 1.05rem, i.e. 5.6 -> 16.8px. Then +20%
   OPTICAL on top of that (user 2026-08-31): the optical gap is the margin
   plus the h1's half-leading (~6px), so 16.8 + 6 = 22.8 x1.2 ≈ 27.4 →
   margin 21.6px = 1.35rem. (Tablets moved separately the same day — their
   8px lives in the 701-1068 hero block, superseding the 2026-08-27 rejection
   noted here.)
   MIND: this rule wins over the hero block's own margin-top by source order —
   the phone value must be edited HERE, not there; a duplicate declared there
   is dead on arrival, which is exactly how the 2026-08-31 first attempt
   silently no-opped. */
@media (max-width: 700px) {
  .mhero--b .hero__title { margin-top: 1.35rem; }
}

/* The gap between the drills cards and the courses band, +30% ON DESKTOP
   (user 2026-08-27). As above, the gap is built by two sections: at 1440 it is
   .train padding-bottom 81 + --padT 158.4 = 240px. Both x1.3 -> 105 + 206 =
   311px, which preserves the proportion between the neighbour's padding and
   the scrim's runway.
   --padB gets the same clamp even though the bottom gap was not flagged:
   asymmetric was tried (206 top / 158 bottom) and the band read unbalanced,
   with a longer entry than exit. The scrim takes each ramp's length from its
   own variable, so matching them keeps both seams at the same slope.
   >=1069 is where .ctabs returns to its desktop layout; from 820 down the
   mobile block and its +40% takes over. */
@media (min-width: 1069px) {
  .train { padding-bottom: clamp(83px, 11.7vh, 156px); }        /* x1.3 de 64/9vh/120 */
  .ctabs {
    --padT: clamp(146px, min(calc(var(--frame) * .143), 23.4vh), 260px);
    --padB: clamp(146px, min(calc(var(--frame) * .143), 23.4vh), 260px);
  }
}

/* See-your-game's plate, flat black ON DESKTOP (user 2026-08-27).
   The gradient ended at #06070a right against .train, which is flat #000: a
   6/7/10-per-channel step, and a blue-tinted one at that (the B channel rises
   10 against red's 6), so the bottom seam read as a line.
   Scoped to >=1001 because that is where the card lifts off: from 1000 down it
   has padding 0 and radius 0, covers the plate on all four sides and the
   gradient is never visible — changing it there or not makes no difference. */
@media (min-width: 1001px) {
  .deck--see { background: #000; }
}

/* Short phones (SE, older Androids): the dedicated max-height block that
   lived here retired on 2026-08-31 — it was written against the old
   viewport-sized scenehold (min(86vw, 460px)) and, sitting later in the
   sheet, silently overrode the art-sized geometry the hero block computes
   now. That geometry is height-aware by itself: the scenehold's 45svh term
   scales with short viewports and the padding is already down to 74px. */
