/* ==========================================================================
   Foresight Premier — product page
   ========================================================================== */

@font-face {
  font-family: "TT Interphases Pro";
  /* woff2 first — same variable font at a third of the bytes (699KB -> 242KB);
     the ttf stays as the fallback for anything that cannot take it */
  src: url("../fonts/TTInterphasesProVariable.woff2") format("woff2-variations"),
       url("../fonts/TTInterphasesProVariable.ttf") format("truetype-variations");
  font-weight: 100 900;
  font-stretch: 75% 100%;
  font-display: swap;
}

@font-face {
  font-family: "TT Interphases Pro Mono";
  src: url("../fonts/TTInterphasesProMonoVar.woff2") format("woff2-variations"),
       url("../fonts/TTInterphasesProMonoVar.ttf") format("truetype-variations");
  font-weight: 400 700;
  font-display: swap;
}

:root {
  /* Palette = Product UI Library tokens (Figma). Base/Black·White, Brand/500·600,
     and the Alpha system: text on dark = Alpha/Text (#fafafa at 100/80/60),
     inks on light = alpha-black-90/70/50, hairlines = alpha 10. */
  --black: #000;              /* Base/Black */
  --elev: #0d0d0d;            /* alpha-white-5 over black */
  --card-dark: #1a1a1a;       /* alpha-white-10 over black */
  --light: #f5f5f6;           /* Gray (dark mode)/50 */
  --white: #fff;              /* Base/White */

  --ink-d1: #f5f5f6;                    /* text-primary (dark) */
  --ink-d2: rgba(250, 250, 250, .6);    /* Alpha/Text/Tertiary */
  --ink-d3: rgba(255, 255, 255, .5);    /* alpha-white-50 */
  --ink-l1: rgba(0, 0, 0, .9);          /* alpha-black-90 */
  --ink-l2: rgba(0, 0, 0, .7);          /* alpha-black-70 */

  --blue: #0348d9;            /* Brand/500 */
  --blue-light: #3ba5ff;      /* no library equivalent — loose value */
  --grad-blue: linear-gradient(94deg, #8fd8ff 0%, #3ba5ff 48%, #1e6dff 100%);

  --line-d: rgba(255, 255, 255, .1);    /* alpha-white-10 */
  --line-l: rgba(0, 0, 0, .1);          /* alpha-black-10 */

  --r-card: 28px;
  --r-panel: 18px;

  /* Scroll parallax: how far a media plate overhangs its clipped window on
     each side (percent of the window's HEIGHT — top/bottom insets resolve
     against it). The JS travel is 5.5%, so this leaves 2.5% of slack; without
     it the plate's edge slips into frame on short cards, where the Foresight+
     deck clamps to 420px and the overhang shrinks with it. */
  --par-over: 8%;

  --font: "TT Interphases Pro", -apple-system, BlinkMacSystemFont,
          "Helvetica Neue", Helvetica, Arial, sans-serif;
  --mono: "TT Interphases Pro Mono", ui-monospace, "SF Mono", SFMono-Regular, Menlo, monospace;

  --ease: cubic-bezier(.22, .61, .36, 1);
  --nav-h: 52px;
}

/* ---------- Reset / base ---------- */

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

/* Safari drew the whole page oblique (user 2026-09-18): TT Interphases Pro's
   slnt axis runs 0..11 — positive, where OpenType expects negative — and at
   font-style: normal WebKit lands on the far end of it (measured 11.1deg in
   Safari 26.4, 0 in Chrome). Pinning the axis is the one thing that holds it
   upright there; no font-style range or @font-face setting does.
   On every element and !important because the `font` shorthand resets
   font-variation-settings wherever it is used (download-modal.css, seven
   times) and that failure is silent outside Safari. Nothing here drives an
   axis by hand: weight and width go through font-weight / font-stretch. */
*, *::before, *::after { font-variation-settings: "slnt" 0 !important; }

html {
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--nav-h) + 12px);
  -webkit-text-size-adjust: 100%;
}

body {
  font-family: var(--font);
  font-size: 17px;
  line-height: 1.5;
  letter-spacing: -.011em;
  background: var(--black);
  color: var(--ink-d1);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: clip;
}

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

::selection { background: rgba(59, 165, 255, .35); }

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

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

/* ---------- Layout helpers ---------- */

.container        { width: min(1080px, 92vw); margin-inline: auto; }
.container-wide   { width: min(1320px, 94vw); margin-inline: auto; }
.container-narrow { width: min(760px, 92vw);  margin-inline: auto; }

.theme-dark  { background: var(--black); color: var(--ink-d1); }
.theme-light { background: var(--light); color: var(--ink-l1); }

/* ---------- Type ---------- */

/* Display 2xl/Bold — 72/90, −2% (Product UI Library). The old 4.2rem/1.06
   ramp was off-system: it capped at 67px and ran a much tighter leading. */
.h2 {
  font-size: clamp(2.4rem, 5.4vw, 4.5rem);
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: -.02em;
}
/* Display 3xl/Bold — 96/120, −2%; same step as the hero H1 */
.h2--xl { font-size: clamp(2.7rem, 6.4vw, 6rem); }

.h3-lg {
  font-size: clamp(1.7rem, 3.2vw, 2.4rem);
  font-weight: 700;
  letter-spacing: -.018em;
}

.lede {
  font-size: clamp(1.12rem, 1.9vw, 1.4rem);
  line-height: 1.42;
  letter-spacing: -.013em;
  max-width: 46ch;
  /* Optical gap, not box gap: the h2's line-height went 1.06 → 1.25 with the
     Display 2xl ramp, which added ~5px of half-leading between title and lede
     for free. Pulled 1.1rem → .8rem so the SEEN gap lands back at ~23px. */
  margin-top: .8rem;
}
.theme-dark .lede  { color: var(--ink-d2); }
.theme-light .lede { color: var(--ink-l2); }

/* Corner-bracket tag (per Figma 11720:28332) — Mono sm caps between hairline
   brackets. White on dark, black on light; the brackets follow currentColor. */
.eyebrow {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 400;
  line-height: 1.2;
  letter-spacing: .1em; /* 1.2px at 12px */
  text-transform: uppercase;
  color: #fff;
  margin-bottom: .9rem;
}
.theme-light .eyebrow { color: #000; }

.eyebrow::before,
.eyebrow::after {
  content: "";
  flex: none;
  height: 20.03px;
  background: currentColor;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
}
.eyebrow::before {
  width: 5.132px;
  -webkit-mask-image: url("../assets/tag-bracket-l.svg");
  mask-image: url("../assets/tag-bracket-l.svg");
}
.eyebrow::after {
  width: 5px;
  -webkit-mask-image: url("../assets/tag-bracket-r.svg");
  mask-image: url("../assets/tag-bracket-r.svg");
}

/* Gradient text. Unused on the home since 2026-07-31 (the Foresight+ headline
   went flat white) — kept because the cinematic still builds on --grad-blue. */
.grad {
  background: var(--grad-blue);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.footnote {
  font-size: .8rem;
  line-height: 1.5;
  letter-spacing: 0;
  color: var(--ink-l2);
  max-width: 64ch;
}
.theme-dark .footnote { color: var(--ink-d3); }

/* ---------- Buttons & links ---------- */

.cta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  align-items: center;
  margin-top: 1.9rem;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 13px 26px;
  border-radius: 980px;
  font-size: 1rem; /* Text md/Semibold */
  font-weight: 600;
  letter-spacing: -.01em;
  transition: transform .35s var(--ease), filter .35s var(--ease),
              background .35s var(--ease), border-color .35s var(--ease);
  white-space: nowrap;
}
.btn:active { transform: scale(.97); }

.btn--primary {
  /* FSP Button — brand gradient 600→500 */
  background: linear-gradient(90deg, #0342c6, #0348d9);
  border: 1px solid rgba(255, 255, 255, .1);
  color: #fff;
}
.btn--primary:hover { filter: brightness(1.18); transform: translateY(-1px); }

.btn--ghost {
  border: 1.5px solid currentColor;
  color: var(--ink-d1);
}
.theme-light .btn--ghost { color: var(--ink-l1); }
.btn--ghost:hover { transform: translateY(-1px); background: rgba(127,127,127,.09); }

.btn--mini { padding: 8px 14px; font-size: .875rem; line-height: 1.43; } /* Text sm/Semibold, FSP sm */

/* Label roll — the capsule stays still, the text rolls up on hover.
   Markup: <span class="roll"><b>Label</b><b aria-hidden="true">Label</b></span> */
.btn .roll { display: grid; overflow: hidden; }
.btn .roll b {
  grid-area: 1 / 1;
  font-weight: inherit;
  transition: transform .35s var(--ease);
  will-change: transform;
}
.btn .roll b:nth-child(2) { transform: translateY(115%); }
.btn:hover .roll b:first-child,
.btn:focus-visible .roll b:first-child { transform: translateY(-115%); }
.btn:hover .roll b:nth-child(2),
.btn:focus-visible .roll b:nth-child(2) { transform: translateY(0); }

.link-arrow {
  display: inline-block;
  position: relative;
  font-weight: 600;
  font-size: 1.05rem;
  color: #fff;
}
.theme-light .link-arrow { color: var(--ink-l1); }
/* underline is always on; hover renews it — it runs out the right edge and
   re-enters from the left (transform-origin swaps mid-keyframes) */
.link-arrow::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -.18em;
  height: 1.5px;
  background: currentColor;
  transform: scaleX(1);
}
.link-arrow:hover::after,
.link-arrow:focus-visible::after { animation: link-renew .91s var(--ease); }

@keyframes link-renew {
  0%   { transform: scaleX(1); transform-origin: right center; }
  49%  { transform: scaleX(0); transform-origin: right center; }
  50%  { transform: scaleX(0); transform-origin: left center; }
  100% { transform: scaleX(1); transform-origin: left center; }
}
.link-arrow--lg { margin-top: 1.8rem; }

/* ---------- Reveal-on-scroll ---------- */

.reveal {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity .9s var(--ease) var(--d, 0s),
              transform .9s var(--ease) var(--d, 0s);
}
.reveal.in { opacity: 1; transform: none; }

/* ==========================================================================
   LOCAL NAV
   ========================================================================== */

/* ==========================================================================
   0 · GLOBAL NAV — matches the CURRENT foresightsports.com header (user
   2026-08-14): logo left, uppercase dropdown nav, Specials + Contact Sales
   pills, search and cart. Sits in flow at the very top so it scrolls away;
   the fixed localnav starts just under it and rides up to the viewport top
   (offset in main.js). The slide-in drawer below remains the mobile menu.
   ========================================================================== */

.gnav {
  --gnav-h: 64px;
  position: relative;
  z-index: 101; /* above the localnav (100) while both are on screen */
  height: var(--gnav-h);
  background: #000;
  font-family: "Termina", "Roboto", var(--font);
}
.gnav__inner {
  height: 100%;
  display: flex;
  align-items: center;
  gap: clamp(16px, 2vw, 36px);
  padding-inline: max(3vw, calc((100vw - 1760px) / 2));
}
.gnav__brand { line-height: 0; flex: 0 0 auto; }
.gnav__brand img { display: block; height: 23px; width: auto; }
.gnav__nav {
  display: flex;
  align-items: center;
  gap: clamp(14px, 1.6vw, 30px);
  margin-inline: auto;
}
.gnav__item { position: relative; }
.gnav__link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 22px 2px; /* tall hit area bridges the hover gap to the dropdown */
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  white-space: nowrap;
  color: rgba(255, 255, 255, .85);
  transition: color .25s var(--ease);
}
.gnav__link:hover { color: #fff; }
.gnav__caret { width: 9px; height: 6px; opacity: .7; transition: transform .25s var(--ease); }
.gnav__item:hover .gnav__caret,
.gnav__item:focus-within .gnav__caret { transform: rotate(180deg); }
/* ---------- mega menus (2026-08-25) ----------
   Rebuilt to match foresightsports.com: a full-width strip under the bar
   rather than a floating card. Launch Monitors carries four product cards and
   a side column of links; every other tab is a centred row of links.
   The panel spans the header, not the item, so .gnav__item--mega goes static
   and the panel positions against .gnav itself. */
.gnav__item--mega { position: static; }
.gnav__mega {
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  z-index: 100;
  background: #111;
  border-top: 1px solid rgba(255, 255, 255, .06);
  opacity: 0;
  visibility: hidden;
  transition: opacity .2s var(--ease), visibility 0s linear .2s;
}
.gnav__item--mega:hover .gnav__mega,
.gnav__item--mega:focus-within .gnav__mega {
  opacity: 1;
  visibility: visible;
  transition: opacity .2s var(--ease);
}
.gnav__mega-inner {
  width: min(1152px, 100%);
  margin-inline: auto;
  padding: 3px 24px 5px;
}
.gnav__mega-inner--cards {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 220px;
  gap: 18px;
}
.gnav__cards {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 15px;
  padding-right: 32px;
  border-right: 1px solid rgba(255, 255, 255, .08);
}
.gnav__card {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 12px 0;
  color: #fff;
  text-decoration: none;
}
.gnav__card-media {
  display: flex;
  align-items: center;
  justify-content: center;
  /* clips rather than trusting the image to constrain itself: in a grid area
     sized by its own content, max-height:100% has nothing definite to resolve
     against and the renders spilled straight out of the panel */
  overflow: hidden;
  width: 100%;
  height: 130px;
  padding: 8px;
  border-radius: 14px;
  background: rgba(255, 255, 255, .06);
  transition: background .2s var(--ease);
}
.gnav__card:hover .gnav__card-media { background: rgba(255, 255, 255, .11); }
.gnav__card-media img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.gnav__card-title { margin-top: 10px; font-size: 16px; font-weight: 700; }
.gnav__card-sub { margin-top: 2px; font-size: 13px; color: #00b3fe; }
.gnav__mega-links {
  display: flex;
  justify-content: center;
  gap: 8px;
  flex-wrap: wrap;
}
.gnav__mega-links--side {
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-start;
  gap: 2px;
  padding-top: 8px;
}
.gnav__mega-links a {
  padding: 9px 6px;
  font-size: 15px;
  font-weight: 500;
  color: rgba(255, 255, 255, .7);
  text-decoration: none;
  transition: color .2s var(--ease);
}
.gnav__mega-links a:hover { color: #fff; }
.gnav__mega-links a[aria-current="page"] { color: #00b3fe; }
/* divides the FSX links from our own three pages in the Software panel */
.gnav__mega-sep {
  width: 1px;
  align-self: stretch;
  margin: 6px 10px;
  background: rgba(255, 255, 255, .14);
}
.gnav__mega-links--side .gnav__mega-sep { width: auto; height: 1px; align-self: stretch; margin: 6px 0; }

/* ---- the strips retire exactly where the bar does, at 1060px, where the
   burger drawer takes over. 1280 was wrong: between 1060 and 1280 the desktop
   nav is still on screen, so the tabs sat there with no menus at all. ---- */
@media (max-width: 1060px) {
  .gnav__mega { display: none; }
}

.gnav__dd {
  position: absolute;
  left: 50%;
  top: 100%;
  transform: translateX(-50%) translateY(6px);
  min-width: 230px;
  padding: 10px;
  border-radius: 14px;
  background: rgba(8, 8, 10, .96);
  border: 1px solid rgba(255, 255, 255, .1);
  box-shadow: 0 18px 50px rgba(0, 0, 0, .5);
  opacity: 0;
  visibility: hidden;
  transition: opacity .25s var(--ease), transform .25s var(--ease), visibility 0s linear .25s;
}
.gnav__item:hover .gnav__dd,
.gnav__item:focus-within .gnav__dd {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) translateY(0);
  transition: opacity .25s var(--ease), transform .25s var(--ease);
}
.gnav__dd a {
  display: block;
  padding: 9px 12px;
  border-radius: 8px;
  font-size: 13.5px;
  font-weight: 500;
  color: rgba(255, 255, 255, .8);
  transition: background .2s var(--ease), color .2s var(--ease);
}
.gnav__dd a:hover { background: rgba(255, 255, 255, .08); color: #fff; }
.gnav__dd a[aria-current="page"] { color: #00b3fe; }
.gnav__actions { display: flex; align-items: center; gap: 12px; flex: 0 0 auto; margin-left: auto; }
.gnav__pill {
  font-size: 13px;
  font-weight: 600;
  line-height: 1.2;
  white-space: nowrap;
  padding: 10px 18px;
  border-radius: 27px;
  color: #fff;
  transition: background .25s var(--ease), border-color .25s var(--ease);
}
/* Brand/500, not the old cyan (user 2026-08-25) — the same blue the primary
   CTA is built from, so the bar's one colour accent matches the buttons */
.gnav__pill--specials { background: var(--blue, #0348d9); }
.gnav__pill--specials:hover { background: #1257ea; }
.gnav__pill--ghost { border: 1.5px solid rgba(255, 255, 255, .85); }
.gnav__pill--ghost:hover { border-color: #fff; background: rgba(255, 255, 255, .08); }
.gnav__icon {
  position: relative;
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  color: #fff;
}
.gnav__icon svg { width: 20px; height: 20px; }
.gnav__burger {
  display: none; /* mobile only — the drawer takes over below 1280px */
  appearance: none;
  background: none;
  border: 0;
  cursor: pointer;
  flex-direction: column;
  gap: 5px;
  padding: 10px 2px;
}
.gnav__burger span { display: block; width: 22px; height: 2px; background: #fff; border-radius: 1px; }
.gnav__scrim {
  position: fixed;
  inset: 0;
  z-index: 190;
  background: rgba(0, 0, 0, .5);
  opacity: 0;
  visibility: hidden;
  transition: opacity .4s var(--ease), visibility 0s linear .4s;
}
/* right-hand drawer, same recipe as the live site's burger menu */
.gnav__menu {
  position: fixed;
  inset: 0 0 0 auto;
  z-index: 200;
  width: min(600px, 100vw);
  background: #000;
  padding: clamp(56px, 9vh, 84px) clamp(28px, 5vw, 60px) 48px;
  overflow-y: auto;
  transform: translateX(100%);
  visibility: hidden;
  transition: transform .5s var(--ease), visibility 0s linear .5s;
}
.gnav.menu-open .gnav__scrim { opacity: 1; visibility: visible; transition: opacity .4s var(--ease); }
.gnav.menu-open .gnav__menu { transform: none; visibility: visible; transition: transform .5s var(--ease); }
.gnav__close {
  position: absolute;
  top: 20px;
  right: 24px;
  appearance: none;
  background: none;
  border: 0;
  cursor: pointer;
  color: #fff;
  padding: 8px;
}
.gnav__close svg { width: 18px; height: 18px; display: block; }
.gnav__group { margin-bottom: clamp(28px, 4.5vh, 44px); }
.gnav__group-title {
  display: inline-block;
  font-family: "Termina", "Roboto", var(--font);
  font-size: clamp(26px, 3.2vw, 34px);
  font-weight: 700;
  letter-spacing: .01em;
  color: #fff;
  margin-bottom: 14px;
}
.gnav__group ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  column-gap: 28px;
  row-gap: 12px;
}
.gnav__group ul a { color: rgba(255, 255, 255, .6); font-size: 17px; transition: color .2s var(--ease); }
.gnav__group ul a:hover { color: #fff; }
.gnav__group ul a[aria-current="page"] { color: #00b3fe; }
.gnav__foot { display: flex; gap: 28px; margin-top: 8px; }
.gnav__foot a {
  color: #fff;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
}
.gnav__foot a:hover { color: #00b3fe; }
/* squeeze tier: keep the full nav alive on laptop widths before collapsing */
@media (max-width: 1380px) {
  .gnav__inner { gap: 14px; }
  .gnav__brand img { height: 20px; }
  .gnav__nav { gap: 11px; }
  .gnav__link { font-size: 11px; letter-spacing: .05em; }
  .gnav__pill { font-size: 12px; padding: 9px 14px; }
  .gnav__icon { width: 32px; }
  .gnav__actions { gap: 8px; }
}
@media (max-width: 1060px) {
  .gnav__nav,
  .gnav__pill--ghost { display: none; }
  .gnav__burger { display: inline-flex; }
}
@media (max-width: 640px) {
  .gnav__pill--specials,
  .gnav__icon--cart { display: none; }
  .gnav__brand img { height: 20px; }
}

.localnav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  background: rgba(8, 8, 10, .4);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 1px solid transparent;
  transition: background .4s var(--ease), border-color .4s var(--ease);
}
.localnav.scrolled {
  background: rgba(8, 8, 10, .72);
  border-color: rgba(255, 255, 255, .08);
}

.localnav__inner {
  width: min(1320px, 94vw); /* matches .container-wide */
  margin-inline: auto;
  height: var(--nav-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
}

.localnav__brand { display: flex; align-items: center; }
.localnav__brand img { height: 14px; width: auto; }

.localnav__links {
  position: relative;
  display: flex;
  gap: 28px;
  font-size: .875rem; /* Text sm/Semibold */
  font-weight: 600;
  line-height: 1.43;
  color: var(--ink-d2);
}
.localnav__links a { transition: color .3s; }
.localnav__links a:hover,
.localnav__links a.active { color: var(--ink-d1); }

.localnav__ind {
  position: absolute;
  top: calc(100% + 3px);
  left: 0;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--blue);
  opacity: 0;
  pointer-events: none;
  transition: transform .45s var(--ease), opacity .3s var(--ease);
  will-change: transform;
}
.localnav__ind.on { opacity: 1; }

.localnav__actions { display: flex; align-items: center; gap: 14px; }

.localnav__burger {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 34px;
  height: 34px;
  padding: 6px;
}
.localnav__burger span {
  display: block;
  height: 1.5px;
  background: var(--ink-d1);
  transition: transform .35s var(--ease), opacity .35s;
}
.menu-open .localnav__burger span:first-child { transform: translateY(3.25px) rotate(45deg); }
.menu-open .localnav__burger span:last-child  { transform: translateY(-3.25px) rotate(-45deg); }

.localnav__sheet {
  display: none;
  flex-direction: column;
  padding: 12px 4vw 26px;
  gap: 4px;
}
.localnav__sheet a {
  padding: 12px 4px;
  font-size: 1.125rem; /* Text lg/Semibold */
  font-weight: 600;
  color: var(--ink-d1);
  border-bottom: 1px solid rgba(255, 255, 255, .08);
}
.menu-open .localnav__sheet { display: flex; }
.menu-open { background: rgba(8, 8, 10, .96); }

/* ==========================================================================
   1 · HERO
   ========================================================================== */

.hero {
  padding: calc(var(--nav-h) + clamp(64px, 11vh, 120px)) 0 40px;
  text-align: center;
  position: relative;
  overflow: clip;
}
.hero::before {
  /* soft brand glow behind the stage */
  content: "";
  position: absolute;
  left: 50%;
  top: 46%;
  width: 1200px;
  height: 700px;
  transform: translateX(-50%);
  background: radial-gradient(closest-side, rgba(3, 72, 217, .18), transparent 70%);
  pointer-events: none;
  z-index: 1; /* glow above the dot field — same stack as the closer */
}
.hero__copy { position: relative; z-index: 2; }

/* Ambient dot-field canvas (js/dot-field.js), per Figma 11902:3701. The
   design's overlay is a black gradient born opaque at the bottom edge and
   gone by half height (transform resolves to exactly 2×), so the dots live
   in the top half and dissolve behind the copy. Over a black section a mask
   is visually identical to that overlay and keeps the glow stacking simple. */
.hero__shader {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
  -webkit-mask-image: linear-gradient(to top, transparent 0%, #000 50%);
          mask-image: linear-gradient(to top, transparent 0%, #000 50%);
}

.hero__kicker { display: flex; justify-content: center; }
.hero__kicker img { height: clamp(15px, 1.6vw, 21px); width: auto; }

.hero__title {
  /* Display 3xl/Bold — 96/120, −2% */
  font-size: clamp(3.2rem, 8.5vw, 6rem);
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: -.02em;
  margin-top: .5rem;
}

.hero__lede {
  /* Text xl — 20/30 */
  font-size: clamp(1.1rem, 1.4vw, 1.25rem);
  font-weight: 500;
  line-height: 1.5;
  letter-spacing: 0;
  color: var(--ink-d2);
  max-width: 64ch; /* two lines for the current copy (user 2026-08-12) */
  margin: 1.4rem auto 0;
}

.hero .cta-row { justify-content: center; }

/* --- Legend dots --- */

.dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--blue-light);
  display: inline-block;
  flex: none;
}
.dot--white { background: #f5f5f7; }
.dot--green { background: var(--blue-light); }

/* ==========================================================================
   1b+2 · VIDEO → PRACTICE, PLAY, COMPETE (texto calado, per Figma 11843:2669)
   Coreografía 100% scroll-nativa: el pin del video y el panel calado son DOS
   stickies hermanos. El panel (slab negro con la tipografía calada vía SVG
   <mask> inline — un data-URI no puede usar la fuente del documento) entra
   1:1 con el scroll sobre el video clavado, dockea arriba y ambos quedan
   ~60vh con el video corriendo dentro de las letras. Al asomar el panel, el
   frame escala a full-bleed por detrás (solo transform, GPU) para que las
   letras siempre calen sobre video.
   ========================================================================== */

.vt {
  height: 215vh; /* 100vh pin + 100vh panel travel + 15vh hold.
                    HARD FLOOR IS 200vh: below that the panel never reaches
                    top:0, `is-docked` never fires and the white sub never
                    appears. The old 60vh hold read as the page freezing at
                    the end of the section (user 2026-08-03). */
  background: var(--black);
  position: relative;
}

/* Hero over the video (user 2026-08-03, same treatment as Foresight+): the
   standalone hero section is gone — the copy sits on the full-bleed video
   under a light black scrim, and the calado panel is the next beat. The frame
   is born full-bleed, so the card entrance / is-fill growth is neutralised. */
.vt--hero .vt__frame {
  width: 100vw;
  height: 100svh;
  aspect-ratio: auto;
  max-height: none;
  min-height: 0;
  border-radius: 0;
  transition: none;
}
.vt--hero.is-fill .vt__frame { transform: none; }

.vt__hero {
  position: absolute;
  inset: 0;
  z-index: 1; /* above the frame (z-auto), below the panel (sticky z2) */
  display: grid;
  place-items: center;
  text-align: center;
  background: rgba(0, 0, 0, .35); /* light black scrim over the footage */
  pointer-events: none;
  transition: opacity .45s var(--ease);
}
.vt__hero .container { pointer-events: auto; }
.vt__hero .cta-row { justify-content: center; }
/* gone before the panel's letter zone crosses the copy — otherwise the white
   text would read through the calado letters */
.vt.is-rising .vt__hero { opacity: 0; }

.vt__pin {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  display: grid;
  place-items: start center; /* el frame pega a la sección de arriba */
  overflow: clip;
}

.vt__frame {
  position: relative;
  width: min(1360px, 95.5vw); /* ya grande por default — no hay fase de growth */
  aspect-ratio: 1312 / 738;
  max-height: 92svh;          /* ratio+max-height: el ancho queda, cover absorbe */
  min-height: 84svh;          /* viewports angostos: sin esto el 16:9 queda corto
                                 y las letras caladas caen sobre negro */
  background: var(--card-dark);
  border-radius: 28px;
  overflow: hidden;
  transform-origin: 50% 0;
  transition: transform 1.1s var(--ease), border-radius 1.1s var(--ease);
  will-change: transform;
}
.vt.is-fill .vt__frame {
  transform: scale(var(--fs, 1.2)); /* --fs = max(vw/frameW, vh/frameH), via JS */
  border-radius: 0;
}

.vt__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  animation: vt-drift 16s ease-in-out infinite alternate; /* Ken Burns en las letras */
}
@keyframes vt-drift {
  from { transform: scale(1); }
  to   { transform: scale(1.06); }
}

/* Panel negro con la tipografía calada — sticky, sube en flujo normal */
.vt__panel {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  z-index: 2;
  /* the maskart enters at scale(1.035), so it pokes ~3.5% past both sides and
     that became document scroll width (the mobile sideways-swipe). The panel
     is exactly viewport-wide, so clipping here costs nothing visually — the
     bleed was always meant to sit off-screen. */
  overflow: hidden;
}
@supports (overflow: clip) {
  /* clip keeps the panel from becoming a scroll container */
  .vt__panel { overflow: clip; }
}

.vt__maskart {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  transform: scale(1.035);     /* micro-settle: entra 3.5% grande y asienta */
  transform-origin: 50% 41%;   /* centro óptico del bloque de texto */
  transition: transform .9s var(--ease);
}
.vt.is-docked .vt__maskart { transform: none; }

.vt__masktext {
  font-family: var(--font);
  font-weight: 900; /* Black, per Figma */
  font-size: 141.068px;
  letter-spacing: -2.8214px; /* −2% per Figma 11843:2653 */
  /* The tspan y's are fixed attributes, so the gap/size relationship (125.3 /
     141.068 = .89) only holds at THIS font size — shrinking the font alone
     stretched the lines apart. Fit narrow screens by scaling the whole block
     instead: --ms is measured in main.js so the widest line always lands
     inside the screen, and the proportions stay exactly as designed. */
  transform: scale(var(--ms, 1));
  transform-box: view-box;
  transform-origin: 720px 331px; /* centre of the three-line block */
}

/* White-10% veil between the video and the black calado (user 2026-07-31):
   lifts the footage read through the letters so it separates from the #000
   mask. Fades in with .is-rising so the takeover phase keeps the clean video. */
.vt__veil {
  position: absolute;
  inset: 0;
  background: rgba(255, 255, 255, .1); /* alpha-white-10 */
  opacity: 0;
  transition: opacity .8s var(--ease);
  pointer-events: none;
  z-index: 1; /* over the video, under the media toggle (z2) */
}
.vt.is-rising .vt__veil { opacity: 1; }

/* El sub viaja EN el panel y entra CASI JUNTO al calado (user 2026-07-31):
   .is-rising se prende cuando el panel escaló la mitad del viewport, no al
   dockear — .is-docked queda para el settle del masktext. */
.vt__sub {
  position: absolute;
  left: 50%;
  top: 74.5%;
  transform: translate(-50%, 14px);
  width: min(700px, 86vw);
  text-align: center;
  font-size: clamp(1.05rem, 1.67vw, 1.5rem); /* Display xs/Semibold */
  font-weight: 584;
  line-height: 1.34;
  color: #fff;
  opacity: 0;
  transition: opacity .6s var(--ease) .18s, transform .6s var(--ease) .18s;
}
.vt.is-rising .vt__sub {
  opacity: 1;
  transform: translate(-50%, 0);
}

/* Media play/pause toggle */
.media-toggle {
  position: absolute;
  top: 50%;
  right: 20px;
  transform: translateY(-50%);
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: rgba(210, 210, 215, .64);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  display: grid;
  place-items: center;
  z-index: 2;
  transition: background .3s var(--ease), transform .3s var(--ease);
}
.media-toggle:hover { background: rgba(223, 223, 227, .72); }
.media-toggle:active { transform: translateY(-50%) scale(.94); }
.media-toggle svg { width: 36px; height: 36px; fill: #1d1d1f; }
.media-toggle .media-toggle__play { display: none; }
.media-toggle.is-paused .media-toggle__pause { display: none; }
.media-toggle.is-paused .media-toggle__play { display: block; }

/* ==========================================================================
   4b2 · FSX COURSE MARQUEE (per Figma 11755:90740)
   Each course card is Figma-s own 2x render (551×600 PNG, transparent
   corners) — nothing is rebuilt in HTML. Desktop card = 275.217px wide with a
   19.355px gap (ratio .070325), per the updated node. The belt loops forever
   in JS (rAF) and speeds up while the user scrolls; see main.js.
   ========================================================================== */

.fsx {
  --cw: clamp(216px, 66vw, 275.217px);
  --gap: calc(var(--cw) * .070325);
  padding: 96px 0 clamp(96px, 12vh, 160px);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 48px;
}

/* clip solo en X: el arc wheel (main.js) baja las cards de los bordes ~60px
   y un clip vertical las cortaría recto */
.fsx__viewport { width: 100%; overflow-x: clip; overflow-y: visible; }

.fsx__track {
  display: flex;
  gap: var(--gap);
  width: max-content;
  padding-inline: var(--gap);
}

.fsx-card { flex: none; width: var(--cw); will-change: transform; }
.fsx-card img { width: 100%; height: auto; display: block; }

/* CTA — was sized to Figma's FSP Button (16/10); now it rides the default
   .btn geometry so it matches the "Explore Foresight+" capsule exactly
   (user 2026-09-01) */

/* ==========================================================================
   4b3 · COURSE SHOWCASE (per Figma 11784:92145)
   Cash-App-style pinned media. The sticky stage holds one 958×539 r24 frame
   (x 634 on the 1440 canvas — it bleeds ~152px off the right edge) while the
   alternating black/white panels scroll behind it. Images stack inside the
   frame; layers 2+ are revealed with clip-path driven by their panel's top
   edge (main.js), so the section boundary doubles as the wipe line.
   ========================================================================== */

.showcase { position: relative; }

/* Full-viewport sticky stage; the panels wrapper's negative top margin lays
   the panels underneath it. The negative margin must live on the WRAPPER —
   on the sticky element itself it zeroes the sticky constraint height and
   the frame stays pinned into the next section. With it here, the stage
   unpins exactly when one viewport of section remains, so the frame rides
   out together with panel 3. */
.showcase__stage {
  position: sticky;
  top: 0;
  height: 100svh;
  z-index: 2;
  pointer-events: none;
  /* the frame is anchored at right:-8.5vw so ~16% of the card runs off-screen
     by design — clip it here instead of leaning on body's overflow-x, which
     iOS ignores (that's what let the page swipe sideways in production) */
  overflow: hidden;
}
@supports (overflow: clip) {
  .showcase__stage { overflow: clip; }
}
.showcase__panels { margin-top: -100svh; }

.showcase__frame {
  position: absolute;
  right: -8.5vw; /* keeps the design's crop: ~16% of the card runs off-edge */
  top: 50%;
  translate: 0 -50%;
  width: min(53.2vw, 108svh); /* design size −20%; height ≤ 61svh via ratio */
  aspect-ratio: 958 / 539;
  border-radius: 24px;
  overflow: hidden;
  background: var(--elev);
}
/* Split layers: the wrapper carries the wipe clip (scrubbed in main.js), the
   par shell carries the scroll parallax, and the img carries the Ken Burns
   drift. All three must stay separate — a transform on the clipped element
   drags the wipe line, and the drift would fight the parallax offset. */
.showcase__layer {
  position: absolute;
  inset: 0;
}
/* Overhangs the frame by --par-over top and bottom, so the image has room to
   drift against the scroll without ever exposing an edge — an oversized
   plate inside a clipped window. */
.showcase__par {
  position: absolute;
  inset: calc(var(--par-over) * -1) 0;
}
.showcase__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  will-change: transform;
  animation: showcase-drift 16s ease-in-out infinite alternate;
}
/* out of phase per layer so the panels never breathe in sync */
.showcase__layer:nth-child(2) .showcase__img { animation-delay: -6s; animation-direction: alternate-reverse; }
.showcase__layer:nth-child(3) .showcase__img { animation-delay: -11s; }

/* slow zoom + diagonal drift; starts pre-scaled so edges never show */
@keyframes showcase-drift {
  from { transform: scale(1.06) translate3d(1.6%, -1%, 0); }
  to   { transform: scale(1.15) translate3d(-1.8%, 1.3%, 0); }
}

/* layers 2+ start hidden; main.js scrubs the inset down to 0 as their panel
   edge sweeps up across the frame */
.showcase__layer + .showcase__layer { clip-path: inset(100% 0 0 0); }

.showcase__panel {
  min-height: 100svh;
  display: flex;
  align-items: center;
  background: var(--black);
  color: var(--ink-d1);
}
.showcase__panel--light {
  background: var(--white);
  color: #06090f;
}

.showcase__inner { width: min(1320px, 94vw); margin-inline: auto; }

/* per-panel stills: mobile-only replacement for the pinned frame */
.showcase__still { display: none; }

.showcase__copy {
  width: min(428px, 38vw); /* stays clear of the frame at every desktop width */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 24px;
}

/* pill tag — Text sm/Medium under the library's soft top-light */
.showcase__tag {
  padding: 9px 14px;
  border-radius: 28px;
  font-size: .875rem;
  font-weight: 492;
  line-height: 1.43;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: rgba(250, 250, 250, .6);
  background:
    radial-gradient(56% 90% at 50% 0%, rgba(185, 192, 204, .22), rgba(138, 148, 165, .04) 40%, rgba(138, 148, 165, 0) 70%),
    rgba(0, 0, 0, .2);
  border: 1px solid rgba(232, 236, 243, .08);
}
.showcase__panel--light .showcase__tag {
  color: rgba(6, 9, 15, .6);
  background: radial-gradient(56% 90% at 50% 0%, rgba(185, 192, 204, .3), rgba(138, 148, 165, .06) 40%, rgba(138, 148, 165, 0) 70%);
  border-color: rgba(6, 9, 15, .08);
}

.showcase__title {
  /* Display lg/Bold — 48/50, −2% */
  font-size: clamp(2.1rem, 3.34vw, 3rem);
  font-weight: 700;
  line-height: 1.042;
  letter-spacing: -.02em;
}
.showcase__text {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 7px; /* 24 + 7 = Figma's 31 title→text */
}
.showcase__sub {
  font-size: 1.25rem; /* Text xl/Semibold — 20/30 */
  font-weight: 584;
  line-height: 1.5;
}
.showcase__body {
  font-size: 1.125rem; /* Text lg/Medium — 18/28 */
  font-weight: 492;
  line-height: 1.556;
  color: rgba(250, 250, 250, .6);
}
.showcase__panel--light .showcase__body { color: rgba(6, 9, 15, .6); }

@media (max-width: 820px) {
  /* The pinned stage + boundary wipe is a desktop pattern. On mobile each
     panel carries its own still IN FLOW, sitting right above its tag — fixed
     to the section, not the device (user call, 2026-07-30). */
  .showcase__stage { display: none; }
  .showcase__panels { margin-top: 0; } /* no stage to overlap anymore */
  /* The still deliberately bleeds 7vw past the right edge (3vw offset +
     104vw). Nothing was cutting it, so it became the document's scroll width
     and the page swiped sideways on mobile — `body { overflow-x: clip }` masks
     this in Chrome but iOS ignores it. The panel is exactly viewport-wide, so
     clipping HERE kills the overflow at its source and keeps the crop. */
  .showcase__panel { overflow-x: hidden; }
  @supports (overflow: clip) {
    /* clip doesn't create a scroll container the way hidden does */
    .showcase__panel { overflow-x: clip; }
  }
  .showcase__still {
    display: block;
    /* left edge on the text margin, right side crops off-screen */
    width: 104vw;
    max-width: none; /* the global img reset caps width at 100% */
    height: auto;    /* the height attr would otherwise pin 1078px */
    aspect-ratio: 958 / 539;
    object-fit: cover;
    border-radius: 24px;
    margin-bottom: 28px;
  }
  .showcase__copy { width: 100%; max-width: 520px; }
}

/* --- 4K COURSES — SHADER SHOWCASE (user 2026-08-03 v3). One block on the
   section black (same as the modules around it): a single rounded frame whose
   imagery lives on a WebGL canvas — switching tabs runs a noise-melt shader
   transition (main.js) instead of moving cards. The <img> fallback only shows
   when WebGL is unavailable (.ctabs--nogl). Replaces the pinned scroll story
   above in the markup (those rules stay for reference, same as the legacy
   bento). --- */

.ctabs {
  padding: clamp(40px, 7vh, 90px) 0;
  background: var(--black);
}
.ctabs__frame {
  position: relative;
  width: min(1680px, 94vw);
  margin-inline: auto;
  height: clamp(480px, 82svh, 880px);
  border-radius: 28px;
  overflow: hidden;
  background: var(--card-dark);
}
.ctabs__gl,
.ctabs__fallback {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
}
.ctabs__fallback { display: none; }
.ctabs--nogl .ctabs__gl { display: none; }
.ctabs--nogl .ctabs__fallback { display: block; transition: opacity .3s var(--ease); }
.ctabs__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(0, 0, 0, .26), rgba(0, 0, 0, 0) 34%, rgba(0, 0, 0, 0) 62%, rgba(0, 0, 0, .44) 100%);
}
.ctabs__mark {
  position: absolute;
  top: clamp(24px, 4.5svh, 52px);
  left: clamp(24px, 3.4vw, 64px);
  max-width: 7ch; /* wraps to "4K / Remastered" like the comp */
  font-size: clamp(1.9rem, 2.9vw, 3rem);
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: -.02em;
  color: #fff;
}
.ctabs__tabs {
  position: absolute;
  top: clamp(20px, 4svh, 44px);
  right: clamp(20px, 2vw, 40px);
  z-index: 2;
  display: inline-flex;
  gap: 4px;
  padding: 4px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .3);
  -webkit-backdrop-filter: blur(18px) saturate(1.4);
  backdrop-filter: blur(18px) saturate(1.4);
}
/* the white chip travels between tabs instead of swapping, same recipe as the
   Foresight+ tiers toggle (user 2026-09-01): JS measures the active tab and
   feeds --tx/--tw, so the unequal label widths still land on the button box */
.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__tabs.is-measured .ctabs__thumb { opacity: 1; }
.ctabs__tabs:not(.is-measured) .ctabs__thumb { transition: none; }
.ctabs__tab {
  appearance: none;
  position: relative;
  z-index: 1;
  border: 0;
  background: transparent;
  color: rgba(22, 24, 28, .62);
  font-family: var(--font);
  font-size: 15px;
  font-weight: 584;
  line-height: 1;
  letter-spacing: -.01em;
  padding: 12px 24px;
  border-radius: 999px;
  cursor: pointer;
  transition: color .3s var(--ease);
}
.ctabs__tab:hover:not(.is-active) { color: rgba(22, 24, 28, .92); }
.ctabs__tab.is-active { color: #000; }
/* fallback: no JS, no measure — the active tab paints its own chip */
.ctabs__tabs:not(.is-measured) .ctabs__tab.is-active { background: #fff; }
@media (prefers-reduced-motion: reduce) {
  .ctabs__thumb { transition: none; }
}
.ctabs__copy {
  position: absolute;
  left: clamp(24px, 3.4vw, 64px);
  bottom: clamp(24px, 5svh, 56px);
  max-width: 620px;
  padding-left: clamp(18px, 1.8vw, 34px);
  border-left: 3px solid rgba(255, 255, 255, .92);
  /* stacked panels: outgoing copy sinks + fades, incoming rises in after the
     shader melt is mostly through (the .45s delay) */
  opacity: 0;
  transform: translateY(14px);
  visibility: hidden;
  pointer-events: none;
  transition: opacity .4s var(--ease), transform .4s var(--ease), visibility 0s linear .4s;
}
.ctabs__copy.is-active {
  opacity: 1;
  transform: none;
  visibility: visible;
  pointer-events: auto;
  transition: opacity .5s var(--ease) .45s, transform .5s var(--ease) .45s;
}
.ctabs__title {
  font-size: clamp(1.7rem, 2.5vw, 2.5rem);
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: -.02em;
  color: #fff;
}
.ctabs__body {
  margin-top: 10px;
  max-width: 480px;
  font-size: 1.0625rem; /* Text lg/Medium step down */
  font-weight: 492;
  line-height: 1.55;
  color: rgba(255, 255, 255, .8);
}

@media (max-width: 820px) {
  .ctabs__frame { width: 90vw; height: min(72svh, 620px); }
  .ctabs__mark { font-size: 1.5rem; }
  /* no room next to the mark on phones — the control drops to the bottom of
     the frame (it clips overflow, so it can't hang below) and the copy lifts
     out of its way */
  /* full-width control on phones (user 2026-08-26): the pill spans the frame
     to the same inset the copy uses, and the three tabs share the row evenly
     rather than each hugging its own label. flex-basis 0 is what makes them
     equal — basis auto would size them by text and leave "Parkland" widest. */
  .ctabs__tabs {
    top: auto;
    left: clamp(24px, 3.4vw, 64px);
    right: clamp(24px, 3.4vw, 64px);
    bottom: 18px;
    transform: none;
    display: flex;
  }
  .ctabs__tab {
    flex: 1 1 0;
    min-width: 0;
    padding: 11px 8px;
    font-size: 13.5px;
    text-align: center;
  }
  /* the rule and its indent cost ~37px of a 90vw frame, which the copy
     needs more than the flourish (user 2026-08-26) */
  .ctabs__copy { bottom: 84px; border-left: 0; padding-left: 0; }
  .ctabs__body { font-size: .9375rem; }
}

/* --- SEE EVERY SHOT — deck layout (user 2026-08-04, Figma 885:2031).
   Replaces the light analysis carousel: two-tone display at the deck ratio
   (15vw / .8 leading / −5%), centered driver art, lede mid-left, three
   blocks along the bottom. Chassis shared with the Mobile page's deck
   sections. --- */

.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__art-center {
  position: absolute;
  left: 50%;
  top: 46%;
  transform: translate(-50%, -50%);
  width: min(64vw, 1180px);
  height: auto;
  z-index: 0;
}
.deck__frame {
  position: relative;
  z-index: 1;
  width: min(1840px, 94vw);
  margin-inline: auto;
  display: flex;
  flex-direction: column;
}
.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-indent { margin-left: .9em; }
/* per the deck: "shot" rides right of center, tucked under "every" */
.deck--ses .deck__display-indent { display: inline-block; margin-left: 52%; }
.deck__lede {
  margin-top: clamp(48px, 14vh, 160px);
  font-size: clamp(1.35rem, 2vw, 2.25rem);
  font-weight: 584;
  line-height: 1.3;
  color: #fff;
  max-width: 20ch;
}
.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);
  font-weight: 584;
  line-height: 1.3;
  color: #fff;
}
.deck__block p {
  margin-top: 10px;
  font-size: clamp(.9375rem, 1.25vw, 1.1875rem);
  line-height: 1.5;
  color: rgba(255, 255, 255, .6);
  max-width: 44ch;
}
@media (max-width: 820px) {
  .deck { min-height: 0; }
  .deck__display { font-size: clamp(3.2rem, 17vw, 5rem); }
  .deck__display-indent { margin-left: .5em; }
  .deck__art-center {
    position: relative;
    left: auto;
    top: auto;
    transform: none;
    width: 100%;
    margin: 24px 0 0;
  }
  .deck--ses .deck__lede { margin-top: 18px; }
  .deck__blocks { grid-template-columns: 1fr; gap: 24px; padding-top: 32px; }
}

/* ==========================================================================
   CAROUSEL MACHINERY (partly live since 2026-07-30)
   `.paddle` is now used by the analysis gallery (`.ag`), whose stepping
   lives in main.js. The `.gallery` / `.gallery-wrap` scroll-snap row below
   is still dormant (the old 4c course slider); reviving it needs its own
   wiring — main.js now targets `.ag__scroller`, not `.gallery`.
   ========================================================================== */

.gallery-wrap { margin-top: clamp(48px, 7vh, 90px); padding-bottom: clamp(90px, 12vh, 150px); }

.gallery {
  display: flex;
  gap: clamp(20px, 2.8vw, 40px);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: max(4vw, calc((100vw - 1320px) / 2));
  scrollbar-width: none;
  padding: 6px max(4vw, calc((100vw - 1320px) / 2)) 26px;
}
.gallery::-webkit-scrollbar { display: none; }

.gallery-paddles {
  display: flex;
  justify-content: flex-end;
  gap: 12px;
}
.paddle {
  /* media-control style */
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: rgba(210, 210, 215, .64);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  color: #1d1d1f;
  display: grid;
  place-items: center;
  transition: background .3s, opacity .3s, transform .3s var(--ease);
}
.paddle svg { width: 36px; height: 36px; transition: transform .35s var(--ease); }
.paddle:hover:not(:disabled) { background: rgba(223, 223, 227, .72); }
/* the chevron peeks 2px in its travel direction */
.paddle:hover:not(:disabled) svg { transform: translateX(2px); }
.paddle[data-dir="-1"]:hover:not(:disabled) svg { transform: translateX(-2px); }
.paddle:active:not(:disabled) { transform: scale(.94); }
.paddle:disabled { opacity: .36; cursor: default; }

/* ==========================================================================
   5 · REALISM SPLIT
   ========================================================================== */

/* Full-bleed video background with the copy over it (per Figma 11753:6362 —
   video plus a flat rgba(0,0,0,.2) scrim). The scrubbed split graphic is gone,
   so the section no longer needs the sticky pin. */
.realism {
  position: relative;
  isolation: isolate;
  overflow: clip;
  display: flex;
  align-items: center;
  min-height: min(720px, 86vh);
  padding: clamp(64px, 10vh, 120px) 0;
}

/* Expanding mask, shared by every [data-clip] plate (the realism video and the
   venues backdrop): one progress value drives BOTH the side inset and the
   corner radius, so the window widens and squares off in a single move.
   --clip runs 1 → 0 (windowed → full-bleed), scrubbed in main.js. The inset
   is horizontal only — the band's own height frames it. */
[data-clip] {
  --clip: 1;
  clip-path: inset(0 calc(var(--clip) * 6.25vw) round calc(var(--clip) * 28px));
  will-change: clip-path;
}
.realism__media {
  position: absolute;
  inset: 0;
  z-index: -1;
}

.realism__video {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.realism__scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  /* Figma specifies a flat rgba(0,0,0,.2), but its mock used a much darker frame
     than this footage: over the bright grass the gray lede fell to 4.4:1. Raised
     to .35 and the lede is white here, which puts both well past AA. */
  background: rgba(0, 0, 0, .35);
}

.realism__head { text-align: center; }
.realism__head .lede {
  max-width: 48ch;
  margin-inline: auto;
  font-weight: 600;
  color: rgba(255, 255, 255, .85);
}

/* ==========================================================================
   6 · ANALYSIS BENTO
   ========================================================================== */

.analysis { padding: clamp(96px, 14vh, 170px) 0; }
/* Left-aligned head with the paddles on the right (user 2026-08-03; was a
   centered stack with the paddles under the row) */
.analysis__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  text-align: left;
}
/* .eyebrow is a centering flex row globally — pin it left here */
.analysis__head .eyebrow { justify-content: flex-start; }

/* --- Analysis gallery — caption-tile carousel.
   Card 372×450 (344×416 ≤1068), radius 28, gap 20,
   caption 17/600 below the tile, full-bleed snap row starting at the
   content inset. ≤734 stacks the tiles (user request). Gray card blocks
   are placeholders until the real imagery lands. --- */

.ag {
  /* row starts exactly on the header's text edge: same math as .container
     (min(1080px, 92vw) centered), user 2026-08-03 */
  --ag-inset: max(4vw, calc((100vw - 1080px) / 2));
  --ag-card-w: 372px;
  --ag-card-h: 450px;
  --ag-gap: 20px;
  margin-top: clamp(44px, 6vh, 72px);
}
.ag__scroller {
  overflow-x: scroll;
  scrollbar-width: none;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: var(--ag-inset);
  /* breathing room so nothing clips, mirrored away */
  padding: 10px 0 14px;
  margin: -10px 0 -14px;
}
.ag__scroller::-webkit-scrollbar { display: none; }
/* mouse-drag support (main.js adds the class when it wires the handlers) */
/* mandatory snap re-snaps on every frame of a drag (the stutter) — off while
   the pointer is down; main.js settles on a card before restoring it */
.ag__scroller.is-dragging { scroll-snap-type: none; }
.ag__scroller--draggable { cursor: grab; }
.ag__scroller--draggable:active { cursor: grabbing; }
.ag__scroller--draggable .ag-tile { user-select: none; }
.ag__scroller--draggable .ag-tile img { -webkit-user-drag: none; }

.ag__set {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: var(--ag-card-w);
  gap: 0 var(--ag-gap);
  width: fit-content;
  padding-inline: var(--ag-inset);
  margin: 0;
}

.ag-tile { display: flex; flex-direction: column; scroll-snap-align: start; }
.ag-tile:last-child { scroll-snap-align: end; }

/* the media card — Figma 2x exports; gray shows only while loading */
.ag-tile__card {
  order: 1;
  height: var(--ag-card-h);
  border-radius: 28px;
  background: #e3e3e8;
  overflow: hidden;
}
.ag-tile__card img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
}

/* caption under the card: title on top, copy below, flush with the card
   edge (user 2026-07-30 — no side inset) */
.ag-tile__caption {
  order: 2;
  padding: 28px 0 0;
  font-size: 17px;
  line-height: 1.2353;
  font-weight: 600;
  letter-spacing: -.022em;
  color: var(--ink-l2);
}
.ag-tile__caption h3 { display: block; font: inherit; color: var(--ink-l1); }
.ag-tile__caption p  { display: block; margin: .35em 0 0; }

.ag__paddles {
  display: flex;
  gap: 12px;
  flex-shrink: 0;
  margin-bottom: 6px; /* optical alignment with the lede baseline */
}

@media (max-width: 1068px) {
  .ag { --ag-card-w: 344px; --ag-card-h: 416px; }
}
@media (max-width: 734px) {
  /* mobile: stacked, one tile under the other, full image (no crop);
     4vw padding = the .container gutter, so tiles align with the header */
  .ag__scroller { overflow-x: visible; padding: 0 4vw; margin: 0; }
  .ag__set {
    grid-auto-flow: row;
    grid-auto-columns: unset;
    gap: 40px 0;
    width: auto;
  }
  .ag-tile__card { width: 100%; height: auto; }
  .ag-tile__card img { height: auto; }
  /* Text md/Semibold (16/24) — the caption reads too small at 14 on a phone */
  .ag-tile__caption { padding-top: 24px; font-size: 16px; line-height: 1.5; letter-spacing: -.016em; }
  .ag__paddles { display: none; }
}

/* Desktop scroll-driven mode: main.js adds .analysis--pin (≥735, motion OK),
   sizes --ag-extra to the row's overflow, and scrubs scrollLeft against the
   pin. Without JS or with reduced motion the class never lands and the
   carousel stays free-scrolling. */
.analysis--pin {
  /* the user's "3×" air lives OUTSIDE the pinned frame, as white entry/exit
     stretches around it — cramming it INSIDE the 100svh pin shrank the tiles
     until the whole row fit and the carousel had nothing to scroll */
  --ag-air-t: clamp(120px, 15svh, 210px);
  --ag-air-b: clamp(96px, 12svh, 170px);
  padding: 0;
  height: calc(100svh + var(--ag-extra, 820px) + var(--ag-air-t) + var(--ag-air-b));
  /* BFC: without this the pin's --ag-air margins collapse through the
     section edge and render as a BLACK band over <main> between the realism
     video and this white section (the "video cortado" bug) */
  display: flow-root;
}
.analysis--pin .analysis__pin {
  position: sticky;
  top: 0;
  height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* compact breathing room inside the pinned frame */
  padding: max(5svh, 40px) 0 max(4svh, 32px);
  margin-top: var(--ag-air-t);
  margin-bottom: var(--ag-air-b);
}
.analysis--pin .ag__scroller { scroll-snap-type: none; }

/* pinned mode fits ONE viewport: the tile scales with svh (same 372:450
   ratio as the 744×900 exports, so cover never crops) and the vertical
   chrome compresses on short screens */
.analysis--pin .ag {
  --ag-card-h: min(450px, 54svh);
  --ag-card-w: calc(min(450px, 54svh) * .82667);
  margin-top: clamp(16px, 3.5svh, 56px);
}
/* pinned: the paddles stay visible in the head (user 2026-08-03, reversing
   2026-07-30) — clicking one scrolls the PAGE a card's worth, which the scrub
   translates into row travel, so arrows and scroll stay in lockstep */
.analysis--pin .ag-tile__caption {
  padding-top: clamp(14px, 2.6svh, 28px);
  /* type follows the tile so long captions stop forcing tiny cards:
     17px at the full 372 width, floor 13.5px */
  font-size: clamp(13.5px, calc(var(--ag-card-w) / 22), 17px);
}
/* (short-viewport residue is trimmed by the JS fit pass in sizePin —
   it measures real overflow instead of guessing with svh tiers) */

.analysis .footnote { margin-top: clamp(44px, 6vh, 64px); margin-inline: auto; text-align: center; }

/* --- LEGACY BENTO (markup now lives only in analysis-lab.html as reference;
   these rules stay so the lab keeps rendering the old SVG mocks) --- */

.bento {
  margin-top: clamp(40px, 6vh, 70px);
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 18px;
}

.bento-card {
  background: var(--white);
  border-radius: var(--r-card);
  padding: clamp(24px, 2.6vw, 34px);
  display: flex;
  flex-direction: column;
  gap: 22px;
  box-shadow: 0 4px 20px rgba(0, 0, 0, .05);
}
.bento-card--7 { grid-column: span 6; }
.bento-card--5 { grid-column: span 6; }
.bento-card--4 { grid-column: span 4; }

.bento-card h3 {
  font-size: 1.4rem;
  font-weight: 700;
  letter-spacing: -.017em;
  margin-bottom: .45rem;
}
.bento-card header p { color: var(--ink-l2); font-size: .99rem; max-width: 46ch; }

.panel {
  background: #0d0d10;
  border-radius: var(--r-panel);
  padding: 18px;
  margin-top: auto;
  color: var(--ink-d1);
}
.panel--center { display: flex; flex-direction: column; align-items: center; gap: 10px; }

/* Data tiles */
.tiles {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 10px;
}
.tile {
  background: #17171b;
  border-radius: 12px;
  padding: 12px 12px 10px;
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}
.tile span {
  font-size: .56rem;
  letter-spacing: .11em;
  text-transform: uppercase;
  color: var(--ink-d3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.tile b {
  font-family: var(--mono);
  font-size: clamp(.95rem, 1.6vw, 1.3rem);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.tile i { font-style: normal; font-size: .6rem; color: var(--ink-d2); text-transform: uppercase; letter-spacing: .08em; }

/* Dispersion + face */
.disp, .face { width: 100%; height: auto; }
.legend {
  display: flex;
  gap: 18px;
  font-family: var(--mono);
  font-size: .66rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-d2);
}
.legend span { display: inline-flex; align-items: center; gap: 7px; }
.legend--chips { flex-wrap: wrap; gap: 8px; }
.chip-sm {
  padding: 5px 10px;
  border-radius: 8px;
  background: #1b1b20;
  border: 1px solid rgba(255, 255, 255, .1);
  font-size: .62rem;
  white-space: nowrap;
}

/* Tags */
.tagcloud { display: flex; flex-wrap: wrap; gap: 9px; margin-top: auto; }
.tag {
  padding: 8px 15px;
  border-radius: 980px;
  border: 1px solid var(--line-l);
  font-size: .86rem;
  font-weight: 600;
  color: var(--ink-l2);
  background: var(--white);
}
.tag--on {
  background: #041030;
  border-color: #041030;
  color: #7fb2ff;
}
.tag--ghost { border-style: dashed; color: rgba(29, 29, 31, .45); }

/* MyBag */
.bag { display: flex; flex-direction: column; gap: 9px; }
.bag__row {
  display: grid;
  grid-template-columns: 30px 1fr 40px;
  align-items: center;
  gap: 12px;
  font-family: var(--mono);
  font-size: .78rem;
}
.bag__row span { color: var(--ink-d3); text-transform: uppercase; }
.bag__row b { font-variant-numeric: tabular-nums; text-align: right; font-weight: 600; }
.bag__bar { height: 7px; border-radius: 4px; background: #202026; overflow: hidden; }
.bag__bar i {
  display: block;
  height: 100%;
  width: var(--w);
  border-radius: 4px;
  background: linear-gradient(90deg, #1e6dff, var(--blue-light));
}
.bag__gap {
  justify-self: start;
  margin-left: 42px;
  padding: 3px 9px;
  border-radius: 6px;
  border: 1px dashed rgba(255, 214, 10, .5);
  color: #ffd60a;
  font-family: var(--mono);
  font-size: .6rem;
  letter-spacing: .09em;
  text-transform: uppercase;
}

.analysis .footnote { margin-top: 26px; text-align: center; }

/* ==========================================================================
   7 · GALLERY — TV carousel: CSS bezel, crossfade slides, pill arrows,
   launch monitor overlapping in front (mock 2026-08-10)
   ========================================================================== */

.tvsec {
  position: relative;
  overflow: hidden;
  padding: clamp(100px, 14vh, 190px) 0 clamp(130px, 17vh, 230px);
  background: var(--black); /* dark belt (user 2026-08-14) */
}
/* the active screenshot, blown up and blurred, doubles as the section
   backdrop (user mock 2026-08-14); a light veil keeps the head readable */
/* isolate so the veil/dither z-indices stay inside this layer and cannot
   climb over .tvsec__head and .tvwrap, which sit at z-index 1 */
.tvsec__bg { position: absolute; inset: 0; isolation: isolate; }
.tvsec__bg span {
  position: absolute;
  inset: -8%;
  background-size: cover;
  background-position: center;
  filter: blur(64px) saturate(1.15);
  transform: scale(1.1);
  opacity: 0;
  transition: opacity .8s var(--ease);
}
.tvsec__bg span.is-active { opacity: 1; }
.tvsec__bg::after {
  content: "";
  position: absolute;
  inset: 0;
  /* Full black at both edges so the dark neighbours melt in (user 2026-08-18),
     but EASED at each end rather than ramping straight off the edge (user
     2026-08-24). The backdrop span is blurred 64px and then hard-clipped by
     .tvsec's overflow, so the blur ends on a straight cut at the section
     boundary. The old ramp was only fully opaque at exactly 0%: a few pixels
     in it had already dropped enough to let that cut edge show against the
     black section above, which read as a seam — obvious under the bright
     screens (Course play, Round setup), invisible under the dark ones. Holding
     near-solid black through the first few percent hides the cut, and the same
     easing at the foot does it for the bottom edge. */
  /* The % stops alone are not enough: the section boundary lands on a
     fractional device pixel, so the row straddling the clip gets blended
     rather than painted solid and resolves a hair lighter than the black
     on the other side — a hairline seam. These two px-based caps paint the
     first and last 4px solid #000 whatever the rounding, and the eased ramp
     underneath carries on from there. */
  background:
    linear-gradient(180deg, #000 0, #000 2px, rgba(0, 0, 0, 0) 4px),
    linear-gradient(0deg,   #000 0, #000 2px, rgba(0, 0, 0, 0) 4px),
    linear-gradient(180deg,
      #000 0%,
      rgba(0, 0, 0, .99) 3%,
      rgba(0, 0, 0, .95) 6%,
      rgba(0, 0, 0, .86) 10%,
      rgba(0, 0, 0, .75) 14%,
      rgba(0, 0, 0, .70) 18%,
      rgba(0, 0, 0, .66) 55%,
      rgba(0, 0, 0, .74) 80%,
      rgba(0, 0, 0, .84) 88%,
      rgba(0, 0, 0, .93) 94%,
      rgba(0, 0, 0, .99) 97%,
      #000 100%);
}

/* DITHER (user 2026-08-24) — the real cause of the banding by these veils.
   They ramp from solid black to ~.66 over several hundred pixels of near
   black, and 8-bit colour cannot express that smoothly: each 1/255 step
   spans dozens of rows, and the eye reads every step as a hard band. It is
   worst where the backdrop underneath is bright, which is why the pale
   screens (Course play, Round setup) showed it and the dark ones did not.
   A faint noise tile over the ramp scatters each step boundary across a few
   pixels, so the eye integrates it as a smooth ramp. Cheap, and it is the
   standard remedy: the geometry was never the problem. */
.tvsec__bg::before,
.phero__dim::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;                    /* over the veil, under the section content */
  pointer-events: none;
  opacity: .05;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}
.tvsec__bg::after { z-index: 1; }

.tvsec__head, .tvsec .tvwrap { position: relative; z-index: 1; }
.tvsec__head { text-align: center; margin-bottom: clamp(34px, 5vh, 64px); }

.tvwrap {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(18px, 2.6vw, 48px);
  width: min(1680px, 94vw);
  margin-inline: auto;
  /* room for the monitor's overhang below the frame */
  padding-bottom: clamp(24px, 3.5vw, 64px);
}

/* the belt: same cylinder carousel as the Foresight+ feature orbit, scaled up
   for full screenshots (user 2026-08-14). Cards ride the near face of the
   cylinder, so the edges bend away; arrows + progress bar sit underneath. */
.orbit__stage {
  --ocw: clamp(340px, 66vw, 1180px);  /* scaled way up (user 2026-08-14) */
  margin-top: clamp(20px, 3vh, 40px);
  perspective: 2100px;               /* pulled back to suit the bigger cards */
  perspective-origin: 50% 42%;
  /* the ends dissolve rather than cutting to slivers */
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 9%, #000 91%, transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0, #000 9%, #000 91%, transparent 100%);
}
.orbit__stage { cursor: grab; touch-action: pan-y; }
.orbit__stage.is-dragging { cursor: grabbing; }
.orbit__stage.is-dragging .orbit__ring { transition: none; }
.orbit__ring {
  position: relative;
  /* decorative: the ring is pushed back in Z, so its projected hit area lands
     nowhere near the cards — the stage above it takes the pointer instead */
  pointer-events: none;
  height: calc(var(--ocw) * .5625 + 104px);  /* 16:9 card + caption */
  transform-style: preserve-3d;
  transition: transform .78s var(--ease);   /* the belt eases round, never snaps */
  will-change: transform;
}
.orbit__item {
  position: absolute;
  left: 50%;
  top: 0;
  width: var(--ocw);
  margin-left: calc(var(--ocw) / -2);
  transform: rotateY(var(--a, 0deg)) translateZ(var(--r, 0px));
  transition: opacity .78s var(--ease), filter .78s var(--ease);
  backface-visibility: hidden;
  /* the item box is huge and projected — leave it transparent to the pointer,
     but the CARD itself takes clicks so a side card can page the belt
     (user 2026-08-21). Dragging still works: pointerdown bubbles to the stage. */
  pointer-events: none;
}
.orbit__card {
  pointer-events: auto;
  cursor: pointer;
  width: 100%;
  aspect-ratio: 16 / 9;
  overflow: clip;
  backface-visibility: hidden;
  border-radius: 20px;
  /* The card carries its clip's poster as a background (set inline in main.js).
     A <video> only shows its poster attribute until the first frame arrives —
     never again after playback has started — so when the belt rewinds a clip
     that was parked on its last frame, the seek flushes the decoder and for
     ~200ms there is no frame to paint and the card fell through to this flat
     colour. That read as the card blinking to black on every move. With the
     still underneath, the gap shows the clip's own first frame instead. */
  background-color: #141416;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  box-shadow: 0 26px 64px rgba(0, 0, 0, .34);
}
.orbit__card img,
.orbit__card .orbit__clip { display: block; width: 100%; height: 100%; object-fit: cover; }
.orbit__cap {
  margin-top: 20px;
  text-align: center;
  font-size: clamp(15px, 1.2vw, 18px);
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: -.017em;
  color: rgba(255, 255, 255, .62);
}
.orbit__cap h3 { display: block; font: inherit; color: #fff; }
.orbit__cap p { display: block; margin: 4px 0 0; font-weight: 400; } /* was .35em ≈ 6px (user 2026-08-19) */

.orbit__nav {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 24px;
  /* 40px from the caption to the controls (user 2026-08-19, was ~75px).
     The nav is a SIBLING of the stage, so the visible gap is this margin PLUS
     the slack the ring leaves under the caption: the ring reserves a flat
     104px for it (see .orbit__ring) while the caption only needs
     20px + 2 lines + 4px. That slack shrinks as the caption's clamp grows,
     so a flat margin here would drift between 40px and 46px across widths —
     tracking the same clamp cancels it out and pins the gap at 40px.
     Derivation: slack = 84 - (2.8 * F + 4); margin = 40 - slack = 2.8 * F - 40.
     Retune if the caption's line-height (1.4) or the ring's 104px change. */
  margin-top: calc(2.8 * clamp(15px, 1.2vw, 18px) - 40px);
}
/* The controls come in WITH the caption (user 2026-08-19). Matching --d isn't
   enough on its own: the reveal observer trips each element as its own top
   crosses 88vh, and the stage is ~640px tall, so the nav's turn came a whole
   screen of scrolling later. Riding the stage's .in makes them one beat. The
   nav keeps .reveal for the transition, and its own later trigger is a no-op. */
.orbit__stage.in ~ .orbit__nav { opacity: 1; transform: none; }
.orbit__arrow {
  display: grid;
  place-items: center;
  /* 44px with the filled Foresight+ chevrons (user 2026-08-18): the two
     carousels on the site now carry the same control */
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: rgba(255, 255, 255, .1);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  color: #f5f5f6;
  cursor: pointer;
  transition: background .3s var(--ease), transform .2s var(--ease);
}
.orbit__arrow svg { width: 26px; height: 26px; }
.orbit__arrow:hover { background: rgba(255, 255, 255, .18); }
.orbit__arrow:active { transform: scale(.94); }
/* The indicator is a dot row (user 2026-08-18): one dot per screenshot, the
   live one stretched into a pill. With autoplay armed the pill doubles as the
   timer — its fill loads left to right across the dwell, the same treatment
   the Foresight+ hero pills use. */
.orbit__dots {
  display: flex;
  align-items: center;
  gap: 10px;
}
.orbit__dot {
  position: relative;
  width: 7px;
  height: 7px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  overflow: clip;
  background: rgba(255, 255, 255, .3);
  cursor: pointer;
  transition: width .5s var(--ease), background .4s var(--ease);
}
.orbit__dot:hover { background: rgba(255, 255, 255, .55); }
.orbit__dot.is-on { width: 30px; background: #fff; }
/* the fill only reads while the timer drives it; parked it sits at scaleX(0)
   behind a solid pill, so autoplay off (or reduced motion) looks untimed */
.orbit__dot i {
  position: absolute;
  inset: 0;
  background: #fff;
  transform: scaleX(0);
  transform-origin: 0 50%;
}
.orbit__nav.is-auto .orbit__dot.is-on { background: rgba(255, 255, 255, .26); }

@media (max-width: 899px) {
  .orbit__stage { --ocw: min(94vw, 560px); perspective: 1100px; } /* = bento width (user 2026-08-18) */
  /* Flat belt on phones (user 2026-09-17, see main.js): no cylinder, no
     mask, no per-card filter. Only the active card is painted, main.js
     crossfades between them. Both the mask over a preserve-3d subtree and
     the filter on each projected card are things WebKit handles badly —
     the belt lagged on Android and did not render on iOS. */
  .orbit__stage { perspective: none; -webkit-mask-image: none; mask-image: none; }
  .orbit__ring { transform-style: flat; will-change: auto; transition: none; }
  .orbit__item { transform: none; filter: none; transition: opacity .5s var(--ease); }
  /* the 64px-blurred, cross-fading backdrop is the other big GPU cost on a
     phone — two full-section blurs painted at once on every card change.
     The veil and dither stay, so the section is the same dark band without
     the blown-up poster behind it. */
  .tvsec__bg span { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .orbit__ring, .orbit__item, .orbit__dot { transition: none; }
}

/* ==========================================================================
   8 · FORESIGHT+ — consolidated band (user 2026-08-10: the intro stack,
   tiers, and course-pass sections moved to the Foresight+ page)
   ========================================================================== */

.plusband {
  padding: clamp(96px, 13vh, 160px) 0;
  background: var(--black);
  text-align: center;
}
.plusband .lede { margin-inline: auto; margin-top: .9rem; }
.plusband__points {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: clamp(20px, 3.4vw, 64px);
  margin-top: clamp(30px, 4.5vh, 52px);
}
.plusband__points li { display: grid; gap: .35rem; }
.plusband__points b { color: #f5f5f6; font-weight: 600; }
.plusband__points span { color: var(--ink-d2); font-size: .95rem; }
.plusband__cta {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  gap: clamp(20px, 2.4vw, 36px);
  margin-top: clamp(30px, 4.5vh, 50px);
}
.plusband .footnote { margin-top: 24px; margin-inline: auto; } /* the 64ch box was flush left under a centered column (user 2026-08-18) */

/* ---- retired with the consolidation (kept for reference / quick revert) ---- */

/* bottom −32.5% total (user 2026-07-31: −25%, then −10% more): the deck→Tiers
   gap is this padding + the stack's own padding-bottom + .tiers' padding-top,
   so all three are cut by the same factor to shrink the total evenly at every
   viewport instead of collapsing at one breakpoint */
.plus { padding: clamp(96px, 14vh, 170px) 0 clamp(65px, 9.45vh, 115px); }
.plus__head { text-align: center; }
.plus__head .lede { margin-inline: auto; }

/* Stacked cards: Mobile slides up over Premier on scroll (sticky deck).
   Geometry per Figma 11712:27951 — 1204×520 cards, radius 26. */
.stack {
  width: min(1204px, 94vw);
  display: flex;
  flex-direction: column;
  gap: 40px;
  margin-top: clamp(80px, 12vh, 140px);
  padding-bottom: 10.8vh; /* was 16vh — same −32.5% pass as .plus/.tiers */
}

.stack-card {
  position: sticky;
  overflow: hidden;
  border-radius: 26px;
  background: #080507;
  height: clamp(420px, 36.12vw, 520px);
  isolation: isolate;
  will-change: transform;
}
.stack-card--sim {
  top: calc(var(--nav-h) + 24px);
  transform-origin: top center;
  background: #151515;
}
.stack-card--sim::before {
  width: 8.5%;
  background: linear-gradient(90deg, rgba(21, 21, 21, 0), #151515);
}
.stack-card--sim::after {
  height: 26%;
  background: linear-gradient(180deg, rgba(21, 21, 21, 0), #151515);
}
.stack-card--mobile { top: calc(var(--nav-h) + 69px); }

/* oversized plate so the photo can drift with the scroll (see --par-over) */
.stack-card__par {
  position: absolute;
  inset: calc(var(--par-over) * -1) 0;
  z-index: 0;
}
.stack-card__bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 0;
}

.stack-card__art {
  position: absolute;
  left: 46.7%;
  top: -43%;
  width: 63%;
  /* parallax rides in front of the pose — the rotation must stay last */
  transform: translate3d(0, var(--par, 0px), 0) rotate(-24.47deg);
  z-index: 0;
  will-change: transform;
}
.stack-card__art img { width: 100%; height: auto; }

/* right + bottom fades per Figma */
.stack-card::before,
.stack-card::after {
  content: "";
  position: absolute;
  z-index: 1;
  pointer-events: none;
}
.stack-card::before {
  /* right fade hangs partly off-card per Figma (visible ≈ 5.6%) */
  top: 0; bottom: 0;
  right: -2.9%;
  width: 8.5%;
  background: linear-gradient(90deg, rgba(0, 0, 0, 0), #000);
}
.stack-card::after {
  /* bottom fade: right-anchored under the art, darkest point clipped below
     the card edge, left flank feathered so no seam shows */
  right: -3%; bottom: -8%;
  width: 48%;
  height: 34%;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0), #000);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 38%);
  mask-image: linear-gradient(90deg, transparent, #000 38%);
}

.stack-card__copy {
  position: absolute;
  z-index: 2;
  top: clamp(24px, 3vw, 43px);
  left: clamp(22px, 2.8vw, 40px);
  max-width: min(525px, 86%);
}
.stack-card__tag {
  /* Mono md/Regular — 16/24 */
  font-family: var(--mono);
  font-size: clamp(.85rem, 1.11vw, 1rem);
  line-height: 1.5;
  text-transform: uppercase;
  color: #fff;
}
.stack-card__copy h3 {
  /* Display lg/Semibold — 48/60, −2% */
  margin-top: 2px;
  font-size: clamp(1.8rem, 3.33vw, 3rem);
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -.02em;
  color: #fff;
}
.stack-card__copy p:last-child {
  /* Text lg/Medium — 18/28, 60% white */
  margin-top: 16px;
  font-size: clamp(1rem, 1.25vw, 1.125rem);
  font-weight: 500;
  line-height: 1.556;
  letter-spacing: 0;
  color: rgba(250, 250, 250, .6); /* Alpha/Text/Tertiary */
  max-width: min(393px, 90%);
}

/* ==========================================================================
   9 · TIERS
   ========================================================================== */

.tiers { padding: clamp(65px, 9.45vh, 115px) 0 clamp(96px, 14vh, 170px); background: var(--black); } /* top −32.5%, see .plus */
.tiers > .container:first-of-type { text-align: center; }
.tiers > .container:first-of-type .lede { margin-inline: auto; }

.tiers__grid {
  margin-top: clamp(40px, 6vh, 64px);
  width: min(1204px, 94vw); /* 3×388 + 2×20 per Figma */
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
  align-items: stretch;
}

/* Glass tier cards (per Figma): radial glow from top center + tinted border.
   The glow lives on ::before so it can breathe (slow pulse + drift). */
.tier-card {
  position: relative;
  overflow: hidden;
  border-radius: 28px;
  padding: clamp(28px, 2.8vw, 40px);
  display: flex;
  flex-direction: column;
  gap: 14px;
  color: var(--ink-d1);
  border: none; /* the line is the gradient ring layer per Figma */
  background: rgba(0, 0, 0, .2);
}
.tier-card--gold { gap: 16px; }
.tier-card > * { position: relative; z-index: 2; }
.tier-card {
  transition: opacity .9s var(--ease) var(--d, 0s), transform .5s var(--ease);
}
.tier-card:hover { transform: scale(1.02); }
.tier-card::before {
  content: "";
  position: absolute;
  inset: -12% -10%;
  z-index: 1;
  background: radial-gradient(70% 42% at 50% 6%, rgba(76, 76, 76, .24), rgba(76, 76, 76, .05) 42%, transparent 70%);
  animation: glow-breathe 7s ease-in-out infinite alternate;
  pointer-events: none;
}

/* rotating border line (Silver/Gold): luminous arc merging into black; Free stays static */
.tier-card .tier-card__ring {
  position: absolute;
  inset: 0; /* must stay inside the padding box — .tier-card's overflow:hidden clips there */
  border-radius: 27px;
  overflow: hidden;
  z-index: 0;
  pointer-events: none;
  /* Figma stroke: diagonal #E8ECF3→#B9C0CC→#8A94A5 at 10% */
  background: linear-gradient(108deg,
    rgba(232, 236, 243, .1), rgba(185, 192, 204, .1) 55%, rgba(138, 148, 165, .1));
}
.tier-card--silver .tier-card__ring {
  /* same ramp, born transparent, at 20% */
  background: linear-gradient(108deg,
    rgba(232, 236, 243, 0), rgba(185, 192, 204, .2) 55%, rgba(138, 148, 165, .2));
}
.tier-card--gold .tier-card__ring {
  /* Figma stroke: transparent → #F5C260 45% → #C88A1F, full opacity */
  background: linear-gradient(108deg,
    rgba(255, 233, 168, 0), #f5c260 45%, #c88a1f);
}
.tier-card--free .tier-card__ring i { display: none; }
.tier-card__ring i {
  /* Silver & Gold: identical geometry — same from-angle, same stops (0/55/110°),
     same duration, zero delay. Only the color differs. */
  position: absolute;
  inset: -60%;
  background: conic-gradient(from 0deg,
    transparent 0deg, rgba(201, 210, 223, .85) 55deg, transparent 110deg);
  animation: line-rotate 6s linear infinite;
}
.tier-card--gold .tier-card__ring i {
  background: conic-gradient(from 0deg,
    transparent 0deg, rgba(255, 223, 154, .95) 55deg, transparent 110deg);
}
.tier-card__ring b {
  position: absolute;
  inset: 1px;
  border-radius: 26px;
  background: #000; /* matches the page black so Silver/Gold read like Free's fill */
}
@keyframes line-rotate { to { transform: rotate(360deg); } }
.tier-card--silver::before {
  background: radial-gradient(70% 42% at 50% 6%, rgba(185, 192, 204, .24), rgba(138, 148, 165, .05) 42%, transparent 70%);
}
.tier-card--gold::before {
  background: radial-gradient(70% 42% at 50% 6%, rgba(245, 194, 96, .3), rgba(200, 138, 31, .09) 42%, transparent 70%);
}

@keyframes glow-breathe {
  from { opacity: .6; transform: translateY(0) scale(1); }
  to   { opacity: 1;  transform: translateY(3.5%) scale(1.07); }
}

.tier-card__head {
  display: flex;
  align-items: center;
  gap: 20px;
  padding-bottom: 14px;
  border-bottom: 1px solid rgba(255, 255, 255, .1);
  min-height: 86px; /* 72 head + 14 divider gap per Figma */
}
/* Figma clips the 72px medal into a 59.143px badge box, trimming its transparent
   margin. Negative insets give the same layout width without cropping the art. */
.tier-card__medal { width: 72px; height: 72px; flex: none; margin-inline: -6.43px; }
/* Video medals (Silver/Gold): the mp4 is cropped to the medal's edge — no
   transparent margin like the PNGs — so it renders at 69px (tiers-lab tuning)
   while keeping the PNG's 59.14px layout footprint. mix-blend-mode: screen
   drops the video's black background over the dark card. */
.tier-card__medal--video {
  width: 69px;
  height: 69px;
  margin-inline: -4.93px; /* (59.14 − 69) / 2 */
  object-fit: cover;
  mix-blend-mode: screen;
  pointer-events: none;
}
.tier-card__medal--silver { filter: grayscale(1) brightness(.91) contrast(1.01); }

.tier-card__name {
  font-size: 1.125rem; /* Text lg/Semibold — 18/28, wght 584 per the library style */
  font-weight: 584;
  line-height: 1.556;
  color: rgba(250, 250, 250, .6); /* Alpha/Text/Tertiary */
}
.tier-card h3 {
  /* Display sm/Bold — 30/38 */
  font-size: clamp(1.5rem, 2.1vw, 1.875rem);
  font-weight: 700;
  letter-spacing: 0;
  line-height: 1.267;
  color: #f5f5f6;
}
.tier-card__kicker {
  /* Text lg/Semibold — 18/28, wght 584 */
  font-size: 1.125rem;
  font-weight: 584;
  line-height: 1.556;
  color: rgba(250, 250, 250, .8); /* Alpha/Text/Secondary */
}

.tier-card ul {
  /* Text md/Regular — 16/24, rows gap 8, kicker→list 20 per Figma */
  margin-top: 6px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  font-size: clamp(.95rem, 1.15vw, 1rem);
  color: rgba(250, 250, 250, .8); /* Alpha/Text/Secondary */
}
.tier-card li {
  position: relative;
  padding-left: 30.755px; /* 18.755 check + 12 gap per Figma */
  line-height: 1.5;
}
.tier-card li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 2px;
  width: 18.755px;
  height: 18.755px;
  border-radius: 50%;
  background: #8a919c;
  -webkit-mask: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M10 1a9 9 0 100 18 9 9 0 000-18zm4.2 6.4l-4.9 5.4a.9.9 0 01-1.32.03L5.7 10.5a.9.9 0 111.27-1.27l1.6 1.6 4.28-4.7a.9.9 0 111.34 1.2z"/></svg>') center / contain no-repeat;
  mask: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M10 1a9 9 0 100 18 9 9 0 000-18zm4.2 6.4l-4.9 5.4a.9.9 0 01-1.32.03L5.7 10.5a.9.9 0 111.27-1.27l1.6 1.6 4.28-4.7a.9.9 0 111.34 1.2z"/></svg>') center / contain no-repeat;
}
.tier-card--silver li::before { background: #c3cbd6; }
.tier-card--gold li::before { background: #e5b34c; }


/* the Compare Plans trigger sits between the cards and the footnotes;
   it carries its own rhythm rather than the flex gap's 14px */
.tiers__foot .link-arrow--lg {
  margin-top: 0;
  margin-bottom: clamp(10px, 1.6vh, 18px);
}

/* centred CTA under the pass cards, matching Foresight+ */
.passes-cta {
  margin-top: clamp(34px, 4vh, 48px);
  text-align: center;
}

.tiers__foot {
  margin-top: clamp(40px, 4.6vh, 56px);
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 14px;
}
.tiers__foot .footnote { max-width: 58ch; }

/* ==========================================================================
   10 · COURSE LIBRARY
   ========================================================================== */

.library { padding: clamp(96px, 14vh, 170px) 0; }
.library > .container:first-of-type { text-align: center; }
.library > .container:first-of-type .lede { margin-inline: auto; }

/* Course fan (per Figma 11720:28361) — Figma's own isolated card renders
   (get_screenshot contentsOnly:true; flat exports bake the #F5F5F5 canvas),
   layered so the fan can open slightly. Shown at 70% of the design's 402×268.
   Offsets are the design's geometry in % of the 402×268 box; the centre one
   was cross-checked against the flat render by pixel correlation (<1px off).
   The downward dissolve is the flat render's own alpha ramp, measured:
   opaque to 64/268, gone by 175/268. */
.fan {
  --w: min(281px, 62vw); /* 402 × 0.7 */
  position: relative;
  width: var(--w);
  aspect-ratio: 402 / 268;
  /* the dissolve leaves the box's bottom empty — close the gap */
  margin: 0 auto calc(var(--w) * -.14);
}
/* Downward dissolve as a black overlay instead of a mask: any mask-image
   clips every overflowing card to the border box, which cut the fan's
   open/hover swing at the box edges. Painting black over the black section
   is visually identical and clips nothing. Stops are the flat render's
   measured ramp in box space (opaque to 64/268, gone by 175/268); the
   overlay reaches past the sides to cover the swung-out cards' bottoms. */
.fan::after {
  content: "";
  position: absolute;
  inset: 0 -20%;
  z-index: 1;
  background: linear-gradient(180deg, transparent 23.9%, #000 65.4%);
  pointer-events: none;
}
/* the heading overlaps the fan's dissolved bottom (negative margin), so it
   must paint above the ::after black — the mask never painted, this does */
.library .h2,
.library .lede { position: relative; z-index: 2; }

/* The fan opens as it scrolls through the viewport, then opens a touch further
   on hover. Both feed ONE transform per card, composed from two inherited
   numbers so they never fight each other:
     --o  scroll openness 0→1 (scrubbed in main.js off viewport progress)
     --h  hover boost 0→1 (CSS :hover)
   At --o:0 the cards sit closed (the old pre-reveal pose); at 1 they rest in
   the Figma pose; --h adds the extra spread on top. */
.fan { --o: 0; --h: 0; }
.fan:hover { --h: 1; }

.fan__card {
  position: absolute;
  height: auto;
  /* short, so the scroll scrub still tracks the wheel but hover eases */
  transition: transform .3s var(--ease);
  will-change: transform;
}
.fan__card--l {
  left: -.97%; top: 1.53%; width: 55.72%; transform-origin: 75% 90%;
  transform: rotate(calc(3.5deg * (1 - var(--o)) + -1.5deg * var(--h)))
             translate(calc(3.5% * (1 - var(--o)) + -1.8% * var(--h)),
                       calc(1%   * (1 - var(--o)) + -0.7% * var(--h)));
}
.fan__card--r {
  left: 45.25%; top: 1.53%; width: 55.72%; transform-origin: 25% 90%;
  transform: rotate(calc(-3.5deg * (1 - var(--o)) + 1.5deg * var(--h)))
             translate(calc(-3.5% * (1 - var(--o)) + 1.8% * var(--h)),
                       calc(1%    * (1 - var(--o)) + -0.7% * var(--h)));
}
.fan__card--c {
  left: 26.62%; top: -.15%; width: 46.77%; transform-origin: 50% 100%;
  transform: translateY(calc(2% * (1 - var(--o)) + -1% * var(--h)));
}

.passes {
  margin-top: clamp(40px, 6vh, 64px);
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 18px;
}

/* Pass cards mirror the tier cards exactly (fondo/glow/line), mapped
   Free → Course Pass (static neutral line) and Gold → Pro (celeste). */
.pass-card {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-card);
  border: none; /* the line is the ring layer, like the tiers */
  background: rgba(0, 0, 0, .2);
  padding: clamp(28px, 3vw, 42px);
  display: flex;
  flex-direction: column;
  transition: opacity .9s var(--ease) var(--d, 0s), transform .5s var(--ease);
}
.pass-card:hover { transform: scale(1.02); }
.pass-card > * { position: relative; z-index: 2; }

/* breathing top glow — Pro breathes celeste the way Gold breathes gold */
.pass-card::before {
  content: "";
  position: absolute;
  inset: -12% -10%;
  z-index: 1;
  background: radial-gradient(70% 42% at 50% 6%, rgba(76, 76, 76, .24), rgba(76, 76, 76, .05) 42%, transparent 70%);
  animation: glow-breathe 7s ease-in-out infinite alternate;
  pointer-events: none;
}
.pass-card--pro::before {
  background: radial-gradient(70% 42% at 50% 6%, rgba(59, 165, 255, .3), rgba(30, 109, 255, .09) 42%, transparent 70%);
}

/* border line = ring layer. overflow:hidden clips at the padding box, so the
   ring sits at inset 0 (not -1px) — same gotcha as .tier-card__ring. */
.pass-card .pass-card__ring {
  position: absolute;
  inset: 0;
  border-radius: calc(var(--r-card) - 1px);
  overflow: hidden;
  z-index: 0;
  pointer-events: none;
  /* Free's static edge: gray ramp at 10% */
  background: linear-gradient(108deg,
    rgba(232, 236, 243, .1), rgba(185, 192, 204, .1) 55%, rgba(138, 148, 165, .1));
}
.pass-card--pro .pass-card__ring {
  /* gold's edge in celeste: transparent → celeste → blue */
  background: linear-gradient(108deg,
    rgba(143, 216, 255, 0), #3ba5ff 45%, #1e6dff);
}
.pass-card:not(.pass-card--pro) .pass-card__ring i { display: none; }
.pass-card__ring i {
  position: absolute;
  inset: -60%;
  background: conic-gradient(from 0deg,
    transparent 0deg, rgba(143, 216, 255, .95) 55deg, transparent 110deg);
  animation: line-rotate 6s linear infinite;
}
.pass-card__ring b {
  position: absolute;
  inset: 1px;
  border-radius: calc(var(--r-card) - 2px);
  background: #000; /* page black, same trick as the tiers */
}

/* Dot field on hover (js/dot-field.js), per Figma 12040:6161 — the same
   halftone grid the hero and the closer carry, at the same 1:1 scale, but
   living inside the card and only while the pointer is on it.
   The design tops it with a black diagonal (30.638deg) so the dots survive
   only in the upper-right corner; over a black card a mask is identical to
   that overlay and keeps the stacking simple.
   The mask is painted at 240% and slides into place, so the field sweeps in
   from outside the corner instead of just fading — mask-position animates,
   while interpolating the gradient's own stops would need @property. */
.pass-card > .pass-card__shader {
  position: absolute;
  inset: 1px;              /* the 1px canto belongs to .pass-card__ring */
  z-index: 1;              /* over the breathing glow, under the copy (z2) */
  width: calc(100% - 2px);
  height: calc(100% - 2px);
  display: block;
  pointer-events: none;
  opacity: 0;
  -webkit-mask-image: linear-gradient(30.638deg, transparent 42%, #000 78%);
          mask-image: linear-gradient(30.638deg, transparent 42%, #000 78%);
  -webkit-mask-size: 240% 240%;
          mask-size: 240% 240%;
  -webkit-mask-position: 0% 100%;
          mask-position: 0% 100%;
  transition: opacity .6s var(--ease),
              -webkit-mask-position .9s var(--ease),
                      mask-position .9s var(--ease);
}
/* the class is toggled by dot-field.js, not :hover — the canvas has to keep
   drawing through the fade-out, so JS owns both ends of the state */
.pass-card.is-dots > .pass-card__shader {
  opacity: 1;
  -webkit-mask-position: 62% 38%;
          mask-position: 62% 38%;
}
/* no pointer, no field — and JS skips the WebGL context entirely there */
@media (hover: none) {
  .pass-card > .pass-card__shader { display: none; }
}

.pass-card__name {
  /* Text lg/Semibold — same ramp as .tier-card__name (Silver/Gold) */
  font-size: 1.125rem;
  font-weight: 584;
  line-height: 1.556;
  color: rgba(250, 250, 250, .6); /* Alpha/Text/Tertiary */
}
/* PRO chip (Figma 11890:3610). It is a PARALLELOGRAM, not a rect — the vector
   path runs 3.09px of lean over 18.56px of height, i.e. exactly 1:6 → 9.46°.
   The 40.21 in Figma is the slanted bounding box; the face itself is 37.11
   wide, so that is the CSS width and the skew re-creates the overhang.
   Skewing the box (rather than a backdrop layer) also makes `overflow: hidden`
   clip the shine to the slanted edges; only the label counter-skews. */
.pass-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  position: relative;
  overflow: hidden;
  isolation: isolate;
  margin-left: 5px;
  /* `vertical-align: middle` lands on the x-height midpoint, not the line-box
     one, which left it 1.8px low against the 18/28 name — Figma has it dead
     centre, so the nudge puts it back */
  vertical-align: middle;
  top: -2px;
  width: 37.1px;
  height: 18.6px;
  border-radius: 1.5px;
  transform: skewX(-9.46deg);
  background: linear-gradient(180deg, rgba(255, 255, 255, .5), rgba(255, 255, 255, .1));
  -webkit-backdrop-filter: blur(6.2px);
  backdrop-filter: blur(6.2px);
  box-shadow: 0 0 43px rgba(255, 180, 60, .18);
  font-size: 12.4px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0;
  color: #fff;
}
.pass-badge i { font-style: normal; transform: skewX(9.46deg); } /* label stays upright */
.pass-badge::after {
  content: "";
  position: absolute;
  inset: -1px;
  background: linear-gradient(105deg,
    transparent 38%, rgba(255, 255, 255, .9) 50%, transparent 62%);
  translate: -160% 0;
  animation: pass-badge-shine 5.2s var(--ease) infinite;
  pointer-events: none;
}
/* the sweep runs in the last ~28% of the cycle; the rest is the rest */
@keyframes pass-badge-shine {
  0%, 72% { translate: -160% 0; }
  100%    { translate: 160% 0; }
}

.pass-card__price {
  margin-top: 1rem;
  font-size: clamp(2.6rem, 4.6vw, 3.6rem);
  font-weight: 700;
  letter-spacing: -.025em;
  line-height: 1;
}
.pass-card__price span { font-size: .38em; font-weight: 600; color: var(--ink-d2); letter-spacing: 0; }
.pass-card__req {
  /* plain quiet caps — the capsule chrome was removed (user 2026-07-30) */
  margin-top: .8rem;
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .11em;
  text-transform: uppercase;
  color: var(--ink-d2);
}
.pass-card__body { margin-top: 1.2rem; color: var(--ink-d2); max-width: 40ch; }

/* Course belt — replaces the placeholder discs with real course chips that
   loop sideways inside the card. Geometry is Figma 12027:4095: 40px disc,
   8px to the label, name 12 / location 10 at 30%, and a 1×39 divider that
   fades out at both ends. The strip is DESIGNED to overflow — in Figma it is
   794 wide inside 489 of usable card — so the card crops it and the loop
   just keeps it moving. */
.courses {
  /* Pinned to the card's foot rather than the flow: both cards are the same
     height (the grid stretches them) but the body runs one line on Course Pass
     and two on Pro, which left the belts at different heights. The auto margin
     eats the slack so both sit on the card's bottom padding; the minimum air
     comes from the body's margin-bottom below. */
  margin-top: auto;
  height: 40px;
  overflow: hidden;
  /* the label fades out instead of being sliced at the crop line */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.pass-card__body { margin-bottom: 32px; } /* = Figma's 32 sp before the strip */

.courses__track {
  display: flex;
  width: max-content;
  height: 100%;
  align-items: center;
  will-change: transform;
}
/* main.js clones the set; the loop shifts by exactly one set's width, so
   -50% of the two-set track lands on the seam. Without JS the strip simply
   sits still — which is the Figma mock.
   Speed is 18px/s — slow enough to read a course name as it goes by. main.js
   sets --courses-dur per belt from its own measured set width, because the two
   cards carry different line-ups and a shared duration would run them at
   different speeds. The value here is only the no-JS fallback. */
.courses { --courses-dur: 45s; }
.courses--run .courses__track { animation: courses-run var(--courses-dur) linear infinite; }
@keyframes courses-run { to { transform: translateX(-50%); } }

/* 24 each side of the 1px divider = 49px chip to chip. Figma has 20 (41px);
   the user opened it up for air. Widening this lengthens the set, but the
   duration is measured, so the belt keeps its 18px/s. */
.courses__set { display: flex; align-items: center; gap: 24px; padding-right: 24px; }

.course-chip { display: flex; align-items: center; gap: 8px; flex: none; }
.course-chip__crest {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  /* most crests sit on white; a few carry the club's own colour and set
     --crest-bg inline (Gearhart gray, Glen Abbey navy, Olympic Club red) */
  background: var(--crest-bg, #fff);
  display: grid;
  place-items: center;
  flex: none;
  overflow: hidden;
}
.course-chip__crest img { display: block; }

.course-chip__txt { display: flex; flex-direction: column; line-height: 1; }
.course-chip__txt b {
  font-size: 12px;
  font-weight: 584;
  letter-spacing: -.01em;
  color: #fff;
  white-space: nowrap;
}
.course-chip__txt i {
  font-style: normal;
  font-size: 10px;
  font-weight: 584;
  color: rgba(255, 255, 255, .3);
  margin-top: 1px;
  white-space: nowrap;
}

.course-chip__sep {
  flex: none;
  width: 1px;
  height: 39px;
  background: linear-gradient(180deg, rgba(3, 72, 217, 0), var(--blue) 50%, rgba(3, 72, 217, 0));
}
/* ==========================================================================
   12 · VENUES
   ========================================================================== */

/* Rebuilt to Figma 13042:1106 (2026-08-27). The plate is no longer BEHIND the
   copy: the render sits on top in its own cropped window, the copy below it on
   flat black. Gone with it — the halftone grid, the radial scrim and the blue
   brand cast, none of which the new composition has anything to sit over. The
   old .venues__bg / __grid / __field / __grade / __scrim rules went with them. */

.venues {
  position: relative;
  background: #000;

  /* The band's framing, two numbers — "crop A" from the lab (user
     2026-08-28, after trying the tighter/taller crop B: 1.235/.82):
     --venues-crop trims the render's sides as a zoom factor — 1.061 takes
     ~3% off each side and keeps all three screens whole.
     --venues-vshow is how much of the render's height the window reveals;
     .78043 is the Figma framing (632.414/810.345). RAISING it is what makes
     the band taller — magnification alone only buys the zoom factor. The
     render below the window is simply unseen now that the plate is static.
     Height, plate geometry and the sofa fades all derive from these two. */
  --venues-crop: 1.061;
  --venues-vshow: .78043;
}

/* ---- the cropped window ----
   1440 x 632.414 in the Figma, holding a render that is 1440 x 810.345 and
   top-aligned: the window shows the top 78% and the floor runs off the bottom.
   That leftover 22% is exactly what the parallax drifts into. */
.venues__media {
  position: relative;
  width: 100%;
  /* 1440 x 632.414 in the Figma, then divided by the crop: trimming the sides
     magnifies everything, so the band has to grow by the same factor or the
     zoom would eat the room from the top and bottom instead.
     --venues-vshow is how much of the render's HEIGHT the window shows —
     .78043 is the Figma framing (632.414/810.345), and raising it is what
     makes the band taller, since magnification alone only ever buys the zoom
     factor. The render below the window is simply unseen: the plate is
     static (the parallax this pair used to feed came out 2026-08-28).
     At crop 1.061 / vshow .78043 this resolves to 1440 x 671. */
  aspect-ratio: calc(1440 / 810.345 / (var(--venues-crop) * var(--venues-vshow, .78043)));
  /* the band is ~47% of the viewport width, which on a phone is a 180px letter
     slot — too short to read as a room. Floor it. The framing below the floor
     is not designed yet (no mobile Figma for this section). */
  min-height: calc(320px * var(--venues-crop));
  overflow: hidden;
  background: #000;
}

/* The drifting plate. data-par-anchor="top" tells the shared parallax loop
   (js/main.js) to run --par from 0 to -2x travel instead of the centred
   +/- range every other plate uses: there is 178px of spare render BELOW the
   window and none above, so the drift can only ever go down. At --par: 0 —
   no JS, or prefers-reduced-motion, which opts out of the loop entirely —
   the plate is exactly the Figma frame. */
.venues__plate {
  position: absolute;
  top: 0;
  /* wider than the window by the crop factor, centred — that overhang IS the
     side trim. The height stays 810.345/632.414 of the window because the
     window grew by the same factor, so the vertical framing is untouched. */
  left: calc((100% - 100% * var(--venues-crop)) / 2);
  width: calc(100% * var(--venues-crop));
  height: calc(100% / var(--venues-vshow, .78043));   /* 128.138% at the Figma framing */
}
.venues__photo {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 0;
  /* no brightness knock-back any more: nothing sits on top of the render */
}

/* The power-on canvas sits exactly on the plate, so its uv IS the render's
   uv (js/venues-poweron.js). Hidden = the plain <img> carries the band: the
   state before the dark renders load, after the effect lands, with no
   WebGL, and under prefers-reduced-motion (the script removes it). */
.venues__fx {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}
.venues__fx[hidden] { display: none; }

/* ---- the four fades, above the plate and static while it drifts ---- */
.venues__fade { position: absolute; pointer-events: none; }
/* 13042:1111 — sinks the ceiling into the black above the section.
   The Figma node sits at y 2.76 (0.437%), which is almost certainly loose
   placement rather than intent: that strip leaves the render's top rows
   unfaded, and the ceiling is dark grey, not black, so against the section
   above it reads as a hard horizontal cut right across the band. Anchored
   at 0 instead, with the height carrying the offset so the gradient still
   ENDS where the Figma's does (0.437 + 43.185). */
.venues__fade--top {
  left: 0;
  top: 0;
  width: 100%;
  height: 43.622%;
  background: linear-gradient(180deg, #000, rgba(0, 0, 0, 0));
}
/* 13042:1113 — the wide floor fade */
.venues__fade--floor {
  left: 0;
  top: 85.278%;
  width: 100%;
  height: 17.013%;
  background: linear-gradient(0deg, #000, rgba(0, 0, 0, 0));
  filter: blur(clamp(8px, .89vw, 16px));
}
/* 13042:1112 + :1114 — two narrow, barely-blurred fades that kill the hard
   bottom edge of each sofa silhouette without dimming the floor between them.
   These are the one thing the crop can break: they are placed against the
   RENDER, not the window, so their Figma percentages have to be mapped through
   the same zoom and re-centred or they slide off the sofas. Vertical
   percentages need no such treatment — the crop is horizontal only. */
.venues__fade--spot {
  top: 92.148%;
  width: calc(23.036% * var(--venues-crop));
  height: 8.179%;
  background: linear-gradient(0deg, #000, rgba(0, 0, 0, 0));
  filter: blur(1.5px);
}
.venues__fade--spot-l {
  left: calc(17.768% * var(--venues-crop) + (100% - 100% * var(--venues-crop)) / 2);
}
.venues__fade--spot-r {
  left: calc(59.195% * var(--venues-crop) + (100% - 100% * var(--venues-crop)) / 2);
}

/* ---- the black below ----
   Not an overlay on the picture: the page's own black, carrying the copy with
   it. The lip is a feathered band ABOVE the join, so the render dissolves into
   the section instead of stopping at a cut, and the black itself never moves. */
.venues__below {
  position: relative;
  z-index: 1;
  background: #000;
  padding-top: clamp(56px, 6.1vw, 96px);      /* Figma: 87.586 at 1440 */
  /* 70% of the shared dark-section token: the FAQ below opens with zero top
     padding, so this padding IS the whole seam — at the full token the two
     sections drifted apart (user 2026-08-28: -30%) */
  padding-bottom: clamp(77px, 11.2vh, 133px);
}
.venues__below::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;
  height: clamp(72px, 8.6vw, 150px);          /* 124 at 1440 */
  background: linear-gradient(180deg, rgba(0, 0, 0, 0), #000);
  pointer-events: none;
}

/* Figma stacks these on explicit gaps (31 eyebrow->title, 30 title->lede)
   rather than on the margins the old block layout inherited, so the column
   is a flex stack and .eyebrow's own margin-bottom is zeroed out of it. */
.venues__inner {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(20px, 2.1vw, 30px);
  text-align: center;
}
.venues__inner .eyebrow { margin-bottom: 0; }
/* The lede speaks in the same voice as the "This is Foresight+" tail
   (.thisis__lede — Text xl/Semibold, user 2026-08-28) rather than the base
   .lede ramp, which runs a step larger. Only the type is borrowed: measure
   and spacing stay the section's own. The negative margin takes 30% back
   out of the column gap between the title and this block alone — the
   eyebrow -> title gap keeps the full token. */
.venues__inner .lede {
  max-width: 56ch;
  margin-inline: auto;
  margin-top: calc(clamp(20px, 2.1vw, 30px) * -.3);
  font-size: clamp(1.05rem, 1.39vw, 1.25rem);
  font-weight: 584;
  line-height: 1.5;
  letter-spacing: normal;
  color: rgba(245, 245, 246, .6);
}
/* The lede -> button distance is the "This is Foresight+" tail's, exactly:
   its token (clamp(22px, 3.5vh, 40px), thisis-band.css .thisis__cta) minus
   this column's own flex gap, so the TOTAL always equals the token — the
   calc may go negative at some widths, which is correct, not a bug
   (user 2026-08-28: "misma distancia del texto al boton"). */
.venues__cta { margin-top: calc(clamp(22px, 3.5vh, 40px) - clamp(20px, 2.1vw, 30px)); }

/* .venues__title's type and gradient live with .compat__title — the two are
   one shared treatment (user 2026-08-27: "mismo tamaño y color"). */

/* ==========================================================================
   13 · FAQ
   ========================================================================== */

.faq { padding: clamp(96px, 14vh, 170px) 0; }

.faq__grid {
  display: grid;
  grid-template-columns: 5fr 7fr;
  gap: clamp(36px, 5vw, 90px);
  align-items: start;
}
.faq__head { position: sticky; top: calc(var(--nav-h) + 48px); }
.faq__head .h2 {
  /* Display lg/Bold — 48/60, −2%: keeps the title on two lines */
  font-size: clamp(2rem, 3.33vw, 3rem);
  line-height: 1.25;
}
.faq__list { margin-top: 6px; }

.qa { border-top: 1px solid var(--line-l); }
.qa:last-child { border-bottom: 1px solid var(--line-l); }

.qa summary {
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: 26px 2px;
  font-size: clamp(1.1rem, 1.39vw, 1.25rem); /* Text xl/Semibold */
  font-weight: 600;
  line-height: 1.5;
  letter-spacing: -.01em;
  cursor: pointer;
  transition: color .3s;
}
.qa summary::-webkit-details-marker { display: none; }
.qa summary:hover { color: var(--blue); }

.qa__icon {
  position: relative;
  flex: none;
  width: 22px;
  height: 22px;
}
.qa__icon::before, .qa__icon::after {
  content: "";
  position: absolute;
  inset: 50% auto auto 50%;
  width: 15px;
  height: 1.8px;
  background: var(--ink-l2);
  transform: translate(-50%, -50%);
  transition: transform .4s var(--ease);
}
.qa__icon::after { transform: translate(-50%, -50%) rotate(90deg); }
.qa[open] .qa__icon::after { transform: translate(-50%, -50%) rotate(0deg); }
.qa[open] .qa__icon::before { transform: translate(-50%, -50%) rotate(0deg); }

.qa__panel { overflow: hidden; }
.qa__panel p {
  padding: 0 40px 28px 2px;
  font-size: clamp(1rem, 1.25vw, 1.125rem); /* Text lg/Medium */
  font-weight: 500;
  line-height: 1.556;
  color: var(--ink-l2);
  max-width: 60ch;
}

/* ==========================================================================
   14 · CLOSER
   ========================================================================== */

.closer {
  /* top matched to bottom (user 2026-07-31) — it was clamp(168px, 26vh, 300px),
     roughly 36% taller than the foot, which read as a lopsided module */
  padding: clamp(124px, 19vh, 220px) 0;
  text-align: center;
  position: relative;
  overflow: clip;
}
.closer::before {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -320px;
  width: 1100px;
  height: 640px;
  transform: translateX(-50%);
  background: radial-gradient(closest-side, rgba(3, 72, 217, .2), transparent 70%);
  pointer-events: none;
  z-index: 1; /* glow above the dot field */
}

.closer__inner { position: relative; z-index: 2; }
/* Closing headline leading: halfway between .h2's own 1.25 (too loose beside
   the band headline) and the band's .875 (too tight here) — user 2026-08-19.
   The ≤1024px step splits the same way, 1.25 vs the band's 1.02. */
.closer__inner .h2--xl { line-height: 1.06; }
@media (max-width: 1024px) {
  .closer__inner .h2--xl { line-height: 1.135; }
}

/* Ambient dot-field canvas (js/dot-field.js) — behind the glow and content.
   Without the explicit z-indexes the canvas paints OVER ::before (pseudo is
   first in paint order). Mask fades the grid at the section's edges. */
.closer__shader {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 12%, #000 88%, transparent);
          mask-image: linear-gradient(to bottom, transparent, #000 12%, #000 88%, transparent);
}

/* 3D glass app icon (Figma 11802:93118) — replaces the eyebrow tag here.
   JS scrubs its entrance + float/tilt; static and fully visible without it. */
.closer__icon {
  display: block;
  width: clamp(133px, 11.9vw, 171px);
  height: auto;
  aspect-ratio: 1; /* <video> won't infer it from the attrs before metadata lands */
  margin: 0 auto 1.05rem;
  will-change: transform, opacity;
}
.closer .lede { margin-inline: auto; }
.closer .cta-row { justify-content: center; }

/* ==========================================================================
   FOOTER
   ========================================================================== */

.footer {
  background: var(--black);
  border-top: 1px solid rgba(255, 255, 255, .1); /* alpha-white-10 */
  padding: 34px 0 46px;
  text-align: left;
  font-size: .74rem;
  line-height: 1.7;
  color: var(--ink-d3);
}
.footer p + p { margin-top: .5rem; }
.footer__legal { padding-top: 1rem; color: rgba(255, 255, 255, .4); font-weight: 500; } /* alpha-white-40 */

/* ==========================================================================
   FLOATING COMPATIBILITY PILL
   Capsule geometry (360×42, 4px inset, 34px round button) wearing the
   localnav's glass. The scroll choreography runs in two beats and reverses:
   leaving — the capsule furls toward its button cap (clip-path), then the cap
   slides off past the right edge; returning — it slides back in first, then
   unfurls leftward. Driven from main.js: visible near the top and again near
   the end of the page, furled away in between. The × dismisses it for good.
   ========================================================================== */

.pill {
  position: fixed;
  right: 24px;
  bottom: 24px;
  z-index: 90; /* under the nav (100), over everything else */
  display: flex;
  align-items: center;
  gap: 4px;
  width: min(360px, calc(100vw - 32px));
  height: 42px;
  padding: 4px 4px 4px 0;
  border-radius: 980px;
  border: 1px solid rgba(255, 255, 255, .08);
  background: rgba(8, 8, 10, .4);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px);
  box-shadow: 0 4px 24px rgba(0, 0, 0, .35);
  clip-path: inset(0 0 0 0 round 980px);
  translate: 0 0;
  opacity: 1;
  /* return beat: slide in, then unfurl */
  transition:
    translate .5s var(--ease),
    clip-path .55s var(--ease) .22s,
    opacity .25s var(--ease);
}
.pill--away {
  /* leaving beat: furl to the cap, then slip off the edge */
  clip-path: inset(0 0 0 calc(100% - 42px) round 980px);
  translate: calc(100% + 32px) 0;
  opacity: 0;
  pointer-events: none;
  transition:
    clip-path .5s var(--ease),
    translate .55s var(--ease) .2s,
    opacity .3s var(--ease) .45s;
}

.pill__text {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  /* dissolve only where the text exits, toward the button */
  -webkit-mask-image: linear-gradient(90deg, #000 0, #000 75%, transparent 100%);
  mask-image: linear-gradient(90deg, #000 0, #000 75%, transparent 100%);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
}

.pill__track {
  display: flex;
  width: max-content;
  animation: pill-ticker 30s linear infinite;
}
@keyframes pill-ticker { to { transform: translateX(-50%); } }
.pill:hover .pill__track { animation-play-state: paused; }

.pill__item {
  padding-left: 18px; /* clears the capsule curve */
  padding-right: 2em;
  font-size: 15px;
  line-height: 24px;
  font-weight: 492;
  white-space: nowrap;
  color: rgba(250, 250, 250, .8); /* Alpha/Text/Secondary */
}

.pill__btn {
  flex: none;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, .08);
  color: #fff;
  cursor: pointer;
  transition: background .25s ease;
}
.pill__btn:hover { background: rgba(255, 255, 255, .16); }
.pill__btn:active { background: rgba(255, 255, 255, .22); }
.pill__btn svg { width: 16px; height: 16px; transition: transform .4s var(--ease); }
.pill__btn:hover svg { transform: rotate(90deg); }

/* ==========================================================================
   BREAKPOINTS
   ========================================================================== */

@media (max-width: 1068px) {
  .faq__grid { grid-template-columns: 1fr; }
  .faq__head { position: static; }
  .bento-card--7, .bento-card--5 { grid-column: span 12; }
  .bento-card--4 { grid-column: span 6; }
  .tiers__grid { grid-template-columns: 1fr; max-width: 640px; margin-inline: auto; }
}

@media (max-width: 820px) {
  .localnav__links { display: none; }
  /* no burger on mobile (user 2026-08-10): the Download pill is the only
     localnav action — .localnav__burger keeps its base display:none */

  /* hero carries a single CTA on mobile; Download already sits top right */
  .phero .cta-row .btn--ghost { display: none; }

  .passes { grid-template-columns: 1fr; }
  .stack-card__art { width: 82%; left: auto; right: -24%; top: 4%; }
  .bento-card--4 { grid-column: span 12; }

  /* +20% module height on mobile (user 2026-07-30): 14vh → 20.3vh padding
     measured to land total height at 1.2× (513 → 616 at 375×812) */
  .realism { min-height: 0; padding: clamp(130px, 20.3vh, 240px) 0; }
  /* the expanding mask is off at this breakpoint — a 6.25vw inset is a
     sliver here, and the rounded window just eats the frame */
  [data-clip] { --clip: 0 !important; clip-path: none; }

  /* el calado en pantallas angostas: el slice del viewBox recorta los lados,
     así que la máscara baja de cuerpo para que las tres líneas entren */
  /* (the old `font-size: 88px` override lived here — it fitted the width but
     left the fixed line gaps at 1.42× the font instead of .89×, which is what
     read as "muy separado". --ms replaces it.) */

  /* Composition on mobile (user 2026-07-31). The panel is exactly one screen
     and the calado+sub group only fills ~half, so ~434px of air has to live
     somewhere — top and bottom trade against each other and can't both shrink.
     Two passes, measured at 390×844:
       1. gap 166 → 83 (halved) by dropping the mask block, bottom 125 → 90;
       2. top 344 → 258 (−25%, same move as the module margins) by lifting the
          whole group 86px — which is why the bottom lands back at ~176.
     The mask translate is in viewBox units so it rides the slice scale and
     holds the same proportion at every width; the sub stays a %. */
  .vt__masktext { transform: translateY(30.5px) scale(var(--ms, 1)); }
  .vt__sub { top: 68.5%; }

  .gallery { gap: 14px; }
  .gallery-paddles { display: none; }
}

@media (max-width: 480px) {
  /* One gutter for the whole phone layout. The containers are percentage-based
     (92vw / 94vw), so they drifted apart on a narrow screen — 15px for .container
     against 11px for .container-wide. A flat 16px lines every section up on the
     same edge; --ag-inset carries it into the stacked analysis tiles, and the
     showcase still measures its 104vw bleed from that same margin. */
  .container, .container-wide, .container-narrow,
  .showcase__inner { width: calc(100% - 32px); } /* .showcase__inner repeats the 94vw formula as its own class */
  .ag { --ag-inset: 16px; }
  .hero__title { font-size: clamp(1.9rem, 8vw, 3.9rem); } /* "Real data. Real golf." holds one line (user 2026-08-12) */
  .btn { width: 100%; }
  .cta-row { flex-direction: column; align-items: stretch; }
  .hero .cta-row, .closer .cta-row { align-items: center; }
  /* plusband joins the opt-out (user 2026-08-18): it sits outside a .cta-row,
     so width:100% ran it edge to edge while its siblings in the closer kept
     the pill shape */
  .hero .btn, .closer .btn,
  .plusband__cta .btn { width: auto; min-width: 230px; }
  .fsx__cta { width: auto; } /* the marquee CTA hugs its label, per Figma */
  /* compat and the band's ghost CTA hug their labels too (user 2026-09-21).
     Both declare the same 13px/26px as the marquee CTA, but neither was
     showing it: "Shop Now" was held open by the 230px min-width above and
     floated ~72px from each edge, and "Explore Foresight+" had no opt-out at
     all, so .btn's width:100% ran it the full 343px of a 375 viewport and
     pushed its label ~100px in. Dropping the min-width — not touching the
     padding — is what makes the three read as the same button. */
  .compat__cta, .thisis__cta { width: auto; min-width: 0; }
  /* Tighter around the marquee on phones (user 2026-09-17): the desktop
     96px shoulders plus the deck's own top padding left ~150px of black
     under the Explore CTA, and the hero above it is in flow now, so it no
     longer needs the room the pinned hand-off did. */
  .fsx { padding: 56px 0 72px; gap: 64px; }
  .ctabs { padding-top: 56px; }
  /* ...but not that tight (user 2026-09-18): 40 + 24 left the deck 64px under
     the Explore CTA and it read as part of the marquee, and the CTA sat 32px
     under the cards. Both doubled — 128 to the deck, the extra split evenly
     across the two sections, and 64 from the cards to the CTA. Measure the
     deck once it has revealed: until then .reveal holds it 28px lower. */
  .pill { right: 16px; bottom: 16px; }
  .tiles { grid-template-columns: repeat(2, 1fr); }
}

/* ==========================================================================
   REDUCED MOTION
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .reveal { opacity: 1; transform: none; transition: none; }
  .tb-card__club, .tb-card__viewsart, .tb-card__drillart { opacity: 1; transform: none; transition: none; }
  .tv__screen img { transition: opacity .01s linear; transform: none; }
  /* the scrub job never registers here — land on the open state, not the window */
  [data-clip] { --clip: 0; clip-path: none; }
  .showcase__img { animation: none; }
  .tier-card::before { animation: none; }
  .tier-card__ring i { animation: none; }
  .pass-card::before { animation: none; }
  .pass-card > .pass-card__shader { display: none; } /* pure decoration, hover-only */
  .pass-badge::after { animation: none; translate: -160% 0; }
  .pass-card__ring i { animation: none; }
  .tier-card:hover { transform: none; }
  .pass-card:hover { transform: none; }
  .paddle, .btn { transition: none; }
  .btn .roll b, .paddle svg, .pill__btn svg { transition: none; }
  /* roll frozen: first copy always in place, second always parked below */
  .btn .roll b:first-child { transform: none !important; }
  .btn .roll b:nth-child(2) { transform: translateY(115%) !important; }
  .link-arrow::after { animation: none; }
  .fsx__viewport { overflow-x: auto; } /* JS belt is off; row scrolls by hand */
  .pill, .pill--away { transition: none; }
  .pill__track { animation: none; }
  /* vt: sin transiciones ni drift; la composición calada queda estática
     (el sub visible, el frame a su tamaño de reposo — min-height cubre el hold) */
  .vt__frame { transition: none; transform: none !important; border-radius: 28px !important; }
  .vt__video { animation: none; }
  .vt__maskart { transition: none; transform: none; }
  .vt__sub { transition: none; opacity: 1; transform: translate(-50%, 0); }
  .vt__veil { transition: none; opacity: 1; } /* the is-rising job never runs here */
  .fan__card { transition: none !important; transform: none !important; }
}

/* ============================================================
   9 · COMPARE PLANS — full-bleed overlay + slide-up module
   Figma: 12228:3770 (Table-1), 12228:4220 (Table module),
          12228:4743 (scroll behaviour).

   The panel is a FIXED 833-tall window (the Figma module height,
   clamped to the viewport). Everything inside scrolls — including
   the close button and title — except the Free/Silver/Gold header,
   which sticks to the top of its own table container. Rows blur
   away behind that header and fade at both edges of the window.
   ============================================================ */

/* plate = alpha-white-5 composited over --elev; used by the sticky
   header and the fade ramps so rows dissolve into the same value. */
:root { --cmp-plate: #191919; }

.compare {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px 16px;
  visibility: hidden;
  pointer-events: none;
  /* hold visibility until the exit transition has played out */
  transition: visibility 0s linear .5s;
}
.compare.is-open {
  visibility: visible;
  pointer-events: auto;
  transition-delay: 0s;
}

.compare__scrim {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, .72);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  opacity: 0;
  transition: opacity .42s var(--ease);
  cursor: pointer;
}
.compare.is-open .compare__scrim { opacity: 1; }

/* The module — eases up on open, back down and out on close. */
.compare__panel {
  position: relative;
  width: 633px;
  max-width: 100%;
  height: min(833px, calc(100vh - 48px));
  background: var(--elev);
  border: 1px solid rgba(255, 255, 255, .05);
  border-radius: var(--r-card);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  /* the close button keeps the panel's own 24px; the headline and the
     table plate sit a further 40px in, for the 64px gutter in Figma */
  --cmp-inset: 40px;
  padding: 24px 24px 24px;
  opacity: 0;
  transform: translateY(56px);
  transition: opacity .42s var(--ease), transform .52s var(--ease);
}
.compare.is-open .compare__panel { opacity: 1; transform: none; }


.compare__close {
  flex: none;
  align-self: flex-start;
  width: 44px;
  height: 44px;
  padding: 0;
  position: relative;
  border: 1px solid var(--line-d);
  border-radius: 50%;
  background: rgba(255, 255, 255, .06);
  cursor: pointer;
  transition: background .2s var(--ease), border-color .2s var(--ease);
}
.compare__close:hover { background: rgba(255, 255, 255, .12); border-color: rgba(255, 255, 255, .22); }
.compare__close:focus-visible { outline: 2px solid var(--blue-light); outline-offset: 3px; }
.compare__close::before,
.compare__close::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 14px;
  height: 1.5px;
  margin: -.75px 0 0 -7px;
  background: var(--ink-d1);
  border-radius: 2px;
}
.compare__close::before { transform: rotate(45deg); }
.compare__close::after  { transform: rotate(-45deg); }

.compare__head {
  flex: none;
  text-align: center;
  padding: 10px 0 20px;
  margin: 0 var(--cmp-inset);
}
.compare__head .eyebrow { margin-bottom: 8px; }
.compare__title {
  margin: 0;
  font-family: var(--font);
  font-weight: 600;                /* Demibold */
  font-size: 36px;                 /* display-md */
  line-height: 44px;
  letter-spacing: -.72px;
  color: var(--ink-d1);
}

/* ---- table plate: alpha-white-5, radius-2xl, 24 padding ---- */
.compare__table {
  position: relative;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  background: rgba(255, 255, 255, .05);
  border-radius: 16px;
  padding: 0 24px;
  margin: 0 var(--cmp-inset);
}

.compare__cols,
.compare__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) repeat(3, 80px);
  gap: 4px;
  align-items: center;
}

/* Free / Silver / Gold — fixed with the rest of the header block.
   Bottom-aligned so FREE sits on the same line as the Silver/Gold
   prices rather than floating between name and price. */
.compare__cols {
  flex: none;
  align-items: end;
  padding: 24px 0 8px;
}

/* The only scrolling region. Rows dissolve into both edges of the
   window via a mask, so nothing hard-clips at the plate bounds. */
.compare__rows {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;
  /* top pad + the group's own 20px lands the first heading at 28px,
     just clear of the 22px ramp */
  padding: 8px 0 20px;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 22px, #000 calc(100% - 38px), transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0, #000 22px, #000 calc(100% - 38px), transparent 100%);
}
.compare__rows::-webkit-scrollbar { width: 0; height: 0; }

.compare__plan { text-align: center; }
.compare__plan-name {
  display: block;
  font-size: 16px;
  font-weight: 500;
  color: var(--ink-d1);
  letter-spacing: -.01em;
}
.compare__plan-price {
  display: block;
  margin-top: 1px;
  font-family: var(--mono);
  font-size: 11px;
  line-height: 16px;
  color: var(--ink-d2);
}
.compare__plan--free .compare__plan-name {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-d2);
}

.compare__group {
  padding: 20px 0 8px;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -.01em;
  color: var(--ink-d1);
}

.compare__row {
  padding: 9px 0;
  border-top: 1px solid rgba(255, 255, 255, .07);
}
.compare__label {
  font-size: 14px;          /* text-sm */
  line-height: 20px;
  font-weight: 500;         /* Medium */
  color: var(--ink-d1);
}
.compare__cell {
  text-align: center;
  font-size: 14px;
  color: var(--ink-d1);
}

/* included — the circle-check mask the tier cards use */
.compare__yes {
  display: inline-block;
  width: 16px;
  height: 16px;
  vertical-align: -2px;
  background: var(--ink-d1);
  -webkit-mask: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M10 1a9 9 0 100 18 9 9 0 000-18zm4.2 6.4l-4.9 5.4a.9.9 0 01-1.32.03L5.7 10.5a.9.9 0 111.27-1.27l1.6 1.6 4.28-4.7a.9.9 0 111.34 1.2z"/></svg>') center / contain no-repeat;
  mask: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M10 1a9 9 0 100 18 9 9 0 000-18zm4.2 6.4l-4.9 5.4a.9.9 0 01-1.32.03L5.7 10.5a.9.9 0 111.27-1.27l1.6 1.6 4.28-4.7a.9.9 0 111.34 1.2z"/></svg>') center / contain no-repeat;
}
.compare__no { font-weight: 600; color: rgba(255, 255, 255, .28); }

/* ---- Scroll indicator -------------------------------------------
   Sits in the panel's right gutter, outside the table plate, and
   spans exactly the plate's height (Figma 12237:5221). Hidden until
   you hover the module, and flashes briefly while scrolling. */

.compare__bar {
  position: absolute;
  top: 4px;                 /* iOS insets the indicator from the scroll bounds */
  bottom: 4px;
  right: calc(-1 * var(--cmp-inset) - 9px);   /* 15px in from the panel edge */
  width: 6px;
  border-radius: 999px;
  opacity: 0;
  pointer-events: none;
  /* out: lingers, then eases away. in: effectively instant. */
  transition: opacity .4s var(--ease) .45s;
}
.compare__panel:hover .compare__bar,
.compare__bar.is-active {
  opacity: 1;
  pointer-events: auto;
  transition: opacity .05s linear;
}
.compare__thumb {
  display: block;
  width: 100%;
  border-radius: 999px;
  background: rgba(255, 255, 255, .4);
  transition: background .15s var(--ease);
  cursor: grab;
}
.compare__bar:hover .compare__thumb { background: rgba(255, 255, 255, .52); }
.compare__bar.is-dragging .compare__thumb { background: rgba(255, 255, 255, .62); cursor: grabbing; }

@media (max-width: 560px) {
  .compare { padding: 16px 12px; }
  .compare__panel { height: calc(100vh - 32px); border-radius: var(--r-panel); --cmp-inset: 0px; }
  .compare__panel { padding: 20px 16px; }
  .compare__table { padding: 0 16px; }
  .compare__cols { padding: 16px 0 12px; }
  .compare__cols,
  .compare__row { grid-template-columns: minmax(0, 1fr) repeat(3, 56px); gap: 4px; }
  .compare__title { font-size: 27px; line-height: 34px; }
  .compare__label { font-size: 12px; }
}

@media (prefers-reduced-motion: reduce) {
  .compare__scrim,
  .compare__panel { transition: opacity .2s linear; }
  .compare__panel { transform: none; }
}

/* ---- Course list variant (Figma 12237:5221) ---------------------
   Same shell as Compare plans, but two 80px plan columns, brighter
   row labels, and rows separated by a 12px gap instead of rules. */

.compare--courses .compare__cols,
.compare--courses .compare__row {
  grid-template-columns: minmax(0, 1fr) 80px 80px;
}
.compare--courses .compare__cols {
  align-items: start;
  padding: 24px 0 8px;
}
.compare--courses .compare__plan-name {
  font-size: 16px;
  line-height: 18px;
}
.compare--courses .compare__rows {
  display: flex;
  flex-direction: column;
  gap: 19px;              /* 21px row + 19px = 40px pitch, same as Compare plans */
  padding: 20px 0 20px;
}
.compare--courses .compare__row {
  padding: 0;
  border-top: 0;
}

@media (max-width: 560px) {
  .compare--courses .compare__cols,
  .compare--courses .compare__row { grid-template-columns: minmax(0, 1fr) 68px 68px; }
  .compare--courses .compare__plan-name { font-size: 14px; }
}


.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ==========================================================================
   1b · PHERO — pinned hero (user 2026-08-06, Apple-style storyboard).
   The section is a tall scroll track; .phero__pin stays stuck for its
   length while js scrubs: zoom into the projection screen, fade the bay
   frame/HUD/copy/floor, dim the settled course, fill the statement over
   it. Beams + dots ride on the pin so they hold while pinned; the course
   covers them once full-bleed.
   ========================================================================== */

.phero {
  position: relative;
  height: 200svh; /* pin travel — rise, zoom, settle, fade to black */
  background: var(--black);
}
.phero__pin {
  position: sticky;
  top: 0;
  height: 100svh;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-top: calc(var(--nav-h) + clamp(64px, 11vh, 120px));
  text-align: center;
  overflow: clip;
  background: var(--black);
}
.phero__pin::before { /* beams — opposite diagonal from mobile's 112deg pair */
  content: "";
  position: absolute;
  inset: -12%;
  background:
    linear-gradient(64deg, transparent 8%, rgba(96, 150, 255, .5) 18%, transparent 30%),
    linear-gradient(64deg, transparent 32%, rgba(23, 82, 224, .45) 44%, transparent 56%);
  filter: blur(clamp(20px, 3vw, 44px));
  -webkit-mask-image:
    radial-gradient(58% 64% at 0% 0%, #000 24%, transparent 76%),
    radial-gradient(48% 54% at 100% 62%, #000 18%, transparent 74%);
  mask-image:
    radial-gradient(58% 64% at 0% 0%, #000 24%, transparent 76%),
    radial-gradient(48% 54% at 100% 62%, #000 18%, transparent 74%);
  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 required: a
   positioned canvas would otherwise paint over ::before (the beams), the same
   trap annotated on .closer__shader. Pools wider than the static field did. */
.phero__shader {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
  -webkit-mask-image:
    radial-gradient(52% 56% at 92% 34%, #000, transparent 76%),
    radial-gradient(46% 50% at 4% 4%, #000, transparent 74%);
  mask-image:
    radial-gradient(52% 56% at 92% 34%, #000, transparent 76%),
    radial-gradient(46% 50% at 4% 4%, #000, transparent 74%);
}
/* the class lands only once a GL context actually exists, so a browser
   without WebGL keeps the static halftone instead of losing the dots */
.phero__pin.has-dots::after { display: none; }

.phero__pin::after { /* halftone dots, mid-right + a whisper up top-left.
     Still the no-WebGL fallback — see .phero__shader above. */
  content: "";
  position: absolute;
  inset: 0;
  background-image: radial-gradient(rgba(140, 180, 255, .9) 1px, transparent 1.4px);
  background-size: 16px 16px;
  opacity: .4;
  -webkit-mask-image:
    radial-gradient(34% 38% at 97% 34%, #000, transparent 72%),
    radial-gradient(30% 34% at 4% 6%, #000, transparent 70%);
  mask-image:
    radial-gradient(34% 38% at 97% 34%, #000, transparent 72%),
    radial-gradient(30% 34% at 4% 6%, #000, transparent 70%);
  pointer-events: none;
}
.phero__copy { position: relative; z-index: 3; }
.phero__copy .cta-row { justify-content: center; }
.phero__media {
  position: relative;
  z-index: 2;
  width: min(1450px, 100vw); /* bigger at rest (user 2026-08-06); the rise-and-
    center beat brings the mat into view before the zoom leaves the bay */
  /* negative pull: the canvas carries transparent margin above the bay, so
     the visual gap to the CTAs needs to eat into it (user 2026-08-06).
     Doubled 2026-08-26: the wider bay's canvas carries ~18% dead space above
     the enclosure (content starts 328px into 1812), which left ~91px of air
     under the Download button. */
  margin-top: clamp(-168px, -9.5vh, -56px);
}
.phero__media::before { /* bloom lifts the dark render off black at rest */
  content: "";
  position: absolute;
  inset: -8% -12% 24%;
  background: radial-gradient(56% 60% at 50% 34%, rgba(34, 92, 226, .34), transparent 70%);
  filter: blur(clamp(24px, 4vw, 56px));
  pointer-events: none;
}
.phero__zoom {
  position: relative;
  will-change: transform;
}
.phero__layer {
  display: block;
  width: 100%;
  height: auto;
}
.phero__bay,
.phero__ui {
  position: absolute;
  inset: 0;
  will-change: opacity;
}

/* High-res still for the fully-zoomed frame (user 2026-08-26). The composite
   layers are sized for the bay at rest, where the course fills only ~40% of
   the width — at full bleed that same content was being stretched about 3x,
   which is what read as blurry. This sits exactly on the course layer's
   content rect (x 871..2029, y 586..1240 of the 2900x1812 canvas) and
   cross-fades in as the bay clears, so the final frame lands near 1:1 without
   making the layers that only ever show small any heavier. It carries no src
   until the pin starts moving, and only on displays that can resolve more
   than the composite already gives. */
.phero__hi {
  position: absolute;
  z-index: 3;              /* over the media, under .phero__dim and the framing */
  left: 0;
  top: 0;
  max-width: none;         /* the global img rule would clamp it to the pin */
  object-fit: cover;
  opacity: 0;
  pointer-events: none;
  will-change: opacity;
}
/* stack: bay frame at the back, course over its (opaque black) screen,
   HUD on top — so fading bay + HUD leaves the clean course */
.phero__course { position: relative; z-index: 2; }
.phero__bay { z-index: 1; }
.phero__ui { z-index: 3; }
.phero__dim { /* bottom ~20% fades to black under the course marquee hand-off (user 2026-08-12) */
  position: absolute;
  inset: 0;
  z-index: 4;
  /* px cap on the bottom edge for the same reason as .tvsec__bg::after: the
     pin clips this on a fractional pixel, and a part-blended row reads as a
     hairline against the solid black of the section below. */
  background:
    linear-gradient(0deg, #000 0, #000 2px, rgba(0, 0, 0, 0) 4px),
    linear-gradient(to top, #000 0%, rgba(0, 0, 0, .85) 8%, transparent 24%);
  opacity: 0;
  pointer-events: none;
}

/* "Play real courses" over the settled hero course (user 2026-08-12: the
   old playhero section's framing, relocated into the pin — same 1440-frame
   proportions, now vw-relative). js pops the pieces in after the dissolve. */
.phero__playreal {
  position: absolute;
  inset: 0;
  z-index: 5;
  pointer-events: none;
}
.phero__pl1, .phero__pl2 {
  position: absolute;
  font-size: min(13.65vw, 197px);
  font-weight: 700;
  line-height: .8;
  letter-spacing: -.05em;
  word-spacing: -.04em;
  color: #fff;
  white-space: nowrap;
  opacity: 0;
}
/* Own layers for everything js fades on the scrub (user 2026-09-18). Painted
   into the pin's shared surface, Safari skipped the repaint on the step that
   lands on opacity 0, so after a few quick scrubs the last faint frame of
   "Play real" / "courses" stayed on screen over the resting hero — the DOM
   said 0, the pixels did not. On their own layers the fade is the
   compositor's, and there is nothing left behind to repaint. */
.phero__pl1, .phero__pl2, .phero__plcopy { will-change: transform, opacity; }
.phero__dim { will-change: opacity; }
.phero__pl1 { left: 4.4vw; top: 34%; }
.phero__pl2 { right: 4.4vw; top: 52%; }
.phero__plcopy {
  position: absolute;
  left: 50%;
  bottom: max(6vh, 40px);
  translate: -50% 0; /* centering lives here — js owns transform for the pop-in */
  width: min(88vw, 540px);
  font-size: clamp(.95rem, 1.67vw, 1.5rem);
  font-weight: 584;
  line-height: 1.2;
  text-align: center;
  color: #fff;
  opacity: 0;
}
@media (max-width: 700px) {
  .phero__pin { padding-top: calc(var(--nav-h) + clamp(48px, 8vh, 72px)); }
  /* oversize the canvas so the bay reads big on a phone — the sides crop
     into the pin's overflow clip, and the one-line title above buys the
     scene its height back (user 2026-08-10) */
  /* Pull the bay up under the CTA (user 2026-08-26). This was a positive
     margin, which pushed it further down on top of the ~65px of dead
     canvas the oversized 148vw crop already carries above the enclosure. */
  .phero__media { width: 148vw; margin-top: clamp(-56px, -4vh, -20px); }
  /* the lone CTA hugs its label instead of running full width */
  .phero .cta-row { align-items: center; }
  .phero .cta-row .btn { width: auto; min-width: 0; padding-inline: 16px; }
  /* "Play real" / "courses" read too far apart and too airy on phone
     (user 2026-08-19): bigger type, lines stacked touching. pl2's offset is
     one line box (19vw × .8 line-height) so the seam holds on any viewport
     height — desktop keeps the wide 34/52 stagger */
  .phero__pl1, .phero__pl2 { font-size: 19vw; }
  .phero__pl1 { top: 35%; }
  .phero__pl2 { top: calc(35% + 15.2vw); }
}

/* Reduced motion: no pin, no zoom — the composite at rest, statement below it */
@media (prefers-reduced-motion: reduce) {
  .phero { height: auto; }
  .phero__pin { position: static; height: auto; padding-bottom: 120px; }
  .phero__dim { display: none; }
  .phero__playreal { display: none; } /* scrub-only framing; the marquee follows anyway */
}

/* ==========================================================================
   6b · TRAINING FACILITY — head + golfer per mock (user 2026-08-06). The
   tile rail below reuses the original analysis-gallery CSS (still intact
   above, from c5605e6); only a dark skin is layered on via .tfac.
   ========================================================================== */

.tfac {
  position: relative;
  padding: clamp(96px, 14vh, 170px) 0 clamp(90px, 12vh, 150px);
  background: var(--black);
  overflow: clip;
}
/* Ambient dot-field canvas (js/dot-field.js) — same pattern as .closer__shader.
   Mask fades the grid to black at the top/bottom section seams. */
.tfac__shader {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
  /* dots start at the very top of the section, fading in over the first 40px.
     The bottom fade was lifted from 40%/57% to 62%/86% (user 2026-08-18) so the
     field carries down past the golfer instead of stopping above the tile rail. */
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 40px, #000 62%, transparent 86%);
          mask-image: linear-gradient(to bottom, transparent, #000 40px, #000 62%, transparent 86%);
}
.tfac > :not(.tfac__shader) { position: relative; z-index: 1; }
.tfac__head { text-align: center; }
.tfac__head .h2 { margin-top: .5rem; }
/* Sizing moved to the wrapper so it can act as the parallax driver — see the
   note in index.html. The layout box stays put while the image drifts inside
   it, which is what keeps the phase honest. */

/* Bento grid (user 2026-08-07: replaces the analysis rail). Column spans
   follow the mock: top row 7/5, bottom row 4/8. Copy is live HTML pinned
   bottom-left; card art rides as CSS backgrounds (see the --shot/--views
   skins) so the grid renders fine before assets land. */
.tfac-bento {
  --tb-gap: clamp(16px, 1.6vw, 28px);
  /* inner width of a 4-span (1x) card: (grid − 2 gaps) / 3 */
  --tb-1x: calc((min(1680px, 94vw) - 2 * var(--tb-gap)) / 3);
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--tb-gap);
  width: min(1680px, 94vw);
  /* the golfer cutout used to sit between the headline and the grid
     (user 2026-08-21) — without it the bento rides up under the head.
     Doubled 2026-09-01, then brought back down 2026-09-04: the cards
     should sit closer under "Your new training ground". */
  margin: clamp(32px, 4vh, 56px) auto 0;
}
.tb-card {
  position: relative;
  overflow: hidden;
  border-radius: clamp(18px, 1.6vw, 28px);
  background: #1c1c1e center / cover no-repeat;
  min-height: clamp(300px, 30vw, 600px);
  display: flex;
  align-items: flex-end;
}
.tb-card--shot  { grid-column: span 8; } /* 2:1 with --views (user 2026-08-07) */
.tb-card--views { grid-column: span 4; }
.tb-card--drills { grid-column: span 4; }
.tb-card--range  { grid-column: span 8; }
.tb-card--drills, .tb-card--range { min-height: clamp(260px, 26vw, 520px); }

/* Scroll parallax (user 2026-08-18): the photo leaves the card and rides an
   oversized plate, same pattern as .showcase__par. The card keeps its flat
   colour as the backstop, and its overflow:hidden is what crops the plate. */
.tb-card__plate {
  /* Two knobs, and they are coupled: --tb-gain multiplies the engine's 5.5%
     travel, --tb-over must stay above that or the plate edge enters frame.
     16% over / 2.4 gain = 13.2% travel, 2.8% slack. Raising gain without
     raising over is what exposes an edge. Cost of a bigger overhang is a
     tighter `cover` crop, so this is the trade to make when tuning. */
  --tb-over: 16%;
  --tb-gain: 2.4;
  position: absolute;
  inset: calc(var(--tb-over) * -1) 0;
  z-index: 0;
  background: center / cover no-repeat;
  transform: translate3d(0, calc(var(--par, 0px) * var(--tb-gain)), 0);
  will-change: transform;
}
.tb-card--shot .tb-card__plate { background-image: url("../assets/ShotAnalysisBento.webp"); }
.tb-card--views {
  background-image: radial-gradient(85% 60% at 88% 0%, rgba(45, 160, 158, .3), transparent 62%),
                    linear-gradient(185deg, #113b42 0%, #0c2c33 55%, #0a2227 100%);
}

/* Club View module — rides above the course plate, settles in on reveal.
   The wrapper carries the dark frosted backdrop the flattened PNG can't
   (its radius ratio matches the panel's corners at any width). */
.tb-card__club {
  position: absolute;
  z-index: 1;
  right: clamp(16px, 5%, 60px);
  top: 50%;
  translate: 0 -50%; /* centering lives here so the transform stays free for the entrance */
  width: clamp(120px, 21%, 214px);
  border-radius: 9% / 5.7%;
  background: rgba(9, 11, 14, .42);
  -webkit-backdrop-filter: blur(14px);
          backdrop-filter: blur(14px);
  box-shadow: 0 18px 44px rgba(0, 0, 0, .35);
  opacity: 0;
  transform: translateY(18px);
  transition: opacity .9s var(--ease) .3s, transform .9s var(--ease) .3s;
}
.tb-card__club img { display: block; width: 100%; height: auto; }
.tfac-bento.in .tb-card__club { opacity: 1; transform: none; }

/* Split-view UI collage — bleeds off the card's left and right edges,
   cropped by the card; settles in on reveal (mock 2026-08-07) */
.tb-card__viewsart {
  --tb-gain: 2;
  position: absolute;
  z-index: 1;
  /* Gain is per-card here: unlike the plates these carry their own oversize
     in top/width, so each has a different ceiling before its edge shows.
     transform stays the entrance's — this rides `translate`. */
  translate: 0 calc(var(--par, 0px) * var(--tb-gain, 1));
  top: -16%;
  left: -26%;
  width: 160%;
  max-width: none;
  height: auto;
  opacity: 0;
  transform: translateY(14px);
  transition: opacity .9s var(--ease) .35s, transform .9s var(--ease) .35s;
}
.tfac-bento.in .tb-card__viewsart { opacity: 1; transform: none; }

/* Drill Library — slate card, transparent rings/trees art anchored to the top.
   Oversized so its baked-in fades clear the card edges (no hard art seams). */
.tb-card--drills {
  background-image: linear-gradient(185deg, #4a545f 0%, #434d58 55%, #3b4550 100%);
}
.tb-card__drillart {
  --tb-gain: 1.8;
  position: absolute;
  z-index: 1;
  /* Gain is per-card here: unlike the plates these carry their own oversize
     in top/width, so each has a different ceiling before its edge shows.
     transform stays the entrance's — this rides `translate`. */
  translate: 0 calc(var(--par, 0px) * var(--tb-gain, 1));
  top: -19%;
  left: -10%;
  width: 138%; /* grown from 124%: buys the bottom clearance the gain needs */
  max-width: none;
  height: auto;
  opacity: 0;
  transform: translateY(14px);
  transition: opacity .9s var(--ease) .4s, transform .9s var(--ease) .4s;
}
.tfac-bento.in .tb-card__drillart { opacity: 1; transform: none; }

/* Customize your Practice — full-bleed drill-cards plate, same treatment
   as the Shot Analysis plate */
.tb-card--range .tb-card__plate { background-image: url("../assets/CustomizeBento.webp"); }
.tb-card--range::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1; /* over the drifting plate, under the copy */
  pointer-events: none;
  background: linear-gradient(45deg, rgba(0, 0, 0, .42) 0%, rgba(0, 0, 0, .18) 22%, transparent 46%);
}

.tb-card__copy {
  position: relative; /* above the backgrounds and blur layers */
  z-index: 2;
  padding: clamp(20px, 2.2vw, 40px);
  /* wide (2x) cards keep the same text measure as a 1x card */
  max-width: var(--tb-1x);
}
.tb-card__copy h3 {
  font-size: clamp(1.15rem, 1.5vw, 1.65rem);
  font-weight: 600;
  color: #f5f5f6;
}
.tb-card__copy p {
  margin-top: .55em;
  font-size: clamp(.92rem, 1vw, 1.1rem);
  line-height: 1.45;
  color: var(--ink-d2);
}
/* quiet corner darken behind the copy (user 2026-08-07: the progressive
   blur read as a filter effect — a plain gradient does the legibility job) */
.tb-card--shot::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1; /* over the drifting plate, under the copy */
  pointer-events: none;
  background: linear-gradient(45deg, rgba(0, 0, 0, .48) 0%, rgba(0, 0, 0, .22) 22%, transparent 46%);
}

@media (max-width: 820px) {
  .tfac-bento { grid-template-columns: 1fr; }
  .tfac-bento .tb-card { grid-column: auto; min-height: clamp(240px, 62vw, 420px); }
  /* taller card + smaller module so the club clears the copy */
  .tfac-bento .tb-card--shot { min-height: clamp(320px, 96vw, 480px); }
  .tb-card__club { width: clamp(96px, 30vw, 132px); top: 16px; right: 14px; translate: none; }
  .tb-card__copy { max-width: none; } /* single column — no 1x measure to match */
}

/* dark skin over the recovered gallery; the rail's inset tracks the
   featured-courses (4K) card edge, min(1680px, 94vw), user 2026-08-06 */
.tfac .ag {
  --ag-inset: max(3vw, calc((100vw - 1680px) / 2));
  margin-top: clamp(44px, 6vh, 72px);
}
.tfac .ag-tile__card { background: var(--card-dark); /* shows only while loading */ }
.tfac .ag-tile__caption { color: var(--ink-d2); }
.tfac .ag-tile__caption h3 { color: var(--ink-d1); }
.tfac .paddle { background: rgba(255, 255, 255, .1); color: #f5f5f6; }
.tfac .paddle:hover:not(:disabled) { background: rgba(255, 255, 255, .18); }
/* paddles ride below the rail here (the old layout tucked them in a header row) */
.tfac .ag__paddles {
  justify-content: flex-end;
  padding-inline: var(--ag-inset);
  margin: 28px 0 0;
}

/* --- COMPATIBILITY (user comp 2026-08-14): metallic gradient headline over
   the device-lineup jpeg; the image tucks up behind the head so the copy
   floats in its dark upper region like the comp --- */
.compat {
  position: relative;
  overflow: hidden;
  background: #000;
  padding-top: clamp(84px, 12vh, 160px);
  text-align: center;
}
.compat__head {
  position: relative;
  z-index: 1;
  /* the shared .container caps at 1080px, but the headline's first line needs
     ~1130px at the top of its clamp — so it broke to three lines and stranded
     the "and". This band gets its own wider measure (user 2026-08-21). */
  width: min(1420px, 94vw);
  max-width: none;
  margin-inline: auto;
}
/* DISPLAY TITLE — the big gradient heading. Shared since 2026-08-27, when
   venues was asked to match the compatibility band exactly; it is one
   treatment now, so it lives in one rule rather than two that drift apart.
   Deliberately NOT built on .h2: it is a step above it in size and lighter
   in weight, and the elements that take it carry this class alone.

   The measure: the compat headline's hard <br> broke after "and", but at the
   top of the clamp that first line is wider than the container, so it wrapped
   again and orphaned the "and" (user 2026-08-21). A measure in em means the
   break scales with the type instead of fighting it, and balance keeps the
   lines even. */
.compat__title,
.venues__title {
  max-width: min(15em, 94vw);
  margin: 0 auto;
  text-wrap: balance;
  font-size: clamp(38px, 5.2vw, 92px);
  font-weight: 640;
  line-height: 1.04;
  letter-spacing: -.03em;
  background: linear-gradient(180deg, #fff 8%, #d9dbdf 46%, #55585e 96%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
/* BUTTON — travelling ring (user 2026-08-14, made shared 2026-08-21).
   Keeps the .btn geometry but wears an animated stroke instead of a fill or a
   ghost border. Now a modifier so any button can take it, rather than living
   on the compat CTA alone. */
.btn--ring {
  position: relative;
  border: 0;
  border-radius: 999px;
  background: #000; /* stays black on hover too — only the stroke reacts */
  --cta-ring: .28;
  color: #fff;
  transition: --cta-ring .28s var(--ease);
}
.btn--ring:hover { transform: none; background: #000; filter: none; }
/* device-list lede under the headline (user 2026-09-04): the shared .lede
   type, centred like the other section ledes; 56ch (not the base 46) so the
   eight device names hold to two lines at desktop widths. */
.compat .lede { margin-inline: auto; max-width: 56ch; }
.compat .compat__cta { margin-top: clamp(26px, 3.5vh, 44px); }
/* the icon rides beside the label, inside the button's flex row */
.btn__ico {
  flex: none;
  width: 1.05em;
  height: 1.05em;
  margin-right: .55em;
  margin-left: -.15em;
}
/* the travelling stroke: a conic gradient rotated by an animated angle, laid
   over a constant dim ring so the outline never disappears. Both layers are
   punched hollow by the same mask, leaving only the 1px edge.
   @property is what makes the angle animatable at all — without it the conic
   simply sits still, which is a clean static fallback. */
@property --cta-angle {
  syntax: "<angle>";
  initial-value: 0deg;
  inherits: false;
}
/* inherits so ::before picks it up from the button; registered as a number so
   it can be transitioned — hover lifts the whole ring to solid white */
@property --cta-ring {
  syntax: "<number>";
  initial-value: 0.28;
  inherits: true;
}
.btn--ring::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background:
    conic-gradient(from var(--cta-angle) at 50% 50%,
      rgba(255, 255, 255, var(--cta-ring)) 0deg,
      rgba(255, 255, 255, 1) 48deg,
      rgba(255, 255, 255, var(--cta-ring)) 96deg,
      rgba(255, 255, 255, var(--cta-ring)) 360deg),
    linear-gradient(rgba(255, 255, 255, var(--cta-ring)) 0 0);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  pointer-events: none;
  animation: cta-stroke 3.6s linear infinite;
}
@keyframes cta-stroke { to { --cta-angle: 360deg; } }
/* hover: the whole pill outlines in solid white, body untouched */
.btn--ring:hover { --cta-ring: 1; }
@media (prefers-reduced-motion: reduce) {
  .compat__cta { transition: none; }
  .btn--ring::before { animation: none; }
}
/* --- the comp's frame, rebuilt as layers (user 2026-08-18) ---------------
   Everything is positioned as a % of Figma's 1440x1129 frame, so the whole
   stage scales with the column. Measured against the flat export, this
   reconstruction lands at a mean error of 1.3/255 — the comp's four blurred
   #0A0A0A blobs turned out to be invisible over the near-black base and were
   dropped rather than carried as four more nodes.
   NOTE: Figma stretches the lineup into its 1090x529 box (the source plate is
   1024x795), so both axes are pinned here on purpose — do not "fix" the
   aspect, it would no longer match the comp. */
.compat__stage {
  position: relative;
  overflow: hidden;              /* clips every plate's overhang */
  width: min(1720px, 100%);
  aspect-ratio: 1440 / 1129;
  /* less negative than before (user 2026-08-21): the lineup grew 20%, so the
     stage was riding up into the headline and CTA — pulling it down is what
     lifts the copy clear */
  margin: clamp(-330px, -19vw, -80px) auto 0;
  /* melts the stage's top edge into the section black, as the flat plate did */
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 30%);
          mask-image: linear-gradient(180deg, transparent 0, #000 30%);
}
.compat__stage > * { position: absolute; max-width: none; }
.compat__sky {
  inset: 0;
  background: linear-gradient(90deg, #050404, #1e2128);
}
/* the rocks: the comp's plate scaled up 6% about its centre purely to buy
   bottom overhang — its bottom row is fully opaque, so the edge must never
   climb into frame when the layer drifts up */
.compat__mtn {
  left: -57.842%;
  top: 28.722%;
  width: 223.115%;
  height: 74.266%;
  transform: translate3d(0, calc(var(--par, 0px) * .35), 0);
}
/* the comp's bottom fade, between the rocks and the lineup */
.compat__haze {
  left: 0;
  right: 0;
  bottom: 0;
  height: 30.115%;
  background: linear-gradient(0deg, #0a0a0a, rgba(10, 10, 10, 0));
}
/* the lineup, 20% smaller than the comp (user 2026-08-18). It keeps the comp's
   TOP edge rather than its base — shrinking off the base dropped the visual
   centre 53px and left the air lopsided (119px above, 21px below).
   Travels ~5x the rocks — that gap IS the parallax. The plate's own bottom row
   is still mid-shadow (alpha 72), so the last 9% is faded out: the solid bodies
   end at 90.7% of the source, so this only tapers the shadow and the cut can
   never read as an edge wherever the drift leaves it. */
.compat__rig {
  /* The old plate was a stretched export, so both axes were pinned. The new
     lineup render (user 2026-08-19) is a true 2048x1590 and must keep its own
     aspect — height is free, and the box is anchored by its FEET so the extra
     height grows upward into the frame's air rather than pushing the units
     down through the fade. */
  left: 21.7%;              /* re-centred for the narrower box */
  bottom: 1.2%;             /* sits lower in the frame (user 2026-08-21) */
  width: 56.6%;
  height: auto;
  /* belt and braces: if a height ever lands on this box again, contain keeps
     the render from stretching — the old rule squashed it 1.6x wide */
  aspect-ratio: 2048 / 1590;
  object-fit: contain;
  /* grows as it rides up the page: --parp is the driver's raw 0→1 pass, and
     the origin sits at the feet so the lineup scales up out of the ground
     rather than swelling from its middle (user 2026-08-21) */
  /* starts 20% up on where it used to sit and still grows on the way past
     (user 2026-08-21) */
  transform: translate3d(0, calc(var(--par, 0px) * 1.8), 0)
             scale(calc(1.2 + var(--parp, 0) * 0.14));
  transform-origin: 50% 100%;
  will-change: transform;
  -webkit-mask-image: linear-gradient(180deg, #000 91%, transparent 100%);
          mask-image: linear-gradient(180deg, #000 91%, transparent 100%);
}
/* The comp's frame bottoms out at #090909, not black, so it met the next
   section's #000 as a visible seam. This pins the fade to the SECTION (not the
   stage) so it stays put while the layers drift. */
.compat::after {
  content: "";
  position: absolute;
  inset: auto 0 0;
  height: clamp(90px, 13vh, 170px);
  background: linear-gradient(180deg, rgba(0, 0, 0, 0), #000 78%);
  pointer-events: none;
}
@media (max-width: 640px) {
  /* the 1440-wide comp scales down to nothing on a phone — blow the stage up
     and re-centre it so the lineup still reads, and pull the head tighter.
     The global `img { max-width: 100% }` (line 83) capped the old 135% here,
     so the plate never grew and the negative margin just shoved it off centre. */
  .compat { padding-top: clamp(64px, 9vh, 92px); }
  .compat__stage {
    max-width: none;
    width: 178%;
    margin-left: -39%;   /* (100 - 178) / 2 — keeps the lineup centred */
    margin-top: clamp(-360px, -55vw, -150px);
  }
  .compat::after { height: clamp(70px, 10vh, 110px); }
}
/* the drift is a desktop nicety; on a phone it costs a scroll-linked write per
   frame for a few px of travel, so the plates sit still */
@media (max-width: 640px), (prefers-reduced-motion: reduce) {
  .compat__mtn, .compat__rig { transform: none; will-change: auto; }
}
/* WIDE WINDOWS (user 2026-09-21). The stage stops growing at 1720px, and past
   that it sat as a box in the section's black: the rocks and the lit side of
   the sky both ended in a hard vertical edge. The rocks plate is 2.23 frames
   wide, so there was always more of it to show. From here up the STAGE runs
   edge to edge and only the FRAME keeps the cap: every horizontal measure is
   the same fraction as above, taken off --frame and hung from the centre, so
   the crop inside the frame does not move and the window just opens onto more
   plate. Heights still read the stage, which the strut holds at the frame's
   own height, so nothing vertical changes either. */
@media (min-width: 1721px) {
  .compat__stage {
    /* a %, so it resolves against whatever reads it: the stage's width in the
       plates' left/width, the sky's own box in its gradient. The min() is for
       a classic scrollbar, which leaves the section just under the cap while
       the window is already over it — there this all equals the rules above. */
    --frame: min(1720px, 100%);
    width: 100%;
    aspect-ratio: auto;
  }
  .compat__stage::before {
    content: "";
    display: block;
    width: var(--frame);
    aspect-ratio: 1440 / 1129;
  }
  /* the same ramp across the frame, holding its end colours outward. The lit
     side falls back to the dark one over the stretch where the rocks fade out
     (92%-100% of the plate, see below), so a window wider than the plate ends
     dark on both sides rather than in a bare column of sky; up to ~3350px that
     stretch is still off screen. */
  .compat__sky {
    background: linear-gradient(90deg,
      #050404 calc(50% - var(--frame) / 2),
      #1e2128 calc(50% + var(--frame) / 2),
      #1e2128 calc(50% + var(--frame) * .97424),
      #050404 calc(50% + var(--frame) * 1.15273));
  }
  .compat__mtn {
    left: calc(50% - var(--frame) * 1.07842);   /* -57.842% of the frame */
    width: calc(var(--frame) * 2.23115);
  }
  .compat__rig {
    left: calc(50% - var(--frame) * .283);      /* 21.7% of the frame */
    width: calc(var(--frame) * .566);
  }
}
/* The plate runs out at ~3700px of window on the left and ~3970px on the right.
   Its ends are faded from 3000px, while both are still off screen, so they melt
   into the sky when they arrive instead of cutting — and nothing pops on the
   way there. */
@media (min-width: 3000px) {
  .compat__mtn {
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
            mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  }
}
/* DITHER (user 2026-09-21) — the sky climbs some 30 levels across the whole
   frame and is then multiplied by the stage's top fade, so every 1/255 step is
   a patch dozens of pixels across and the two ramps draw a grid of blocks; the
   wider the window, the easier it is to see. Same remedy as the noise tile over
   the Premier screenshots' veil: scatter each step boundary over a few pixels
   and the eye integrates a smooth ramp. It lives INSIDE the stage, so the top
   fade tapers it with everything else and the section's black stays pure, and
   UNDER the lineup, which has no ramp to hide. */
.compat__stage::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: .05;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}
.compat__rig { z-index: 1; }

/* ==========================================================================
   FORESIGHT+ ECOSYSTEM BAND — ported from the Foresight+ page (user
   2026-08-18) so the Premier page shows the offering as the wired hub
   instead of a bullet row. Same component, Premier's own CTAs beneath.
   ========================================================================== */

.thisis {
  position: relative;
  /* doubled top air (user 2026-08-14): the band needs room off the hero */
  /* the Foresight+ page tucks the course marquee under this band, so its
     bottom padding was tiny. Here the venues band follows straight after,
     so the CTA needs real air beneath it (user 2026-08-18). */
  padding: clamp(90px, 12vh, 160px) 0 clamp(120px, 16vh, 210px); /* halved off compat (user 2026-08-18) */
  background: var(--black);
  overflow: clip;
  text-align: center;
}
.thisis__head { position: relative; z-index: 2; }
.thisis__head .h2--xl { line-height: .875; } /* the mock's tight two-line leading */
/* the break is a desktop-only device: tablet and phone read it on one line */
@media (max-width: 1024px) {
  .thisis__br { display: none; }
  .thisis__head .h2--xl { line-height: 1.02; }
}
.thisis__lede {
  position: relative;
  z-index: 2;
  /* Text xl/Semibold — 20/30, 60% on-brand white, 586px box (3 lines) */
  max-width: min(760px, 92vw); /* wider, single-flow copy (user 2026-08-14) */
  margin: clamp(1.75rem, 2.78vw, 2.5rem) auto 0;
  padding-inline: 24px;
  font-size: clamp(1.05rem, 1.39vw, 1.25rem);
  font-weight: 584;
  line-height: 1.5;
  color: rgba(245, 245, 246, .6);
}

/* ---- the band: two halves of the frame, hub overlapping between them ---- */
.thisis__stage {
  --wp: 0;
  --msc: .8;          /* module scale, picked in the lab */
  --lw: .5px;         /* wire hairline */
  --ch: 5px;      /* comet head height */
  --cblur: 1.09px;
  position: relative;
  z-index: 2;
  width: min(1440px, 96vw);
  margin: clamp(48px, 6.5vw, 93px) auto 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 0;
  align-items: center;
}
.thisis__col { display: contents; }
.thisis__col--premier > * { grid-column: 1; }
.thisis__col--app > * { grid-column: 2; }

/* ---- wire ---- */
.thisis__wire {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  pointer-events: none;
  opacity: min(1, calc(var(--wp) * 5)); /* first pixels ease in, not pop */
}
.thisis__trace {
  fill: none;
  /* pathLength=1 normalises both sides, and the offset comes from ONE custom
     property so the driver writes a single value instead of six inline styles */
  stroke-dasharray: 1;
  stroke-dashoffset: var(--dash, 1);
}
.thisis__trace--core  { stroke: rgba(255, 255, 255, .92); stroke-width: var(--lw); }
/* the glow is drawn as real strokes under the core, not left to a blur, so
   it stays glued to the line at every width */
.thisis__trace--glow  { stroke: #fff; stroke-width: calc(var(--lw) * 3.6); opacity: .16; }
.thisis__trace--glow2 { stroke: #fff; stroke-width: calc(var(--lw) * 8.8); opacity: .06; }
.thisis__hot { /* short bright segment of the rail under the hub */
  fill: none;
  stroke: url(#thisis-hotgrad);
  stroke-width: 1.5;
  opacity: calc(var(--wp) * .9);
}
/* the comet: a soft blob riding the drawn tip (Figma 12692:3689 — a quick
   ramp to a peak just behind the tip, then a long smooth fade) */
.thisis__comet {
  pointer-events: none;
  filter: blur(var(--cblur));
  /* Rasterise the blur ONCE and then only move the layer. Without this the
     browser re-runs the filter every frame; `mix-blend-mode: screen` used to
     sit here too and forced a backdrop read-back per frame — on black a white
     shape screens to itself, so dropping it costs nothing visually. */
  will-change: transform;
  /* NO css opacity here: it would beat the SVG presentation attribute the
     driver animates, and the comet would never show */
}

/* ---- hub ---- */
.thisis__cube {
  position: relative;
  z-index: 3;
  grid-column: 1 / -1;
  grid-row: 1;
  justify-self: center;
  margin: 0 0 0 2vw; /* the file sits the hub just right of centre */
  width: calc(clamp(285px, 30.9vw, 444px) * var(--msc)); /* +50% (user 2026-08-14) */
}
.thisis__cube img {
  position: relative;
  width: 100%;
  height: auto;
  will-change: transform;
  animation: thisis-float 6.5s ease-in-out infinite;
}
@keyframes thisis-float {
  0%, 100% { transform: translateY(-4px) rotate(-1.1deg); }
  50%      { transform: translateY(4px) rotate(1.1deg); }
}
.thisis__cube::before { /* junction glow blooms as the wire completes */
  content: "";
  position: absolute;
  inset: -40%;
  z-index: -1;
  background: radial-gradient(closest-side, rgba(82, 148, 255, .34), rgba(59, 165, 255, .12) 46%, transparent 72%);
  opacity: calc((.25 + var(--wp) * .75) * .2);
}

/* ---- products: both renders carry their own alpha, so the wire passes
   behind them without a black plate cutting it ---- */
.thisis__scene {
  grid-row: 1;
  position: relative;
  z-index: 2;
  justify-self: center;
  align-self: center;
}
.thisis__scene img { width: 100%; height: auto; }
/* the vertical offsets are set by thisis-wire.js, which aligns each product
   to the measured rail (40.3% / 45.1% of its box, per the file) */
.thisis__scene--bay   { width: calc(clamp(240px, 25.7vw, 370px) * var(--msc)); }
.thisis__scene--phone { width: calc(clamp(180px, 18.78vw, 271px) * var(--msc)); }

/* ---- captions on shared rows across both columns ---- */
.thisis__prodlogo {
  grid-row: 2;
  justify-self: center;
  align-self: center;
  height: calc(clamp(18px, 1.65vw, 23.7px) * var(--msc));
  width: auto;
  margin-top: clamp(14px, 1.6vw, 22px);
  position: relative;
  z-index: 2;
}
.thisis__prodname {
  grid-row: 2;
  justify-self: center;
  align-self: center;
  margin-top: clamp(14px, 1.6vw, 22px);
  font-size: calc(clamp(1.5rem, 2.22vw, 2rem) * var(--msc));
  font-weight: 640;
  letter-spacing: -.02em;
  line-height: 1;
  color: #fff;
  position: relative;
  z-index: 2;
}
.thisis__col p {
  grid-row: 3;
  justify-self: center;
  max-width: calc(min(424px, 92%) * var(--msc));
  margin-top: clamp(10px, .97vw, 14px);
  font-size: calc(clamp(1rem, 1.25vw, 1.2rem) * var(--msc));
  line-height: 1.6;
  color: rgba(255, 255, 255, .82);
  position: relative;
  z-index: 2;
}
/* scene → caption gap halved on desktop/tablet (user 2026-08-19). The slack
   isn't the margin — it's row 1's height (the hub) plus each scene's rail
   offset, measured 43/61px at 842w and 67/99px at 1440w, so the pull-up is
   vw-linear and freezes with the stage's 1440px cap. The whole caption block
   (rows 2-4) rides up together; phones keep the stacked spacing. */
@media (min-width: 701px) {
  /* Both captions share grid row 2 and centre in it, which is what keeps the
     PREMIER logo and the Mobile App label on one line — so the pull-up has to
     be IDENTICAL on the two. (Splitting it per element to equalise the two
     gaps is what knocked them out of alignment, user 2026-08-19.) The gaps
     under each render differ on their own — the phone sits higher on the rail
     than the bay — and that difference is inherent to row 1, not to these
     margins. Shared pull-up: 67/99 → 27/58 at desktop. */
  .thisis__prodlogo,
  .thisis__prodname { margin-top: calc(clamp(14px, 1.6vw, 22px) - min(2.8vw, 40px)); }
}
.thisis__col .link-arrow {
  grid-row: 4;
  justify-self: center;
  margin-top: clamp(9px, .85vw, 12px);
  position: relative;
  z-index: 2;
}

/* ---- dot field: the footer's halftone shader pooled around the hub ---- */
.thisis__dots {
  position: absolute;
  z-index: 0;
  pointer-events: none;
}

/* ---- entrance: the driver owns these, not the global reveal observer,
   so the hub can land before the wire and the columns ---- */
/* the reveal rides `translate`, NOT `transform` — the scenes carry their
   rail alignment in `transform`, and a shared property would cancel it */
.thisis__rv {
  opacity: 0;
  translate: 0 28px;
  /* promoted BEFORE the entrance runs: letting the browser create these
     layers on the first animated frame is what makes the reveal stutter */
  will-change: opacity, translate;
  transition: opacity .9s var(--ease) var(--d, 0s),
              translate .9s var(--ease) var(--d, 0s);
}
.thisis__rv.in { opacity: 1; translate: none; }
/* …and released once it has landed, so the layers are not kept forever */
.thisis__stage.is-settled .thisis__rv,
.thisis__stage.is-settled .thisis__cube img { will-change: auto; }


/* one CTA under the band now (user 2026-08-18): the column links and the
   trial footnote came off, so this is just the single button, centred */
.thisis__tail { position: relative; z-index: 2; margin-top: clamp(48px, 7vh, 84px); }
.thisis__tail .plusband__cta { margin-top: 0; display: flex; justify-content: center; }

/* stacked band on phones (user 2026-08-18): cube -> Premier -> App run
   vertically, the wire chaining down between them */
@media (max-width: 700px) {
  .thisis__stage {
    display: flex;
    flex-direction: column;
    align-items: center;
    row-gap: clamp(40px, 11vw, 64px);
  }
  .thisis__cube { order: 1; margin: 0; width: clamp(200px, 56vw, 270px); }
  .thisis__col { display: flex; flex-direction: column; align-items: center; }
  .thisis__col--premier { order: 2; }
  .thisis__col--app { order: 3; margin-top: clamp(24px, 8vw, 48px); }
  .thisis__col--premier .thisis__prodlogo { margin-top: clamp(-34px, -8vw, -20px); } /* hugs the bay render */
  .thisis__scene--bay { width: min(72vw, 330px); }
  .thisis__scene--phone { width: min(56vw, 255px); }
  .thisis__col p { max-width: 30ch; }
}

/* sub header appears on scroll only, on tablet/phone (user 2026-08-18) */
@media (max-width: 1024px) {
  .localnav {
    transform: translateY(-14px);
    opacity: 0;
    pointer-events: none;
    transition: opacity .35s var(--ease), transform .35s var(--ease);
  }
  .localnav.scrolled {
    transform: none;
    opacity: 1;
    pointer-events: auto;
  }
}

/* return-scroll global header on tablet/phone (user 2026-08-18) */
@media (max-width: 1024px) {
  .gnav--fixed {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 70;
    transform: translateY(-100%);
    transition: transform .35s var(--ease);
  }
  .gnav--fixed.gnav--reveal { transform: translateY(0); }
}

/* --- the Premier mark leads the hero now that the local nav is gone
   (user 2026-08-18): sits where the bracketed eyebrow was --- */
.phero__logo {
  width: clamp(178px, 17vw, 268px);
  height: auto;
  margin: 0 auto clamp(18px, 2.4vh, 30px);
}

/* ==========================================================================
   COURSE CARD (user 2026-08-21) — replaces the baked 551x600 PNGs with a real
   component: a clean photo plus live type. Per the brief this card carries no
   bookmark, no download button and no difficulty dots — just where it is,
   what it is, and how it plays. Proportions follow the old export (551:600)
   so the marquee's rhythm is unchanged.
   ========================================================================== */

.crs { flex: none; width: var(--cw); will-change: transform; }
.crs__card {
  position: relative;
  margin: 0;
  aspect-ratio: 551 / 600;
  /* 8% of the card, not of the viewport: the PNGs this replaced carried a
     constant ~22px radius on a 275px card, and a vw-based clamp only reached
     that above a 1571px window — every real screen got a tighter corner than
     the original. --cw is the card width, so this tracks it. */
  border-radius: calc(var(--cw) * .08);
  overflow: clip;
  background: #0b0d0f;
  container-type: inline-size;   /* type scales with the card, not the page */
  /* The belt rotates each card ±3deg (the arc wheel in main.js). Without its
     own compositing layer the rounded clip is a geometric op applied at
     composite time, with no alpha ramp to rotate — so the corners stair-step
     at these shallow angles. The cards this replaced were PNGs whose rounded
     corners were baked in as an antialiased alpha ramp, which is why the old
     belt read smooth. Promoting the card rasterises the clip WITH antialiasing
     once, and the GPU then filters that texture through the rotation. */
  will-change: transform;
  backface-visibility: hidden;
}
.crs__art {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* The type sits on a plain black ramp over the bottom quarter (user
   2026-08-24). This replaced a progressive blur — three blurred copies of the
   art, masked at increasing depths — which softened the bottom third of every
   card and read as airbrushed. These are 4K course renders and the section's
   whole claim is "rendered in 4K", so holding them sharp to the edge matters
   more than the depth effect. Dropping it also takes 3 of the 4 <img> per card
   and every blur() out of the compositing path. */
.crs__card::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(180deg,
    rgba(0, 0, 0, 0) 62%,
    rgba(0, 0, 0, .72) 82%,
    rgba(0, 0, 0, .90) 100%);
}
.crs__body {
  position: absolute;
  z-index: 1;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 0 8.5cqw 8cqw;
  text-align: center;
}
.crs__loc {
  margin: 0 0 2.2cqw;
  font-family: var(--mono);
  font-size: 3.1cqw;
  font-weight: 400;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .82);
}
.crs__name {
  margin: 0;
  font-size: 7.4cqw;
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: -.02em;
  color: #fff;
}
.crs__spec {
  margin: 3cqw 0 0;
  font-family: var(--mono);
  font-size: 3.1cqw;
  font-weight: 400;
  letter-spacing: .06em;
  color: rgba(255, 255, 255, .74);
}


/* ======================================================================
   FAQ · "Premier FAQs"                 (Figma 1462:7775, 2026-08-26)

   El mismo sistema de la landing de mobile (mobile.css, "FAQs"
   composition): sección oscura, placa de estudio blureada, dos columnas
   reales, glass "Mix" en la fila abierta y el módulo CTA. Lo que cambia
   es la pieza de arriba: en vez de la mano con el teléfono, una ventana
   centrada (870x580 en el frame de 1440) con el render del sim bay, que
   js/faqv3.js va rotando por los cinco launch monitors con el noise-melt
   del showcase de 4K. El título va DEBAJO de la ventana, no detrás.

   Todo scopeado a .faq--v3: las reglas base .faq / .qa (la sección clara
   que esto reemplaza) quedan intactas, y este bloque va al final del
   archivo así sus repetidas le ganan a la base por orden.
   ====================================================================== */
.faq--v3 {
  position: relative;
  overflow: hidden;
  padding: 0 0 var(--faq-foot, 200px);
  background: var(--black);
  --faq-inset: 22px;   /* = el padding vertical del summary (26-4): texto a
                          la misma distancia de los cuatro bordes de la card */
}

/* --- la placa: la MISMA de la landing de mobile (la silueta en niebla
   oscura, mobile/assets/faq-blur.webp copiada acá), pedido 2026-08-26 —
   la azul clara del Figma de Premier arrancaba la sección en claro y su
   borde inferior asomaba como un gradiente cortado detrás de las filas.
   Con la de mobile el principio y el final son oscuros.
   Caja y posición las escribe js/faqv3.js, porque `top` y `height` en %
   resolverían contra el ALTO de la sección, que crece con cada fila
   abierta; y la ancla es la LISTA: la primera fila cae en el 80% de la
   placa, en su cola desvanecida — eso es lo que hace legibles las filas
   (la misma lección medida en mobile: 7.9:1 anclada a la lista contra
   2.6:1 pinneada al techo). --- */
.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;
  max-width: none;     /* styles.css:87 capa img a 100% y pierde el encuadre */
  /* proporcional: el fade de bordes de un blur es ~3x su radio, y a 60px
     fijos se come el sobrante lateral en pantallas chicas */
  filter: blur(clamp(24px, 4.2vw, 60px));
  /* el blur muestrea más allá de los bordes y mete el negro de la página
     como viñeta; escalar esconde el fleco */
  transform: scale(1.08);
}
.faq--v3 .faq__bg::after {
  content: ""; position: absolute; inset: 0;
  /* tres capas, de abajo hacia arriba: 10% negro plano, scrim del 28%
     (AA: sin él las filas de mobile medían 2.6:1), y la rampa negra del
     62% superior, que hunde el arranque en negro */
  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);
}

/* --- la ventana del render. El webp trae alfa real: el bay flota sobre la
   placa, sin marco. El canvas del melt y el still comparten caja; cuando el
   GL levanta (faq--gl-live) el still se apaga y queda de fallback. --- */
.faq--v3 .faq__grid {
  position: relative; z-index: 1;
  display: block;
  /* 80% of the v3 spec (was 180 at the 1440 frame): the whole module rides
     higher so the bay window meets the venues CTA sooner — the seam padding
     above was already trimmed and the leftover read as dead air
     (user 2026-08-28) */
  padding-top: clamp(77px, 10vw, 144px);
}
.faq--v3 .faq__stage {
  position: relative;
  width: min(870px, 92vw);
  /* The Falcon render is an alpha cutout sitting in a 3/2 file with ~12%
     transparent padding above the bay and ~19% below (measured row by row,
     2026-08-28) — at the file's own aspect the box reserved all of it as
     dead layout space, which is what read as "mucho espacio" between the
     venues CTA and the bay. 2/1 + the 40% object-position below shows the
     image band from 10% to 85%: every visible pixel kept, a quarter of the
     box's height gone. */
  aspect-ratio: 2 / 1;
  margin-inline: auto;
}
.faq--v3 .faq__gl,
.faq--v3 .faq__still {
  position: absolute; inset: 0;
  width: 100%; height: 100%; max-width: none;
  /* parallax de scroll, escrito por js/faqv3.js en el WRAPPER (.faq__stage):
     las custom properties heredan, así canvas y still viajan juntos. Sin
     ventana de recorte: el render es un recorte con alfa, como la mano. */
  transform: translate3d(0, var(--faq-par, 0px), 0);
  will-change: transform;
}
.faq--v3 .faq__still { object-fit: cover; object-position: 50% 40%; transition: opacity .35s var(--ease); }
.faq--v3 .faq__gl { opacity: 0; transition: opacity .35s var(--ease); }
.faq--v3.faq--gl-live .faq__gl { opacity: 1; }
.faq--v3.faq--gl-live .faq__still { opacity: 0; }

/* --- el título, DEBAJO de la ventana (la base .faq__head es sticky). El
   box del Figma solapa 25px con la ventana; el leading .8 es lo que lo
   hace leer debajo. --- */
.faq--v3 .faq__head {
  position: static;
  text-align: center;
  /* El solape negativo del Figma (-25, luego -4.5vw) tenía sentido cuando
     la ventana terminaba en un 19% de relleno transparente — el título
     montaba sobre aire. Con la ventana recortada a 2/1 (2026-08-28) el mat
     llega casi al borde del box y el solape lo pisaba, así que el margen
     se da vuelta: positivo, dimensionado para restituir el aire visual que
     había entre el mat y el título antes del recorte (~70px a 1440). */
  /* -20% sobre el aire visual (2026-08-28). El gap que se ve = este margen
     + la tira transparente al pie de la ventana (~5% de su alto), así que
     el margen absorbe la reducción completa de ambos. */
  margin-top: clamp(22px, 2.4vw, 35px);
}
.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
     (user 2026-09-04) the list holds its own air under the title */
  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;
}
/* 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 eso muere
   en la calle interna y el margen del 94vw, pero con los gutters chicos de
   tablet/teléfono el filete quedaba fuera del margen del sitio (la misma
   lección de la landing de mobile). El padding devuelve el sangrado adentro:
   la card cae exacto en el borde del contenedor. */
@media (max-width: 1024px) {
  .faq--v3 .faq__list { padding-inline: var(--faq-inset, 22px); }
}

/* tipografía por ancho — dos columnas parten la medida en desktop; en
   teléfono la composición apila y el cuerpo vuelve a crecer */
@media (max-width: 700px) {
  .faq--v3 .faq__grid { padding-top: clamp(48px, 10vw, 96px); }
  .faq--v3 .qa summary { font-size: 17px; }
  .faq--v3 .qa__panel p { padding-right: var(--faq-inset, 10px); font-size: 16px; }
}
@media (min-width: 701px) {
  .faq--v3 .qa summary { font-size: 16px; line-height: 24px; }
  .faq--v3 .qa__panel p { font-size: 16px; line-height: 1.6; }
  .faq--v3 { --faq-foot: 182px; }
}

/* ----------------------------------------------------------------------
   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 {
  /* same type as the button beside it, colour apart (user 2026-08-28):
     16/24/584, tracking INHERITED like the button's (declaring it in em
     re-resolves at 16px and lands 0.01px off the button's computed value) —
     the 20px/-2% it had made the pair read as two different voices */
  font-size: 16px; line-height: 24px; font-weight: 584;
  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: 14px; line-height: 22px; }
  .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: 13px; }
  .faq--v3 .faqcta__btn { padding: 8px 12px; font-size: 13px; }
}

/* Acá el head está en flujo (en mobile es absolute y el módulo se ubica con
   márgenes negativos contra el padding del grid): la distancia medida del
   sistema es título→módulo 27px y módulo→preguntas 72px, declarada directo. */
.faq--v3 .faq__cta { margin-top: 32px; margin-bottom: 72px; }  /* 27 + 20% (user 2026-08-26) */
@media (min-width: 701px) { .faq--v3 .faq__cta { margin-top: 32px; } }
@media (max-width: 700px) { .faq--v3 .faq__cta { margin-top: 22px; margin-bottom: 56px; } }

/* --- The tail of the FAQ section (2026-09-21).
   Air above only: .faq--v3 already ends on its 200px --faq-foot, and a
   margin under the line would double it. The link is held to one line —
   .link-arrow draws its underline as an absolutely positioned bar across
   the whole box, so a link that wrapped would get ONE rule spanning both
   lines instead of two. --- */
.faq--v3 .faq__more {
  margin: clamp(56px, 7vh, 88px) 0 0;
  text-align: center;
  font-size: 16px; line-height: 24px;
  color: rgba(255, 255, 255, .62);
}
/* a hair more than the word space it inherits (user 2026-09-21): at a plain
   space the question and the link read as one run of text. The margin sits
   OUTSIDE the link box, so .link-arrow's underline — an absolutely
   positioned bar at left:0/right:0 — does not stretch under the gap. */
.faq--v3 .faq__more .link-arrow { font-size: inherit; white-space: nowrap; margin-left: .35em; }
/* 44px of touch on a 24px line (user 2026-09-21), invisibly. NOT padding:
   .link-arrow is an inline-BLOCK, so vertical padding would grow its line box
   and push the section's foot down — and it would drag the underline with it,
   since ::after is pinned to the bottom of that box. This is a transparent
   ::before instead: absolutely positioned inside the link (which is already
   position:relative for the underline), so it adds nothing to layout and
   leaves ::after where it is. Height only — left/right stay on the link's own
   edges, so the strip never reaches over "Still have questions?" beside it,
   and the 10px it gains above and below fall in the empty air between the
   last question and the section's 200px foot. */
.faq--v3 .faq__more .link-arrow::before {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 50%;
  height: 44px;
  transform: translateY(-50%);
}
@media (max-width: 700px) { .faq--v3 .faq__more { margin-top: 44px; } }

@media (prefers-reduced-motion: reduce) {
  .faq--v3 .faq__gl { display: none; }
  .faq--v3.faq--gl-live .faq__still { opacity: 1; }
}
@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; }
}


/* ---------- the rendered scroll clip (user 2026-08-28) ----------
   Replaces .phero__zoom's three layers and the .phero__hi frame: the camera
   move is in the render, main.js only seeks it. Sized to cover the pin, since
   the sequence ends full-bleed on the course rather than on a plate. */
.phero__media:has(.phero__scroll) {
  /* the clip owns the pin: the sequence ends full-bleed on the course, so it
     needs the whole frame, not the 1450px plate the old layers sat on. The
     old negative margin went with them — that ate the dead space above the
     bay in the previous canvas, which this render does not have. */
  position: absolute;
  inset: 0;
  width: auto;
  margin: 0;
  z-index: 1;
}
.phero__scroll {
  display: block;
  width: 100%;
  height: 100%;
  /* contain, not cover: cover would crop the early frames, where the bay is
     small and high in the frame. The letterbox lands on the section's own
     black, so it is invisible. */
  object-fit: contain;
}

/* Phones: no pin, no scrub (user 2026-09-17 — the scrubbed clip stuttered
   on mobile). main.js swaps the video for its poster below this same
   breakpoint; here the section falls back into flow and the still is
   oversized and offset so the bay itself fills the width. The poster is the
   16:9 render with the bay sitting in roughly x 24-76%, y 43-92% of the
   frame, so 180% width centres it and the top offset (a share of the 3:2
   box's height) lifts the dead air above the enclosure out of view. */
@media (max-width: 700px) {
  .phero { height: auto; }
  .phero__pin {
    position: relative;
    height: auto;
    padding-bottom: 0; /* the statement below closes the section; .fsx brings its own top padding */
  }
  .phero__dim { display: none; }
  /* "Play real courses" comes back on phones as a plain title + lede between
     the still and the course cards (user 2026-09-18) — on desktop it lands
     over the last frames of the dive, which a phone never plays. It repeats
     the pair above the still rather than adding a third size: the words take
     the H1's ramp, the copy the hero lede's.
     !important on opacity/transform: js never writes them at this width on a
     phone, but a desktop window narrowed after load still carries the
     scrub's inline values, and the title would sit at opacity 0. */
  .phero__playreal {
    display: block;
    position: relative; /* in flow; positioned only so it can sit over the beams */
    inset: auto;
    z-index: 1;
    padding: 80px 16px 0; /* the still -> the statement (user 2026-09-18: doubled from 40) */
    text-align: center;
    /* the pin's beams are masked around 62% of its height and clipped at its
       bottom edge, which used to fall against the still. With the section
       now ending under this copy the clip showed as a hard blue edge on the
       right, so the block fades the beams to black over its top padding and
       the words sit on clean black. */
    background: linear-gradient(to bottom, transparent, var(--black) 80px);
    /* the H1's ramp sits on the box, not on the two spans, so the space
       BETWEEN them is set in it too — at the body size it read as
       "Play realcourses" */
    font-size: clamp(3.2rem, 8.5vw, 6rem); /* = .hero__title */
    font-weight: 700;
    line-height: 1.25;
    letter-spacing: -.02em;
  }
  .phero__pl1, .phero__pl2 {
    position: static;
    display: inline;
    font-size: inherit;
    line-height: inherit;
    letter-spacing: inherit;
    word-spacing: normal;
    white-space: normal;
    color: inherit;
    opacity: 1 !important;
    transform: none !important;
    will-change: auto;
  }
  .phero__plcopy {
    position: static;
    translate: none;
    width: auto;
    max-width: 64ch;
    margin: 1.4rem auto 0;
    font-size: clamp(1.1rem, 1.4vw, 1.25rem); /* = .hero__lede */
    font-weight: 500;
    line-height: 1.5;
    letter-spacing: 0;
    text-wrap: balance; /* keeps "in 4K." from sitting alone on the last line */
    color: var(--ink-d2);
    opacity: 1 !important;
    transform: none !important;
    will-change: auto;
  }
  .phero__media:has(.phero__scroll) {
    position: relative;
    inset: auto;
    width: 100%;
    aspect-ratio: 3 / 2;
    margin-top: 28px;
    overflow: hidden;
  }
  .phero__scroll {
    position: absolute;
    left: -40%;
    top: -52%;
    width: 180%;
    height: auto;
    max-width: none;
    aspect-ratio: 16 / 9;
    object-fit: contain;
    transform: none;
  }
}
@media (max-width: 480px) {
  /* the H1 drops to its one-line phone ramp here; the statement follows it */
  .phero__playreal { font-size: clamp(1.9rem, 8vw, 3.9rem); }
}

/* The button's display style must not override its initial/recovery hidden state. */
[data-course-retry][hidden] { display: none; }
