/* ==========================================================================
   Foresight+ — page-specific components.
   styles.css is the shared Premier design system, copied verbatim; everything
   here layers the Foresight+ page on top of it without touching the base.
   ========================================================================== */

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

/* Tighter leading per Figma 12045:91445 — 96/97 (≈1.01) instead of the
   Display 3xl ramp's 1.25. The mobile Figma frame keeps 118%, so small
   screens (3-line wraps) get that instead. */
.hero__title { line-height: 1.01; }
@media (max-width: 480px) {
  .hero__title { line-height: 1.18; }
}

/* --- VARIANT B hero (Figma 12598-45187, updated frame 2026-08-11):
   fully centered — bracketed PRODUCT eyebrow (swaps with the slide), H1,
   the phone-over-course still behind, centered lede + blue CTA low, and a
   full-width bottom tab row whose active underline IS the timer. */
.bhero {
  --bhero-rail: clamp(24px, 7vw, 140px); /* one grid: intro + tab row share it (user 2026-08-12) */
  position: relative;
  min-height: calc(100svh - 64px);
  display: flex;
  flex-direction: column;
  padding-top: calc(var(--nav-h, 64px) + clamp(28px, 5vh, 56px));
  overflow: clip;
  background: var(--black);
}
/* corner glow, ported from the app page's mhero (user 2026-08-11): blurred
   blue diagonals masked to the bottom-left and top-right corners, plus a
   faint dot lattice in the same two pockets */
.bhero::before {
  content: "";
  position: absolute;
  inset: -12%;
  z-index: 1;
  /* dialled back from the app page's values — this hero carries photography
     underneath, so the glow stays a hint in the corners */
  background:
    linear-gradient(112deg, transparent 6%, rgba(96, 150, 255, .5) 16%, transparent 28%),
    linear-gradient(112deg, transparent 26%, rgba(23, 82, 224, .45) 38%, transparent 50%),
    linear-gradient(112deg, transparent 56%, rgba(23, 82, 224, .4) 70%, transparent 84%);
  filter: blur(clamp(18px, 2.6vw, 40px));
  -webkit-mask-image:
    radial-gradient(70% 75% at 0% 100%, #000 30%, transparent 78%),
    radial-gradient(60% 66% at 100% 0%, #000 25%, transparent 78%);
  mask-image:
    radial-gradient(70% 75% at 0% 100%, #000 30%, transparent 78%),
    radial-gradient(60% 66% at 100% 0%, #000 25%, transparent 78%);
  pointer-events: none;
}
.bhero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background-image: radial-gradient(rgba(140, 180, 255, .9) 1px, transparent 1.4px);
  background-size: 16px 16px;
  opacity: .3;
  -webkit-mask-image:
    radial-gradient(42% 48% at 6% 98%, #000, transparent 72%),
    radial-gradient(36% 42% at 94% 2%, #000, transparent 72%);
  mask-image:
    radial-gradient(42% 48% at 6% 98%, #000, transparent 72%),
    radial-gradient(36% 42% at 94% 2%, #000, transparent 72%);
  pointer-events: none;
}
.bhero__slides { position: absolute; inset: 0; z-index: 0; } /* full-bleed, per the mock frames (user 2026-08-12) */
/* left blend: the photography dissolves into the hero black so the copy
   column reads clean (user 2026-08-12) */
.bhero__blend {
  position: absolute;
  inset: 0;
  z-index: 2;
  background: linear-gradient(to right, rgba(0, 0, 0, .8), rgba(0, 0, 0, .35) 38%, transparent 60%);
  pointer-events: none;
}
.bhero__slide {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 35%;
  opacity: 0;
  transition: opacity .8s var(--ease);
}
.bhero__slide.is-on { opacity: 1; }
/* per-slide focal points on the right-half stage (order: premier /
   foresight app / course pass) */
.bhero__slide:nth-child(1) { object-position: 50% 70%; scale: 1.08; translate: 3% -6%; } /* sim bay: smaller, higher, right */
.bhero__slide:nth-child(2) { object-position: 62% 45%; } /* tablet in hand */
.bhero__slide:nth-child(3) { object-position: 66% 42%; } /* course-card warp stack */
/* base fade into the page black (Figma: #0a0a0a ramp over the lower band) */
.bhero__fade {
  position: absolute;
  inset: auto 0 0;
  height: 38%;
  z-index: 2;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0), var(--black));
}
/* left-anchored static intro (user 2026-08-12, per mock): eyebrow, H1,
   lede and CTA anchor the page while the slides + tabs rotate behind */
.bhero__intro {
  position: relative;
  z-index: 3;
  margin-block: auto 0; /* sinks to the bottom — the art owns the frame (user 2026-08-18) */
  padding-left: var(--bhero-rail);
  /* tab row (~110px) + its bottom offset + 40px of air */
  padding-bottom: calc(clamp(20px, 3vh, 32px) + 110px + 40px);
  max-width: min(760px, 62vw);
  text-align: left;
}
.bhero__intro .eyebrow { justify-content: flex-start; }
.bhero .hero__title {
  font-size: clamp(2.7rem, 4.5vw, 4.9rem); /* bumped (user 2026-08-12) */
  line-height: 1.05;
  letter-spacing: -.03em;
  margin-top: 1.1rem;
  color: #fff; /* true white (user 2026-08-18) */
}
.bhero__br { display: none; }
@media (min-width: 1100px) {
  .bhero__br { display: inline; }
  .bhero__lede { max-width: none; }
}
.bhero__lede {
  max-width: 58ch;
  margin-top: 1.4rem;
  font-size: clamp(1.05rem, 1.25vw, 1.3rem);
  font-weight: 500;
  line-height: 1.5;
  color: rgba(255, 255, 255, .65);
}
.bhero__ctas { display: flex; align-items: center; margin-top: clamp(24px, 3.6vh, 40px); }
/* full-width bottom tab row (Figma Tabs): 16px Medium labels centered in
   equal thirds over a 12%-white hairline; the ACTIVE tab's 2px white
   underline is the TIMER — it loads left-to-right over the cycle */
.bhero__tabs {
  /* pinned to the very bottom of the section (user 2026-08-11) so the row
     never floats with the copy — and it sits ABOVE the site's launch-monitor
     ticker, which used to overlap the Course Pass label */
  position: absolute;
  left: var(--bhero-rail);
  right: var(--bhero-rail);
  bottom: clamp(20px, 3vh, 32px); /* lifted off the very edge (user 2026-08-11) */
  z-index: 6;
  display: flex;
  /* the rule now lives on each TAB, not the row, so the gap between tabs
     shows as a real break in the line (user 2026-08-11) */
  gap: clamp(6px, .7vw, 10px); /* tighter breaks still (user 2026-08-11) */
  background: linear-gradient(180deg, rgba(0, 0, 0, 0), rgba(0, 0, 0, .55));
}
/* each tab carries its product's name + description, with the progress
   track below — the description rides the timer so the copy is tied to
   what's on stage (user 2026-08-12, per mock) */
.bhero__tab {
  position: relative;
  flex: 1 0 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  /* Bottom-aligned (user 2026-08-19): only the ACTIVE tab shows its
     description, so an inactive one must stay compact — its title sitting
     right on its bar — while the active one grows UPWARD. Top-aligned it
     showed a dead gap where the description used to be, and the three
     progress bars stopped forming one line (measured 43px apart). */
  justify-content: flex-end;
  /* gap:0 on purpose — the space between title and bar is --bhero-tabgap
     alone. A flex gap here is paid TWICE by a collapsed tab (name→slot and
     slot→track) even though the slot is zero-high. */
  gap: 0;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  text-align: left;
  color: rgba(255, 255, 255, .75);
  cursor: pointer;
  transition: color .3s var(--ease);
}
.bhero__tab:focus-visible { outline: 2px solid rgba(120, 170, 255, .9); outline-offset: 6px; border-radius: 4px; }
.bhero__tabname {
  font-size: 17px;
  font-weight: 584;
  line-height: 1.3;
  color: rgba(255, 255, 255, .45);
  transition: color .3s var(--ease);
}
.bhero__tab.is-active .bhero__tabname,
.bhero__tab:hover .bhero__tabname,
.bhero__tab:focus-visible .bhero__tabname { color: #fff; }

/* ---- description: collapsed by default, open on the active tab ----
   (user 2026-08-19, benched in lab/shaders/bhero-tabs.html)
   The slot is a one-row grid going 0fr→1fr, which is the only way to
   transition to an automatic height. Nothing is reserved: a closed tab
   contributes exactly zero, so the strip only occupies what it shows. */
.bhero__desc-slot {
  display: grid;
  grid-template-rows: 0fr;
  width: 100%;
  transition: grid-template-rows .46s var(--ease);
}
.bhero__tabdesc {
  display: block;
  overflow: hidden;
  font-size: 14.5px;
  font-weight: 492;
  line-height: 1.45;
  color: rgba(255, 255, 255, .35);
  max-width: 44ch;
  opacity: 0;
  transition: opacity .3s var(--ease), color .3s var(--ease);
}
/* The air above the description must be CONTENT, not padding: padding is
   never crushed, so with padding-top a closed slot still stood 7px tall and
   the 16px gap measured 23. A block spacer gets clipped by overflow:hidden
   like any other content. */
.bhero__tabdesc::before { content: ""; display: block; height: .45rem; }

/* hover and focus open it too — focus so the copy is reachable with the
   keyboard, which any hover-only pattern silently drops */
.bhero__tab.is-active .bhero__desc-slot,
.bhero__tab:hover .bhero__desc-slot,
.bhero__tab:focus-visible .bhero__desc-slot { grid-template-rows: 1fr; }
.bhero__tab.is-active .bhero__tabdesc,
.bhero__tab:hover .bhero__tabdesc,
.bhero__tab:focus-visible .bhero__tabdesc {
  opacity: 1;
  color: rgba(255, 255, 255, .75);
  transition-delay: .16s; /* the gap opens before the words arrive */
}
/* only ever one open: pointing at an inactive tab closes the active one */
.bhero__tabs:has(.bhero__tab:not(.is-active):hover) .bhero__tab.is-active .bhero__desc-slot {
  grid-template-rows: 0fr;
}
.bhero__tabs:has(.bhero__tab:not(.is-active):hover) .bhero__tab.is-active .bhero__tabdesc {
  opacity: 0;
  transition-delay: 0s;
}
/* no pointer → no hover half; the active tab still shows its copy */
@media (hover: none) {
  .bhero__tab:hover .bhero__desc-slot { grid-template-rows: 0fr; }
  .bhero__tab.is-active .bhero__desc-slot { grid-template-rows: 1fr; }
}

/* short label: only the narrowest phones get it (see the media query below) */
.bhero__tabname--short { display: none; }

/* Shared description under the bars — phones only. Off everywhere else,
   where each tab carries its own. */
.bhero__tabsdesc { display: none; }
.bhero__track {
  display: block;
  width: 100%;
  height: 3px;
  /* the ONLY space between a collapsed tab's title and its bar — picked at
     16px on the bench (user 2026-08-19). Was .6rem plus two .45rem flex gaps. */
  margin-top: var(--bhero-tabgap, 16px);
  border-radius: 999px;
  background: rgba(255, 255, 255, .22);
  overflow: hidden;
}
.bhero__line {
  display: block;
  height: 100%;
  width: 100%;
  border-radius: inherit;
  background: #fff;
  transform: scaleX(0);
  transform-origin: left center;
  visibility: hidden;
}
.bhero__tab.is-active .bhero__line { visibility: visible; }
@media (prefers-reduced-motion: reduce) {
  .bhero__line { visibility: hidden !important; }
  .bhero__tab.is-active .bhero__track { background: #fff; }
}
@media (max-width: 700px) {
  /* no sub-burger on phones: brand left, CTA right, full spread */
  .localnav__burger, .localnav__sheet { display: none !important; }
  .localnav__inner { justify-content: space-between; }

  /* pared-back hero (user 2026-08-18): shorter copy stack, compact 3-up
     tab row with no descriptions, and room for the art to breathe */
  /* the hero fits the first screen INCLUDING the tab row (user 2026-08-18):
     the gnav above eats ~96px, so the section sizes to the remainder */
  .bhero { min-height: calc(100svh - 96px); }
  .bhero__slides { inset: 0 0 auto 0; height: 42svh; }
  .bhero__slide { object-position: 50% 32%; }
  .bhero__blend {
    /* the fade lives at the art's lower edge — the image stays clear above */
    inset: 0 0 auto 0;
    height: 42svh;
    background: linear-gradient(to top, var(--black) 4%, rgba(0, 0, 0, .3) 32%, transparent 64%);
  }
  .bhero__intro { padding-left: 16px; padding-right: 16px; padding-bottom: 18px; max-width: none; }
  .bhero .hero__title { font-size: clamp(2rem, 9vw, 2.4rem); }
  .bhero__lede { font-size: .9rem; max-width: 38ch; margin-top: .9rem; }
  .bhero__tabs {
    position: static;
    flex-direction: row;
    align-items: flex-start;
    gap: 14px;
    padding: 0 16px 24px;
    background: none;
  }
  .bhero__tabname { font-size: 13px; line-height: 1.25; }
  /* the per-tab descriptions do not fit beside three names on a phone — hide
     the SLOT, not just the description, or the collapsed grid row still sits
     in the column. The copy comes back below the bars, see .bhero__tabsdesc */
  .bhero__desc-slot { display: none; }
  .bhero__track { margin-top: .45rem; }

  /* the three names + bars keep their single row, and the active tab's
     description lands underneath them (user 2026-08-19) */
  .bhero__tabs { flex-wrap: wrap; }
  .bhero__tabsdesc {
    display: block;
    width: 100%;
    order: 9;                 /* always after the three tabs */
    margin: .85rem 0 0;
    font-size: 13px;
    font-weight: 492;
    line-height: 1.45;
    color: rgba(255, 255, 255, .72);
    /* The strip is anchored by its BOTTOM, so a description one line taller
       than the last would shove the bars upward on every rotation. main.js
       measures the tallest of the three at the current width and pins that
       exact height; this two-line floor is only the no-JS fallback. */
    min-height: calc(2 * 1.45em);
    transition: opacity .3s var(--ease);
  }
  .bhero__tabs.is-swapping .bhero__tabsdesc { opacity: 0; }
}

/* Narrowest phones only: at 320w "Foresight App" fills 84px of an 87px tab —
   3px of slack, and the fallback face before the webfont lands is wider, so
   it wraps to two lines and breaks the row. By 360w the tab is 105px and the
   full name is comfortable, so the short form stops here (user 2026-08-19). */
@media (max-width: 360px) {
  .bhero__tabname--full { display: none; }
  .bhero__tabname--short { display: inline; }
}

/* ==========================================================================
   2 · THIS IS FORESIGHT+ — ecosystem band (Figma 12691:770)
   The hub icon wired out to Premier and the Mobile App. Geometry is taken
   from the real 1440 render: each product is centred in a HALF of the frame,
   the rail runs through the hub's centre and crosses the plate at 40.3% of
   its box and the phone at 45.1% of its box. thisis-wire.js measures the
   paths from the DOM, so these numbers only need to stay proportional.
   --wp (0..1) is the draw progress, published by the driver.
   ========================================================================== */

.thisis {
  position: relative;
  /* doubled top air (user 2026-08-14): the band needs room off the hero */
  padding: clamp(180px, 26vh, 340px) 0 clamp(32px, 4.5vh, 56px);
  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) */
  /* Rise in with the wordmark and lede instead of simply appearing (user
     2026-08-24). thisis-wire.js already put `.in` on the hub — "1 · the hub
     lands" — it just had no CSS to drive. Like .thisis__rv this rides
     `translate`, NOT `transform`: the figure's transform is the scroll
     parallax (main.js) and the img's is the idle float, so sharing the
     property would cancel one of them. */
  opacity: 0;
  translate: 0 28px;
  will-change: opacity, translate;
  transition: opacity .9s var(--ease) var(--d, 0s),
              translate .9s var(--ease) var(--d, 0s);
}
.thisis__cube.in { opacity: 1; translate: none; }
.thisis__cube img {
  position: relative;
  width: 100%;
  height: auto;
  will-change: transform;
  /* Base tilt to the right (user 2026-08-24). On `rotate`, not inside the
     keyframes: the float owns `transform`, and the independent rotate property
     composes with it — so the idle wobble still reads as +-1.1deg either side
     of this, and the tilt survives prefers-reduced-motion, where the animation
     is switched off entirely. */
  rotate: 8deg;
  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;
}
.thisis__col .link-arrow {
  grid-row: 4;
  justify-self: center;
  margin-top: clamp(9px, .85vw, 12px);
  position: relative;
  z-index: 2;
}
/* scene → caption gap halved on desktop/tablet (user 2026-08-19), mirrored
   from Premier's band. The caption block (label + copy + textlink) rides up
   as one; phones keep the stacked spacing. */
@media (min-width: 701px) {
  /* identical pull-up on both captions — they share grid row 2, so anything
     asymmetric breaks the logo/label alignment (user 2026-08-19). The
     textlinks in row 4 ride up with the block. */
  .thisis__prodlogo,
  .thisis__prodname { margin-top: calc(clamp(14px, 1.6vw, 22px) - min(2.8vw, 40px)); }
}

/* ---- dot field: the footer's halftone shader pooled around the hub ---- */
.thisis__dots {
  position: absolute;
  z-index: 0;
  pointer-events: none;
  opacity: .6; /* dialed back behind the opener (user 2026-08-20) */
}

/* ---- 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,
.thisis__stage.is-settled .thisis__cube img { will-change: auto; }

.thisis__playhead { margin-top: clamp(180px, 24vh, 320px); } /* doubled (user 2026-08-14) */
.thisis__playhead .lede { margin-inline: auto; margin-top: .9rem; max-width: 44ch; }
.thisis .coursepass__marquee { margin-top: clamp(4px, 1.5vh, 20px); padding-bottom: 0; }

@media (max-width: 700px) {
  /* stacked band: block · wire · hub · wire · block. The rail simply turns
     vertical — thisis-wire.js measures it the same way and the comet
     orients itself along the run, so nothing else changes. */
  .thisis__stage {
    display: flex;
    flex-direction: column;
    align-items: center;
    width: min(560px, 92vw);
    margin-top: clamp(96px, 25vw, 140px); /* doubled off the lede (user 2026-08-14) */
  }
  .thisis__col {
    display: flex;
    flex-direction: column;
    align-items: center;
    width: 100%;
  }
  /* chain order (user 2026-08-18): cube first, then Premier, then App —
     the wire drops from the cube into each image in DOM order, so no
     flipping needed and the line never crosses typography */
  .thisis__cube {
    order: 1;
    width: clamp(200px, 56vw, 270px);
    margin: 0 0 clamp(46px, 15vw, 90px); /* the air the first run crosses */
  }
  .thisis__col--premier { order: 2; }
  .thisis__col--app { order: 3; margin-top: clamp(76px, 21vw, 128px); } /* breathing room off the Premier group */
  .thisis__scene--bay,
  .thisis__scene--phone { transform: none; }
  .thisis__scene--bay { width: 100%; }
  .thisis__scene--phone { width: 72%; }
  .thisis__col--premier .thisis__prodlogo { height: 18px; margin-top: clamp(-34px, -8vw, -20px); } /* hugs the bay render (user 2026-08-18) */
  .thisis__prodname { height: auto; }
  .thisis__prodname { margin-top: clamp(30px, 8vw, 44px); } /* tripled (user 2026-08-14) */
  /* Text md/Regular — 16/24, the same ramp the membership tier lists use */
  .thisis__col p {
    margin-top: 8px;
    margin-inline: auto;
    font-size: clamp(.95rem, 1.15vw, 1rem);
    line-height: 1.5;
    max-width: 34ch;
  }
  .thisis__col .link-arrow { margin-top: 10px; }
}

@media (prefers-reduced-motion: reduce) {
  .thisis__stage { --wp: 1; }
  .thisis__trace { stroke-dashoffset: 0 !important; }
  .thisis__comet { display: none; }
  .thisis__cube img { animation: none; }
  .thisis__rv,
  .thisis__cube { opacity: 1; translate: none; transition: none; }
}

/* Get started toggle (user 2026-08-12): segmented pill under the tiers
   head; each segment owns a panel — Foresight+ tiers or the pass cards */
.tiers__toggle {
  position: relative;
  display: flex;
  gap: 4px;
  width: max-content;
  margin: clamp(26px, 3.6vh, 42px) auto 0;
  padding: 4px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .08);
  border: 1px solid rgba(255, 255, 255, .08);
}
/* the white pill travels between segments instead of swapping (user 2026-08-18):
   JS measures the active seg and feeds --tx/--tw, so the unequal label widths
   still land exactly on the button box */
.tiers__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);
}
/* set once the first measure lands, so the pill never flies in from the left */
.tiers__toggle.is-measured .tiers__thumb { opacity: 1; }
.tiers__toggle:not(.is-measured) .tiers__thumb { transition: none; }
.tiers__seg {
  position: relative;
  z-index: 1;
  padding: 10px 24px;
  border: 0;
  border-radius: 999px;
  background: none;
  font: inherit;
  font-size: 15px;
  font-weight: 584;
  color: rgba(255, 255, 255, .75);
  cursor: pointer;
  transition: color .25s var(--ease);
}
.tiers__seg:hover { color: #fff; }
.tiers__seg.is-active { color: #0a0a0b; }
/* fallback: no JS, no measure — the active seg paints its own pill */
.tiers__toggle:not(.is-measured) .tiers__seg.is-active { background: #fff; }
@media (prefers-reduced-motion: reduce) {
  .tiers__thumb { transition: none; }
}
/* feats -> crest belt air matches body -> feats (32px, user 2026-08-12);
   the belt still pins to the card foot via its own margin-top: auto */
.pass-card__feats { margin-bottom: 32px; }
/* the two feature lines carry the tier-card check (user 2026-08-18); the
   "membership required" line stays plain — it is a condition, not a feature */
.pass-card__feats li {
  position: relative;
  padding-left: 30.755px; /* 18.755 check + 12 gap, same as .tier-card li */
  line-height: 1.5;
}
.pass-card__feats li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 2px;
  width: 18.755px;
  height: 18.755px;
  border-radius: 50%;
  background: #c3cbd6; /* = .tier-card--silver */
  -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;
}
/* Pro: blue disc with a solid black tick, so it cannot be a knockout —
   the shader behind the card would show through. Two-tone SVG instead. */
.pass-card--pro .pass-card__feats li::before {
  -webkit-mask: none;
          mask: none;
  background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><circle cx="10" cy="10" r="9" fill="%230348d9"/><path d="M14.2 7.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" fill="%23000"/></svg>') center / contain no-repeat;
}
/* opt-out kept local to the pass cards — .li-plain carries no rule globally.
   The requirement drops to the footnote pairing from the design system
   (.footnote scale + Alpha/white-50) so it reads as a condition, not a perk */
.pass-card__feats li.li-plain {
  padding-left: 0;
  margin-top: 6px;
  font-size: .8rem;
  color: var(--ink-d3);
}
.pass-card__feats li.li-plain::before { content: none; }

.tiers__links {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
}
.tiers__linksep { color: rgba(255, 255, 255, .4); }
/* ---- panel swap (user 2026-08-19) ----
   The two panels are 1328px and 751px tall, so display:none→block dropped the
   rest of the page by 577px in one frame. js/main.js locks this wrapper to the
   outgoing height, fades that panel out, swaps, then glides the wrapper to the
   incoming height while the new cards rise in. `clip` is what keeps the taller
   panel from spilling out while the box is still growing. */
.tiers__panels { position: relative; }
.tiers__panels.is-swapping {
  overflow: clip;
  transition: height .5s var(--ease);
}
.tiers__panel { display: none; }
.tiers__panel.is-active { display: block; }
.tiers__panel.is-leaving { animation: tiers-out .2s var(--ease) forwards; }
/* the cards come in one after the other — --i is set per card by main.js */
.tiers__panel.is-entering .tier-card,
.tiers__panel.is-entering .pass-card {
  animation: tiers-in .44s var(--ease) both;
  animation-delay: calc(var(--i, 0) * 70ms);
}
@keyframes tiers-out {
  to { opacity: 0; transform: translateY(-6px); }
}
@keyframes tiers-in {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .tiers__panels.is-swapping { transition: none; }
  .tiers__panel.is-leaving,
  .tiers__panel.is-entering .tier-card,
  .tiers__panel.is-entering .pass-card { animation: none; }
}
.tiers .lede { margin-inline: auto; }

/* ==========================================================================
   2 (retired) · THE MEMBERSHIP
   ========================================================================== */

/* The membership intro rides INSIDE the mask panel as Premier's vt__sub —
   the section head is gone, so the deck starts right at the section top. */
.vt__sub .eyebrow { margin-bottom: 1rem; }
/* the stacked-tile membership section is back (user 2026-08-11), matching
   the Premier page's Foresight+ block — .plus__head handles the centering */
.plus .stack { margin-top: clamp(48px, 7vh, 80px); }

/* the calado art is gone, so the line owns the panel — dead centre */
.vt__sub {
  top: 50%;
  transform: translate(-50%, -50%);
  width: min(1080px, 90vw);
  display: flex;
  flex-direction: column;
  align-items: center;
  /* the whole block fades on the way out; JS drives --eco-out */
  opacity: var(--eco-out, 1);
  transition: none;
}
.vt.is-rising .vt__sub { opacity: var(--eco-out, 1); transform: translate(-50%, -50%); }

/* +20% word size on desktop (user 2026-08-03): 141.068 → 169.28, tracking
   stays −2% of the size. Narrow screens are unaffected in practice — sizeMask
   (--ms) already clamps the word to 84% of the panel width. */
.vt__masktext {
  font-size: 169.28px;
  letter-spacing: -3.386px;
}

/* Hero over the video (user 2026-08-03): 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 from Premier is neutralised here. */
.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; }
/* panel travel is back, so the original 215vh budget stands */
/* Hero stage: the mobile page's wash + dot mesh, ported verbatim from
   mobile.css (.mhero::before/::after) — masks included, since those are what
   give it its shape. */
/* Aurora plate (user 2026-08-07, replaces the takeover video): bright cyan
   bloom at the top dissolving through rich blue into black by ~80% height —
   the internal tools landing gradient. The dot field canvas reacts to the
   pointer on top of it. */
.vt--hero .vt__frame {
  opacity: var(--plate, 1);
  /* the takeover video is back (user 2026-08-11) — the gradient plate is
     retired; the video element fills the frame */
  background: var(--black);
}


/* ONE pinned stage. There is no second panel sliding up any more — that slide
   was the hard line. The hero layer dissolves out and the ecosystem layers
   dissolve in, both in place. */
.vt__pin { background: var(--black); }
.vt__runway { height: 420vh; }
.egrid, .vt__scrim { opacity: var(--gin, 0); }
.vt__sub {
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: 3;
}
/* frosted region behind the line (Zellerfeld ref): backdrop blur feathered out
   by a radial mask so it melts into the grid with no edge */
.vt__frost {
  position: absolute;
  left: 50%;
  top: 50%;
  width: min(1240px, 86vw);
  height: min(72vh, 680px);
  transform: translate(-50%, -50%);
  z-index: 2;
  background: radial-gradient(55% 55% at 50% 50%, rgba(0, 0, 0, .62) 0%, rgba(0, 0, 0, .38) 45%, transparent 75%);
  opacity: var(--gin, 0);
  pointer-events: none;
}
.vt__hero { z-index: 4; }

.vt__panel { background: var(--black); } /* the calado SVG used to paint this */
/* longer runway: the hero holds, the drift decelerates, and the exit gets
   real distance instead of being crammed into the last 12% */
/* trimmed (user 2026-08-07): the run-out after the grid exit was a full
   screen of nothing — the membership section now arrives on its heels.
   The negative bottom margin makes the NEXT section ride up over the pin's
   last viewport, so the grid exits underneath the incoming light band
   instead of leaving a black frame. */
.vt--hero { height: 520vh; margin-bottom: -100vh; }

.vt__hero {
  z-index: 2;
  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 for copy over the video */
  pointer-events: none;
  /* copy and scrim dissolve on the same scroll ramp as the plate behind them,
     so the whole hero goes to black together. (The old rule that did this via
     .is-rising was lost in the restructure.) */
  opacity: var(--plate, 1);
  transition: none;
}
.vt__hero .container { pointer-events: auto; }
.vt__hero .cta-row { justify-content: center; }


/* ==========================================================================
   MEMBERSHIP DECK — Choose how you play (Figma 12552-2665)
   One card front-and-centre on the light band, the other two peeking behind
   its top edge; the tab bar underneath shuffles the deck. data-pos (set in
   main.js) is the card's place in the stack: 0 front, 1 behind, 2 back.
   ========================================================================== */

.steps { position: relative; z-index: 2; background: var(--light, #f5f5f6); padding: clamp(96px, 13vh, 160px) 0 clamp(110px, 15vh, 180px); }
.steps > .container:first-of-type { text-align: center; }
.steps > .container:first-of-type .lede { margin-inline: auto; }

/* Zellerfeld rhythm (user 2026-08-07): the card is a contained object with
   generous air on every side; type inside keeps its size */
.deck { width: min(920px, 76vw); margin: clamp(40px, 5.5vh, 56px) auto 0; }
/* the mass the cards slide beneath — a soft linear mask instead of a hard
   clip (user 2026-08-07). The fade zones sit in the gaps: the top one midway
   between the headline and the cards (above the resting peeks), the bottom
   one midway between the cards and the tabs. Anything a card overhangs past
   the gradient simply isn't painted, so the swipe-out and drop-in dissolve
   through the edges. */
.deck__mask {
  /* stagger flipped (user 2026-08-10, Lassie ref): rear cards now peek BELOW
     the front card, so the roomy fade zone lives at the bottom (peeks + exit
     dissolve) and the top only needs exit-fade headroom under the headline */
  /* top band trimmed to fit the tighter header gap (user 2026-08-10) so
     exiting cards dissolve before they reach the lede's background; bottom
     band still clears the peeks (opaque to 40px past the stack) and fades
     inside the widened tab gap */
  padding: 48px 64px 80px;
  margin: -48px -64px -80px;
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 48px, #000 calc(100% - 40px), transparent 100%);
  mask-image: linear-gradient(180deg, transparent 0, #000 48px, #000 calc(100% - 40px), transparent 100%);
}
.deck__stack {
  position: relative;
  aspect-ratio: 1300 / 680; /* taller card (user 2026-08-07) */
}
.deck__card {
  position: absolute;
  inset: 0;
  border-radius: 28px;
  overflow: clip;
  background: var(--black);
  box-shadow: 0 24px 60px rgba(0, 0, 0, .16);
  transition: transform .5s var(--ease);
  will-change: transform;
}
.deck__card img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.deck__scrim { position: absolute; inset: 0; background: rgba(0, 0, 0, .45); }
.deck__card[data-pos="0"] { transform: none; z-index: 3; }
.deck__card[data-pos="1"] { transform: translateY(28px) scale(.955); z-index: 2; }
.deck__card[data-pos="2"] { transform: translateY(52px) scale(.915); z-index: 1; }
/* leaving forward: the front card lifts up over the mask's top edge, then
   main.js teleports it below the deck and it rises back in at the rear */
.deck__card.is-exit {
  transform: translateY(-112%);
  transition: transform .45s var(--ease); /* matches the pill's glide */
  z-index: 4;
}
/* going backward: the LAST card slides down out of the stack's bottom
   instead, then re-enters from above the deck and drops to the front */
.deck__card.is-exit-back {
  transform: translateY(115%) scale(.915);
  transition: transform .45s var(--ease);
  z-index: 1;
}


.deck__copy {
  /* the Premier 4K-Remastered caption, verbatim (user 2026-08-07): anchored
     bottom-left on a 3px rule, title + body at the reference type ramp */
  position: absolute;
  left: clamp(24px, 3.4vw, 48px);
  bottom: clamp(24px, 5svh, 48px);
  max-width: 620px;
  padding-left: clamp(14px, 1.4vw, 20px);
  border-left: 3px solid rgba(255, 255, 255, .92);
  color: #fff;
  text-align: left;
}
/* the app page's Plan-your-game stack ramp (user 2026-08-07) */
.deck__title {
  font-size: clamp(1.1rem, 1.4vw, 1.4rem);
  font-weight: 584;
  line-height: 1.3;
}
.deck__lede {
  margin-top: 8px;
  max-width: 440px;
  font-size: clamp(.9rem, 1vw, 1rem);
  font-weight: 400;
  line-height: 1.5;
  color: rgba(255, 255, 255, .72);
}

/* segmented pill bar under the deck. The active pill is ONE sliding
   indicator (built in main.js) that glides between tabs — Figma 12552-3247:
   glossy black pill (top highlight), Text md/Semibold, shadow-sm. */
.deck__tabs {
  position: relative;
  display: flex;
  overflow: clip; /* the stretch can't poke past the end caps (the CP bounce) */
  width: min(640px, 100%);
  /* the rear peeks hang ~32px below the stack, so add that back on top of
     the header gap — the VISUAL gap (peek → tabs) then matches header → card
     (user 2026-08-10: even air above and below the deck) */
  margin: calc(clamp(40px, 5.5vh, 56px) + 32px) auto 0;
  padding: 0;
  background: #fff;
  border: 1px solid rgba(0, 0, 0, .06);
  border-radius: 999px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, .06);
}
.deck__ind {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  border-radius: 999px;
  background: #000; /* texture retired (user 2026-08-11) — clean solid pill */
  box-shadow:
    0 1px 2px rgba(16, 24, 40, .06),
    0 1px 3px rgba(16, 24, 40, .1); /* shadow-sm */
  /* no bounce (user 2026-08-10) — a clean glide, plus a subtle whiff of
     stretch: the pill elongates mid-flight and relaxes on arrival */
  transition: transform .5s var(--ease), width .5s var(--ease);
  will-change: transform;
}
.deck__ind.is-stretch { animation: pill-stretch .5s var(--ease); }
@keyframes pill-stretch {
  0%   { scale: 1 1; }
  45%  { scale: 1.05 .985; }
  100% { scale: 1 1; }
}
@media (prefers-reduced-motion: reduce) { .deck__ind.is-stretch { animation: none; } }
.deck__tab {
  position: relative;
  z-index: 1;
  flex: 1;
  padding: 17px 0;
  border: 0;
  border-radius: 999px;
  background: none;
  font: inherit;
  font-size: 1rem; /* Text md/Semibold */
  font-weight: 584;
  line-height: 1.5;
  color: var(--ink-l1, #000);
  cursor: pointer;
  transition: color .35s var(--ease);
}
.deck__tab:hover:not(.is-active) { color: rgba(0, 0, 0, .55); }
.deck__tab.is-active { color: #fff; }
.deck__tab:focus-visible { outline: 2px solid var(--blue-light); outline-offset: 3px; }

@media (max-width: 899px) {
  .deck__stack { aspect-ratio: 4 / 5; }
  .deck__card[data-pos="1"] { transform: translateY(20px) scale(.96); }
  .deck__card[data-pos="2"] { transform: translateY(38px) scale(.92); }
  .deck__tab { padding: 14px 0; font-size: .95rem; }
}
@media (prefers-reduced-motion: reduce) {
  .deck__card, .deck__copy, .deck__tab, .deck__ind { transition: none; }
}

/* --- "An ecosystem that carries your game." -----------------------------
   The whole line is present from the start, just far out of focus. Scrolling
   resolves it — words lead each other only slightly, so it reads as one mass
   coming into focus rather than six words popping in. Scroll past and the
   block defocuses and fades as one thing. JS sets --f per word, --eco-out on
   the block. */

.eco {
  margin: 0 auto;
  /* same voice as the Premier flyover statement (.pstate__title) */
  font-size: clamp(1.9rem, 4.4vw, 3.4rem);
  font-weight: 700;
  line-height: 1.18;
  letter-spacing: -.015em;
  color: #fff;
  max-width: 24ch; /* lands the copy on 3–4 lines, like the Premier screen */
  text-wrap: balance;
}
/* the phrase animates as ONE piece: slightly oversized and deep out of focus,
   scaling down into place as it sharpens, then zooming down + defocusing out */
.eco {
  opacity: var(--top, 0);
  filter: blur(var(--tb, 42px));
  transform: scale(var(--tsc, 1.18));
  transform-origin: 50% 50%;
  will-change: filter, opacity, transform;
}

@media (prefers-reduced-motion: reduce) {
  .eco__w { opacity: 1; filter: none; }
  .eco__golfer { opacity: 1; }
  .eco__golfer { opacity: 1; filter: none; transform: none; }
  .vt__sub { opacity: 1; filter: none; }
}

/* --- Fly-in card grid behind the ecosystem line -------------------------
   Six columns of square placeholders. Each starts pushed off-frame and out of
   focus — further from centre = further out and blurrier — and assembles to a
   clean aligned grid as you scroll. JS writes --x/--y/--b per column. */

.egrid {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  padding: 8px;
  overflow: clip;
  pointer-events: none;
}
.egrid__col {
  display: flex;
  flex-direction: column;
  gap: 8px;
  /* taller than the frame and centred on it, so the drift can never expose an
     edge — the fill reads as endless */
  height: auto;
  justify-content: start;
  transform: translate3d(var(--x, 0vw), var(--y, 0vh), 0);
  filter: blur(var(--b, 0px));
  will-change: transform, filter;
}
.egrid__card {
  flex: none;
  width: 100%;
  aspect-ratio: 1;
  border-radius: 16px;
  background: #1c1c1c;
  overflow: clip;
}
.egrid__card img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover; /* fill the tile — no letterboxing, no gaps */
}

/* scrim between the grid and the copy */
.vt__scrim {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, .28);
  pointer-events: none;
}


@media (max-width: 899px) {
  .egrid { grid-template-columns: repeat(2, 1fr); gap: 8px; padding: 8px; }
  .egrid__col { gap: 8px; }
}

@media (prefers-reduced-motion: reduce) {
  .egrid__col { transform: none !important; filter: none !important; }
}

/* --- section padding: Course Pass had NO padding of its own, so its eyebrow
   sat flush against the neighbouring section. It takes the app page's
   .library ramp. (The coaches section this used to share with was scrapped
   2026-08-26.) */
.coursepass { padding: clamp(96px, 14vh, 170px) 0; }

/* --- section headers: centered, matching Premier and the app page -------
   styles.css centers .tiers/.library/.venues heads; this page's coursepass
   section had no equivalent rule, so it fell back to left-aligned. */
.coursepass > .container:first-of-type { text-align: center; }
.coursepass > .container:first-of-type .lede { margin-inline: auto; }
.coursepass__sub { text-align: center; }
.coursepass__sub .lede { margin-inline: auto; }
/* the heading overlaps the fan's dissolved bottom — paint above its ::after
   ("Add the courses" is back on the h3-lg ramp, user 2026-08-10: one display
   headline per section — so the lift covers both steps) */
.coursepass .h2,
.coursepass .h3-lg,
.coursepass .lede { position: relative; z-index: 2; }
/* the marquee's stock bottom padding left a dead black gap before the
   pricing block — pull the sub-beat up into the flow */
.coursepass__marquee.fsx { padding-bottom: clamp(56px, 8vh, 96px); }
.passes-cta { text-align: center; margin-top: clamp(28px, 4vh, 44px); }

/* ==========================================================================
   FEATURE ORBIT — cylinder carousel before the tiers (user 2026-08-10).
   The ring is a true 3D cylinder segment: each item sits at its angle and
   radius (set in main.js via --a / --r), the ring counter-rotates to face
   the current card, and the stage's perspective does the rest. Containers
   are intentionally empty for now; captions ride underneath, on the
   analysis-gallery caption ramp.
   ========================================================================== */

.orbit {
  position: relative; /* anchors the dot mesh canvas */
  /* orbit leads the dark run again (user 2026-08-10 re-swap, with the
     "What your membership unlocks" intro): full top after the white deck
     band, tighter bottom so the tiers peek underneath */
  /* bottom +25% (user 2026-08-28, half of the wider orbit->tiers seam:
     the other half is on .tiers' padding-top below — both legs grow 25%
     so the seam does too, and neither section's own rhythm shifts) */
  padding: clamp(72px, 9.75vh, 126px) 0 clamp(90px, 12.5vh, 145px); /* top +50% off the course marquee (user 2026-08-18) */
  background: var(--black);
  overflow: clip;
}
/* the pointer-reactive dot mesh (user 2026-08-11): 10% white at rest,
   80% white at the cursor, always the bottom layer of its section. Shared
   by the orbit, the tiers, and the course-library section. */
.dotmesh {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  z-index: 0;
  pointer-events: none;
}
.tiers, .coursepass { position: relative; }
.orbit > :not(.dotmesh),
.tiers > :not(.dotmesh),
.coursepass > :not(.dotmesh) { position: relative; z-index: 1; }
/* same voice as "Pick your membership" (user 2026-08-10) — the stock .h2
   ramp, just centered and on two lines */
.orbit .h2 { text-align: center; line-height: 1.1; }
.orbit .lede { text-align: center; margin-inline: auto; }
/* the tiers follow the carousel again (user 2026-08-10 re-swap) — back to
   the peek-under-the-carousel breathing room, +25% with the orbit's bottom
   (user 2026-08-28) */
.tiers { padding-top: clamp(70px, 10vh, 120px); }
/* all three CTAs bottom-aligned like Gold's (user 2026-08-10): the cards are
   equal-height flex columns, so auto margin sinks each button to the same
   baseline instead of trailing its list at whatever height it ends */
.tier-card__cta { margin-top: auto; }
/* the cards run a little taller (user 2026-09-04): air between the feature
   list and the Get Started capsule. On the ul, not the cta, so the three
   cards grow together off the tallest list and the capsules stay level. */
.tier-card ul { margin-bottom: clamp(20px, 2.2vw, 32px); }

.orbit__stage {
  --ocw: clamp(270px, 28vw, 420px); /* expanded (user 2026-08-10): the belt
                                       fills more of the frame */
  margin-top: clamp(64px, 9vh, 104px); /* more air under the header */
  perspective: 1150px; /* tighter camera (user 2026-08-11) — the edges bend
                          toward you noticeably more */
  perspective-origin: 50% 40%;
  /* the ends dissolve instead of cutting to slivers (user 2026-08-10):
     a horizontal mask over the flattened stage — the dot mesh reads
     through where the edge cards fade */
}
.orbit__ring {
  position: relative;
  pointer-events: none; /* decorative — far-side cards project huge invisible
                           layers that would otherwise swallow the nav's clicks */
  height: calc(var(--ocw) * .625 + 96px); /* card + caption */
  transform-style: preserve-3d;
  transition: transform .6s var(--ease);
  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));
  /* brightness dim, not opacity — opaque cards keep the dot mesh underneath */
  transition: opacity .6s var(--ease), filter .6s var(--ease);
  /* whole item (card + caption): a card crossing 90° mid-turn must never
     flash its mirrored backside */
  backface-visibility: hidden;
}
.orbit__card {
  width: 100%;
  aspect-ratio: 16 / 10;
  overflow: clip;
  backface-visibility: hidden; /* the far side of the ring never mirrors through */
  border-radius: 18px;
  background: #141416;
  border: 1px solid rgba(255, 255, 255, .09);
  box-shadow: 0 24px 60px rgba(0, 0, 0, .45);
}
.orbit__card img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Placeholder for a feature whose art has not landed (user 2026-08-24). Read
   deliberately as a blank: a flat neutral grey, lighter than the card's own
   #141416, so it is obviously a gap rather than a dark image. */
.orbit__ph {
  width: 100%;
  height: 100%;
  background: #3a3d42;
}
/* = .ag-tile__caption, dark skin */
.orbit__cap {
  margin-top: 18px;
  font-size: 17px;
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: -.017em;
  color: var(--ink-d2);
}
.orbit__cap h3 { display: block; font: inherit; color: var(--ink-d1); }
.orbit__cap p { display: block; margin: .35em 0 0; font-weight: 400; }

.orbit__nav {
  position: relative;
  z-index: 2; /* above the ring's projected layers */
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 24px;
  margin-top: clamp(56px, 8vh, 88px); /* more air above the arrows */
}
.orbit__arrow {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: rgba(255, 255, 255, .1);
  /* frosted over the dot mesh (user 2026-08-11): the dots pass behind and
     blur through the button instead of reading on top of it */
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  color: #f5f5f6;
  cursor: pointer;
  transition: background .3s var(--ease), opacity .3s var(--ease), transform .2s var(--ease);
}
.orbit__arrow svg { width: 26px; height: 26px; }
.orbit__arrow:hover:not(:disabled) { background: rgba(255, 255, 255, .18); }
.orbit__arrow:active:not(:disabled) { transform: scale(.94); }
.orbit__arrow:disabled { opacity: .35; cursor: default; }
/* The indicator is a dot row (user 2026-08-18): one dot per feature, 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 hero pills above already use. Shared with the Premier carousel. */
.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, 520px); perspective: 850px; } /* = tier-card width (user 2026-08-18) */
}
@media (prefers-reduced-motion: reduce) {
  .orbit__ring, .orbit__item, .orbit__dot { transition: none; }
}

/* --- venues: the band around the windowed plate is LIGHT here (user
   2026-08-11) — the neighbouring sections are light, so the black frame
   that showed before the clip expanded read as a bug */
/* the light override is gone (user 2026-08-26): styles.css already sets
   .venues to #000 — "what shows around the rounded window while it opens"
   — and that is what this page wants now that the light coaches section
   before it has been scrapped. */

/* (hero cursor-ring CTA removed, user 2026-08-11 — the blue button is
   the hero CTA) */
/* the launch-monitor ticker clears the hero's bottom tab row (user
   2026-08-11) — it used to sit on top of the Course Pass label */
.pill { bottom: 88px; }


/* ==========================================================================
   FORESIGHT+ OPENER — Figma 12809:72748 (user 2026-08-19)
   Metallic wordmark, the glass app icon over its halftone dot field, the
   one-account lede, then the two product plates. Proportions come off the
   comp's 1920 frame: wordmark 822, icon box 487, plate 739x936 with a 32px
   radius, the caption 20.5/27.3 DemiBold sitting 90px off the plate's foot.
   ========================================================================== */

/* the old band laid its hub and two columns out on a 2-col grid — this
   layout is a single centred stack, so the grid has to be undone */
/* this band opens the page now that the hero is gone, so the top air it
   carried for sitting *after* a hero is dead space (user 2026-08-19) */
.thisis {
  /* The icon+logo+lede block sits CENTERED between the nav and the plates
     (user 2026-08-31, red-line annotation): this padding and the plates'
     margin below split the leftover air evenly at the 1440 frame, both in
     vw so the balance survives other widths. */
  padding-top: clamp(40px, 6.1vw, 120px);
}
.thisis__stage {
  position: relative;
  display: block;
  text-align: center;
  width: min(1533px, 94vw);
  margin-top: clamp(8px, 1.4vw, 24px);
}

/* gnav rides OVER the hero, copied from the mobile page (user 2026-08-31):
   transparent with a white hairline, black back on hover/focus so the
   dropdowns and pills read as everywhere else. Out of flow, so the sky
   starts at the document top and the comp's frame maps 1:1 to the page. */
.gnav {
  position: absolute;
  inset: 0 0 auto;
  background: transparent;
  border-bottom: 1px solid rgba(255, 255, 255, .18);
  transition: background .28s var(--ease), border-color .28s var(--ease);
}
.gnav:hover,
.gnav:focus-within {
  background: #000;
  border-bottom-color: transparent;
}

/* --- backdrop: Figma 13054:9840, mountains pass (user 2026-08-31) --- */
.plushero__bg {
  /* One unit drives the whole backdrop: 1/1440 of the comp frame, FROZEN at
     the 1800px mark — past it the copy block is px-capped while a vw-scaled
     render kept growing until the whole ridge sank below the fold (seen at
     2560x1080, user 2026-09-01). */
  --bgu: min(1vw, 18px);
  position: absolute;
  inset: 0;
  z-index: 0;                 /* under .thisis__stage (z2); transparent below
                                 the mountains so the section's black shows */
  pointer-events: none;
}
/* A positioned z0 layer paints OVER in-flow section content, and on wide
   viewports (or with the parallax lag) the vw-sized mountains reach the
   next block — so everything after the stage is lifted above the backdrop
   (user 2026-08-31: the render must pass BEHIND "Play anytime, anywhere"). */
.thisis__playhead,
.thisis .coursepass__marquee {
  position: relative;
  z-index: 2;
}
/* the comp's Sky: a LATERAL ramp, near-black rising into blue-grey rightward */
.plushero__bg-sky {
  position: absolute;
  inset: 0 0 auto;
  height: calc(var(--bgu) * 58.3);   /* 840 on the 1440 frame */
  background: linear-gradient(90deg, #050404, #1e2128);
  /* the ramp's bottom edge is a hard line against the section's black — on
     desktop it hides behind the opaque render, but at phone widths it showed
     over the card (user 2026-09-01), so the edge fades itself out instead */
  -webkit-mask-image: linear-gradient(180deg, #000 82%, transparent);
  mask-image: linear-gradient(180deg, #000 82%, transparent);
}
/* the mountain render: a 2037x903 box at (-79,201) on the frame. The asset
   arrives PRE-CROPPED to the comp's framing (user 2026-09-01, 4074px = the
   box at @2x), so it fills the box width 1:1 and only its lower reaches are
   trimmed by the box — no overscan mapping any more. */
.plushero__bg-mtns {
  position: absolute;
  left: calc(var(--bgu) * -5.49);
  /* Past the 1440 frame the cards' page-y freezes (px-capped content) while
     the render's saddle sat ~150px BELOW them — only the flanking peaks
     showed and the centre read as void (user 2026-09-01: some mountain must
     clear the cards' top). The clamp lifts the box progressively to 230px
     by ~1920 and holds; below 1440 it is exactly 0, so the comp is intact.
     The box bottom rises with it — the fade ends in solid black, and the
     section's own black carries on to the next block seamlessly. */
  top: calc(var(--bgu) * 13.96 - clamp(0px, (100vw - 1440px) * .48, 230px));
  /* the max() keeps the frozen box full-bleed past the cap — the img rides
     at width:100%, so the few % of stretch lands on rock, invisibly */
  width: max(calc(var(--bgu) * 141.46), calc(100% + var(--bgu) * 5.49 + 2px));
  height: calc(var(--bgu) * 62.71);
  overflow: hidden;
  will-change: transform; /* main.js parallaxes the whole box — the fade
                             ends in solid black, so both edges slide clean */
}
.plushero__bg-mtns img {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: auto;
  max-width: none;
}
/* fade to black across the comp's last 300px, where the plates arrive */
.plushero__bg-mtns::after {
  content: "";
  position: absolute;
  inset: auto 0 0;
  height: 33.2%;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0), #000);
}

.plushero__mark {
  position: relative;
  z-index: 1;
  margin: 0 auto;
  width: min(486px, 33.8vw);   /* Figma 13054:9840 — 486 on the 1440 frame */
}
.plushero__mark img { display: block; width: 100%; height: auto; }

/* the icon overlaps the wordmark's baseline exactly as the comp does */
/* Figma 12809:72751 — a 560x288 plate blurred 157px, sitting behind the icon
   and a touch below its centre. Rebuilt in CSS rather than shipped as two
   blurred rasters: same shape, none of the weight. */
.plushero__glow {
  position: absolute;
  z-index: 2;                 /* over the dot field, under the icon */
  left: 50%;
  top: 0;
  width: min(560px, 58vw);
  aspect-ratio: 560 / 288;
  transform: translate(-50%, 26%);
  pointer-events: none;
  border-radius: 50%;
  background:
    radial-gradient(58% 60% at 50% 46%, rgba(178, 203, 255, .34), rgba(92, 132, 235, .16) 44%, rgba(0, 0, 0, 0) 76%);
  filter: blur(58px);
  opacity: 1;
  /* the smooth bloom breathes on the same slow clock the shader's aura uses,
     so the two read as one light rather than two effects stacked */
  animation: plushero-aura 26s ease-in-out infinite;
}
@keyframes plushero-aura {
  0%   { transform: translate(-50%, 26%) scale(1);      opacity: .70; filter: blur(58px) hue-rotate(0deg); }
  28%  { transform: translate(-54%, 22%) scale(1.12);   opacity: .84;   filter: blur(52px) hue-rotate(-8deg); }
  55%  { transform: translate(-46%, 30%) scale(1.04);   opacity: .76; filter: blur(64px) hue-rotate(6deg); }
  78%  { transform: translate(-52%, 24%) scale(1.16);   opacity: .84;   filter: blur(50px) hue-rotate(-4deg); }
  100% { transform: translate(-50%, 26%) scale(1);      opacity: .70; filter: blur(58px) hue-rotate(0deg); }
}
@media (prefers-reduced-motion: reduce) {
  .plushero__glow { animation: none; }
}
.plushero__cube {
  position: relative;
  z-index: 3;
  /* Figma 13054:9840 — the icon leads at ~190px visible on the 1440 frame,
     13px of air down to the wordmark. hero-cube.webp's solid body spans rows
     191-846 of its 1040 canvas, so the 190px of visible glass needs a 302px
     box. The box's baked-in bottom air (~44px rotated) is NOT pulled back:
     main.js's parallax holds the figure ~32px low while the hero is at the
     top of the page, and air − parallax + this margin lands the comp's 13px
     gap right where the reader sees it. Scrolling only lifts the icon away,
     so the gap never closes. */
  --w: clamp(145px, 15.17vw, 218px); /* −15% twice over (user 2026-08-31) */
  width: var(--w);
  /* the smaller box carries less baked-in bottom air — and once --w hits its
     170px floor the air shrinks faster than the viewport, so the give-back
     margin GROWS as the viewport narrows. The mountains pass opens the gap
     to the comp's ~26px (wordmark at 364 under the icon's 338). */
  margin: 0 auto clamp(23px, 35px - .84vw, 29px);
}
.plushero__cube img { display: block; width: 100%; height: auto; }

.plushero__lede {
  position: relative;
  z-index: 2;
  max-width: min(35em, 88vw);    /* Figma 13054:9840 — the 700px column */
  margin: clamp(16px, 1.7vw, 24px) auto 0;
  font-size: clamp(15px, 1.39vw, 20px);  /* Text xl — 20/30 on the 1440 frame */
  font-weight: 584;              /* Semibold */
  line-height: 1.5;
  letter-spacing: 0;
  color: var(--ink-d1, #f5f5f6);
}

@media (max-width: 700px) {
  /* the old band's stacked rules send the hub to the back of the flex column
     (order:1) and size it for the wire chain — the opener leads with the
     icon, so the DOM order rules and the icon takes a phone-scale box */
  .plushero__cube { order: 0; --w: clamp(123px, 34.85vw, 173px); }
  .plushero__mark { width: min(486px, 60vw); }
  .plushero__lede { font-size: 16px; }

  /* Phones open with DOUBLE the air over the icon (user 2026-09-01) */
  .thisis { padding-top: clamp(90px, 24vw, 140px); }

  /* Phone backdrop (user 2026-09-01): the desktop mapping left the render
     ending behind the icon and the pre-cards gap dead black. The mountain
     zooms to 250vw and drops so the ridge crosses between the lede and the
     cards — the px term in each calc() tracks the px-floored copy block
     (lede bottom ≈ 365 at 375 with the doubled top air — re-measured after the
     closing "Every session…" sentence came out, 2026-09-02; it was 413) against
     the vw-scaled render. */
  .plushero__bg-sky { height: calc(365px + 75vw); } /* runs to the mtns box
                        bottom, where the fade is already solid black */
  .plushero__bg-mtns {
    left: -55vw;
    top: calc(365px - 45vw); /* lifted ~45px (user 2026-09-01 annotation):
                        peaks graze the lede, the saddle rides mid-band */
    width: 250vw;
    height: 120vw;
  }
}

/* --- the two product plates --- */
.plushero__plates {
  position: relative;
  z-index: 2;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(14px, 1.6vw, 30px);
  width: min(1320px, 84vw);
  /* 7.5vw keeps the plates' top on the comp's red guide line (frame y 612)
     with the copy block re-centered above — its other half of the leftover
     air lives in .thisis's padding-top */
  margin: clamp(60px, 7.5vw, 150px) auto 0;
}
/* Below the 1440 frame the copy block floors in px while the mountains keep
   shrinking in vw, so the plates were landing right on the fade and the
   ridge had no room to read between the lede and the cards. Wider air here
   lets the render breathe (user 2026-09-01); ≤700's stacked layout is its
   own upcoming pass. */
@media (min-width: 701px) and (max-width: 1439px) {
  .plushero__plates { margin-top: clamp(120px, 14vw, 200px); }
}
/* Every number below is measured off the comp's 738.955 x 936.295 plate, so
   the composition holds at any width. cqw = 1% of the plate's own width,
   which is what keeps the caption and lockups in proportion to the plate
   rather than to the viewport. */
.plate {
  --edge: 10.8095%;          /* (100 − 78.381) / 2 — the feature list's left edge */
  position: relative;
  display: block;
  container-type: inline-size;
  aspect-ratio: 738.955 / 850; /* shortened from the comp's 936 (user 2026-08-20) */
  border-radius: clamp(18px, 1.7vw, 32px);
  overflow: clip;
  background: #0b0b0d;
  color: inherit;
}
.plate--premier { border: 2px solid rgba(255, 255, 255, .1); }

/* the mobile art is ~196% of the plate and offset — a plain centred cover
   crop throws the composition away. The premier render (SIAB v2, user
   2026-08-25) is framed tight already: it rides at its full height and
   only gives up its sides to the plate's narrower aspect */
.plate__art { position: absolute; max-width: none; object-fit: cover; }
.plate--premier .plate__art {
  height: 100%;
  width: auto;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  transition: transform .8s var(--ease);
}
.plate--premier:hover .plate__art { transform: translate(-50%, -50%) scale(1.03); }
.plate--mobile .plate__art {
  width: 195.274%;
  height: 106.653%;
  right: -13.539%;
  top: -3.376%;
  transition: transform .8s var(--ease);
}
.plate--mobile:hover .plate__art { transform: scale(1.03); }

/* the comp darkens the sim plate only — the app plate reads white on sky */
.plate--premier::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(180deg, #000 0%, rgba(0,0,0,0) 57.928%) top / 100% 68.034% no-repeat,
    linear-gradient(0deg, #000 0%, rgba(0,0,0,0) 100%) bottom / 100% 34.391% no-repeat;
  opacity: .76;                 /* .74 top / .78 bottom, split the difference */
}

.plate__label {
  position: absolute;
  z-index: 2;
  left: var(--edge);
  top: 8.58%;
  /* centered at rest via transform alone: 50cqw − edge − half of self */
  transform: translateX(calc(39.1905cqw - 50%));
  width: 45.136%;
  height: auto;
}
/* the app lockup is a mark + wordmark pair, sized and spaced off the plate */
.plate__label--mobile {
  display: flex;
  align-items: center;
  gap: 1.74cqw;
  width: auto;
}
.plate__label--mobile img { display: block; width: auto; height: auto; }
.plate__label--mobile img:first-child { width: 6.315cqw; }
.plate__label--mobile img:last-child  { width: 34.704cqw; }

.plate__cap {
  position: absolute;
  z-index: 2;
  left: 50%;
  top: 89.69%;
  transform: translateX(-50%);
  width: 78.381%;
  margin: 0;
  font-size: 2.775cqw;          /* 20.503px on the comp's 738.955 plate */
  font-weight: 584;             /* DemiBold */
  line-height: 1.333;
  color: #fff;
}

@media (max-width: 700px) {
  .plushero__plates { grid-template-columns: 1fr; }
  .plate { aspect-ratio: 4 / 5; }
  /* AFTER the base margin shorthand in source order, or it loses the tie:
     the cards give the phone ridge a band of its own before they arrive
     (widened per user 2026-09-01 — the ridge must read between text and card) */
  .plushero__plates { margin-top: clamp(130px, 36vw, 190px); }
}

/* --- plate hover (user 2026-08-19) -------------------------------------
   The plates no longer navigate, so hover is the payoff: the art settles
   back and desaturates, a cursor-tracked spotlight rakes across it, the
   caption hands off to the feature list, and each row wipes up on its own
   beat. Pointer-driven, but keyboard focus gets the identical state. */
.plate { cursor: pointer; outline: none; text-decoration: none; } /* navigates again (user 2026-08-20) */
.plate:focus-visible { box-shadow: 0 0 0 2px var(--blue, #0348d9), 0 0 0 6px rgba(3, 72, 217, .28); }

/* the light that follows the cursor — --mx/--my are written by main.js */
.plate__spot {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0;
  transition: opacity .5s var(--ease);
  background: radial-gradient(38% 30% at var(--mx, 50%) var(--my, 40%),
    rgba(190, 214, 255, .30), rgba(120, 160, 255, .10) 45%, rgba(0, 0, 0, 0) 72%);
  mix-blend-mode: screen;
}
.plate:hover .plate__spot,
.plate:focus-visible .plate__spot,
.plate.is-hot .plate__spot { opacity: 1; }

/* the art eases back so the type has somewhere quiet to land */
.plate--premier:hover .plate__art,
.plate--premier:focus-visible .plate__art,
.plate--premier.is-hot .plate__art { transform: translate(-50%, -50%) scale(1.08); filter: blur(14px) saturate(.68) brightness(.58); }
.plate--mobile:hover .plate__art,
.plate--mobile:focus-visible .plate__art,
.plate--mobile.is-hot .plate__art { transform: scale(1.08); filter: blur(14px) saturate(.68) brightness(.52); }
.plate__art { filter: blur(0) saturate(1) brightness(1); transition: transform 1.1s var(--ease), filter .8s var(--ease); }

/* the lockup lifts to make room — transform only, so the glide is one
   straight move (left+translate together jogged right before going left) */
.plate__label { transition: transform .7s var(--ease); }
.plate:hover .plate__label,
.plate:focus-visible .plate__label,
.plate.is-hot .plate__label { transform: translateX(0) translateY(-14%); }

/* caption steps aside for the list */
.plate__cap { transition: opacity .45s var(--ease), transform .6s var(--ease); }
.plate:hover .plate__cap,
.plate:focus-visible .plate__cap,
.plate.is-hot .plate__cap { opacity: 0; transform: translateX(-50%) translateY(14px); }

.plate__feats {
  position: absolute;
  z-index: 2;
  left: var(--edge);
  top: 44%;
  width: 78.381%;
  margin: 0;
  padding: 0;
  list-style: none;
  text-align: left;
  pointer-events: none;
}
.plate__feats li {
  position: relative;
  padding: 4.6cqw 0 3.4cqw;
  border-top: 1px solid rgba(255, 255, 255, .16);
  font-size: 3.05cqw;
  font-weight: 584;
  line-height: 1.2;
  color: #fff;
  /* each row wipes up behind its own edge */
  clip-path: inset(0 0 100% 0);
  opacity: 0;
  transform: translateY(14px);
  transition:
    clip-path .62s var(--ease) calc(var(--i) * 70ms),
    opacity   .5s  var(--ease) calc(var(--i) * 70ms),
    transform .62s var(--ease) calc(var(--i) * 70ms);
}
.plate__feats li:last-child { border-bottom: 1px solid rgba(255, 255, 255, .16); }
/* the hairline draws in from the left as the row lands */
.plate__feats li::before {
  content: "";
  position: absolute;
  top: -1px;
  left: 0;
  width: 100%;
  height: 1px;
  background: linear-gradient(90deg, rgba(255,255,255,.85), rgba(255,255,255,0));
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform .7s var(--ease) calc(var(--i) * 70ms + 90ms);
}
.plate:hover .plate__feats li,
.plate:focus-visible .plate__feats li,
.plate.is-hot .plate__feats li {
  clip-path: inset(0 0 0 0);
  opacity: 1;
  transform: none;
}
.plate:hover .plate__feats li::before,
.plate:focus-visible .plate__feats li::before,
.plate.is-hot .plate__feats li::before { transform: scaleX(1); }

@media (prefers-reduced-motion: reduce) {
  .plate__art, .plate__cap, .plate__label, .plate__feats li, .plate__feats li::before { transition-duration: .01ms; }
  .plate--premier:hover .plate__art { transform: translate(-50%, -50%); }
  .plate--mobile:hover .plate__art { transform: none; }
}
/* no hover on touch: show the features under a permanently dimmed plate */
@media (hover: none) {
  .plate__art { filter: blur(10px) brightness(.56) saturate(.75); transform: scale(1.06); }
  .plate__cap { opacity: 0; }
  .plate__feats li { clip-path: inset(0 0 0 0); opacity: 1; transform: none; }
  .plate__feats li::before { transform: scaleX(1); }
}


/* ---------- film band (user 2026-08-24) ----------
   A full-bleed beat of the product in motion, sitting between the course
   library and the feature ring. No ramps over the film (user 2026-08-24) —
   its own frame is near-black at the edges, so it meets the black sections
   either side on its own. */
.filmband {
  position: relative;
  background: #000;
  overflow: hidden;
  /* the band needs air either side or the card marquee lands straight on the
     film's top edge */
  padding: clamp(56px, 8vw, 132px) 0;
}
.filmband__frame { position: relative; }
.filmband__vid {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}
/* ======================================================================
   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;
  /* -40% de aire contra la sección de arriba (user 2026-08-26), SIN
     solapar: la primera vuelta fue un margen negativo y el negro de la
     FAQ amputaba el fade de la foto de .venues. La mitad del recorte va
     acá (180 -> 94; el frame 1464:7800 pone la ventana en y=72) y la
     otra mitad en el padding-bottom de .venues, abajo. */
  padding-top: clamp(48px, 6.5vw, 94px);
}
/* -30% more on desktop and tablet (user 2026-09-02): together with the
   matching cut to .venues__below's padding-bottom in styles.css the
   CTA-to-window distance drops from ~195 to ~137 at 1440. Phones keep the
   clamp above. */
@media (min-width: 701px) {
  .faq--v3 .faq__grid { padding-top: clamp(34px, 4.55vw, 66px); }
}
.faq--v3 .faq__stage {
  position: relative;
  width: min(870px, 92vw);
  aspect-ratio: 3 / 2;
  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; 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 del Figma eran -25px; el usuario pidió el título un 30% más
     cerca del render (2026-08-26): -40px extra a 1440, proporcional */
  margin-top: clamp(-65px, -4.5vw, -16px);
}
.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 commented
     out (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, ported
     from Premier): 16/24/584, tracking INHERITED like the button's — an em
     declaration re-resolves at 16px and lands 0.01px off its 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; } }

@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; }
}

/* ---- La composición "ecosistema" de la FAQ (Figma 1464:7800): el sim bay
   y el par iPad+iPhone en DOS capas, cada una con su propia tasa sobre el
   --faq-par que js/faqv3.js escribe en el stage — la de atrás viaja más
   lento que la del frente y el conjunto lee con profundidad.
   Geometría del frame de 1440x774: ventana 884x589 en (240,72), devices
   454x454 en (746,248) → left 57.23%, top 29.86%, ancho 51.4% del stage;
   el stage corre -4.29% (los -37.95px del Figma) porque los devices
   cuelgan del borde derecho y el conjunto se centra ópticamente. ---- */
.faq--v3 .faq__stage--eco { translate: -4.29% 0; }
.faq--v3 .faq__stage--eco .faq__bay,
.faq--v3 .faq__stage--eco .faq__devices {
  position: absolute;
  max-width: none;
  will-change: transform;
}
.faq--v3 .faq__stage--eco .faq__bay {
  inset: 0; width: 100%; height: 100%; object-fit: cover;
  transform: translate3d(0, calc(var(--faq-par, 0px) * .7), 0);
}
.faq--v3 .faq__stage--eco .faq__devices {
  left: 57.23%; top: 29.86%; width: 51.4%; height: auto;
  transform: translate3d(0, calc(var(--faq-par, 0px) * 1.45), 0);
}
@media (prefers-reduced-motion: reduce) {
  .faq--v3 .faq__stage--eco .faq__bay,
  .faq--v3 .faq__stage--eco .faq__devices { transform: none; }
}

/* (el override de padding-bottom de .venues se fue con el rebuild
   2026-08-28: la sección nueva no lleva padding en .venues — la costura
   vive en .venues__below, ya recortada al -30% en styles.css. Dejarlo
   SUMABA ~100px de aire nuevo sobre una regla pensada para el diseño
   anterior.) */

/* ======================================================================
   PRODUCT CARDS V2 — the "This is Foresight+" plates, redesigned
   (Figma 13053:9068 premier / 9074 mobile, hover 13053:9290; settings
   picked in the holocards2 lab, user 2026-08-31: tilt 12° · text parallax
   10px · art depth 6px · blur 9→45 comp-px · belt 18px/s · art breathing).
   The old .plate block above stays untouched but nothing renders it now.
   The comp frame is 657x833 — lengths in cqw are that comp over 100.
   ====================================================================== */

/* registered so the pointer ratios interpolate (strings otherwise) */
@property --rx { syntax: "<number>"; inherits: true; initial-value: 0; }
@property --ry { syntax: "<number>"; inherits: true; initial-value: 0; }

/* cards at 68% of the comp column (user 2026-08-31: first -20%, then -15%
   more): the grid narrows and every cqw-derived length inside the cards
   follows for free */
.plushero__plates {
  perspective: 1500px;
  width: min(898px, 57.12vw);          /* 68% of min(1320px, 84vw) */
  gap: clamp(9px, 1.09vw, 20px);
}

.card2 {
  position: relative;
  display: block;
  container-type: inline-size;
  aspect-ratio: 657 / 833;
  border-radius: clamp(18px, 1.7vw, 32px);
  overflow: clip;
  background: #0b0b0d;
  cursor: pointer;
  outline: none;
  text-decoration: none;
  color: inherit;
  will-change: transform;
  /* pointer tilt — main.js writes --rx/--ry in [-1,1]; smoothing the RATIOS
     lets transform, gradients and the type parallax glide as one. The
     opacity/translate pair keeps the .thisis__rv entrance intact: the reveal
     declares its own transition and this rule would silence it otherwise. */
  transform: rotateY(calc(-12deg * var(--rx))) rotateX(calc(12deg * var(--ry)));
  transition: --rx .18s linear, --ry .18s linear,
              opacity .9s var(--ease) var(--d, 0s),
              translate .9s var(--ease) var(--d, 0s);
}
.card2:focus-visible { box-shadow: 0 0 0 2px var(--blue, #0348d9), 0 0 0 6px rgba(3, 72, 217, .28); }

/* --- art: soft-focus at rest, drowned on hover --------------------------
   Blur numbers are the comp's, cqw-scaled (so they hold at any card size)
   and halved (CSS blur(σ) reads ~2x Figma's layer blur): 9 → .685, 45 → 3.42.
   At REST the image is SHARP — blur(0) is FINAL (user, twice): the blur is
   the hover's move only. The overscale keeps the blur's dissolving edges
   (~3σ) outside the card. */
.card2__art {
  position: absolute;
  max-width: none;
  filter: blur(0);
  scale: 1.03;
  translate: calc(var(--rx) * 6px) calc(var(--ry) * 6px);  /* depth drift */
  transition: filter .8s var(--ease), scale .8s var(--ease);
  will-change: filter;
}
.card2:is(:hover, :focus-visible, .is-hot) .card2__art {
  filter: blur(3.42cqw);
  scale: 1.14;
}
/* idle breathing — never dips under the 1.03 the blur fringe needs; hover
   drops the animation and the transition carries scale on to 1.14 */
@keyframes card2-breathe {
  0%, 100% { scale: 1.03; }
  50%      { scale: 1.078; }
}
.card2:not(:hover):not(:focus-visible):not(.is-hot) .card2__art {
  animation: card2-breathe 16s ease-in-out infinite;
}
.card2--mobile:not(:hover):not(:focus-visible):not(.is-hot) .card2__art {
  animation-delay: -8s;
}
/* Figma 13053:9069 — 1285x886 centered, nudged (-8.5%, +.9%) */
.card2--premier .card2__art { width: 195.6%; height: 106.37%; left: -56.3%; top: -2.29%; }
/* Figma 13053:9075 — 1232x852, pinned top, overflowing right */
.card2--mobile .card2__art { width: 187.52%; height: 102.28%; right: -10.44%; top: 0; }

/* --- hover dots — the blink field (mounted by js/dot-field.js, now synced
   with Premier's engine). The canvas paints ABOVE the shade, so two masks:
   a vertical fade that hands the field back to the darkening before the
   foot (unmasked it floats there as a ghost band), INTERSECTED with a
   54cqw spot glued to the pointer (--mx/--my from main.js) — the field
   only exists around the cursor. */
.card2__dots {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  opacity: 0;
  pointer-events: none;
  transition: opacity .6s var(--ease);
  -webkit-mask-image: radial-gradient(circle 54cqw at var(--mx, 50%) var(--my, 45%), #000, rgba(0,0,0,.6) 55%, transparent 100%),
                      linear-gradient(180deg, rgba(0,0,0,.55), #000 14%, #000 72%, rgba(0,0,0,0) 94%);
  -webkit-mask-composite: source-in;
          mask-image: radial-gradient(circle 54cqw at var(--mx, 50%) var(--my, 45%), #000, rgba(0,0,0,.6) 55%, transparent 100%),
                      linear-gradient(180deg, rgba(0,0,0,.55), #000 14%, #000 72%, rgba(0,0,0,0) 94%);
          mask-composite: intersect;
}
.card2:is(:hover, :focus-visible, .is-hot) .card2__dots { opacity: 1; }

/* the 1px line around the card (holocards2 lab, user 2026-08-31): the line
   IS the gradient — a segment that lights to white 10% at the cursor and
   dies to 0 along the ring (radial on --mx/--my, ring-masked via padding-box
   xor). No constant base: under a 1px line it hides the falloff and the
   whole ring reads as "always on". */
.card2__rim {
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: inherit;
  pointer-events: none;
  padding: 1px;
  opacity: 0;
  transition: opacity .5s var(--ease);
  background: radial-gradient(circle 50cqw at var(--mx, 50%) var(--my, 45%),
    rgba(255,255,255,.1), rgba(255,255,255,0) 100%);
  -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;
}
.card2:is(:hover, :focus-visible, .is-hot) .card2__rim { opacity: 1; }

/* the comp darkens the sim card only (74 top / 78 bottom, as before) */
.card2--premier .card2__shade {
  position: absolute;
  /* -2px below: the bottom-anchored gradients size in % of a fractional
     height and device-pixel rounding sometimes lands them a hair short,
     leaking the bright floor as a 1px line at the edge; overshooting the
     clip makes the wash cover the last row no matter how it rounds */
  inset: 0 0 -2px;
  pointer-events: none;
  background:
    linear-gradient(180deg, #000 0%, rgba(0,0,0,0) 57.928%) top / 100% 68.034% no-repeat,
    linear-gradient(0deg, #000 0%, rgba(0,0,0,0) 100%) bottom / 100% 34.391% no-repeat,
    linear-gradient(0deg, #000 0%, rgba(0,0,0,.72) 22%, rgba(0,0,0,.28) 55%, rgba(0,0,0,0) 100%) bottom / 100% 30% no-repeat;
  opacity: .76;
}

/* --- lockup: rest at the head, hover flies to dead center at 1.152x -----
   transform-only so the glide is one straight move; 52.24cqw is the
   head-to-center run on the 126.79cqw-tall card */
.card2__label {
  position: absolute;
  z-index: 2;
  left: 50%;
  top: 8.8%;
  width: 45.12%;
  height: auto;
  transform: translateX(-50%);
  transition: transform .7s var(--ease);
}
.card2__label--mobile {
  display: flex;
  align-items: center;
  gap: 1.74cqw;
  width: auto;
}
/* One <img> now, not a mark + wordmark pair. Sized by HEIGHT to match the
   PREMIER label beside it (user 2026-09-01). Sizing it by WIDTH, as the pair
   was, made its glyphs half again as big: app.svg and premier-logo.svg draw
   the mark at the same ~98% of their box height, so equal HEIGHT is what
   matches them and the differing file aspects are just wordmark length.
   The premier label is 45.12% wide on a 526x57 art box, so its rendered
   height is 45.12% x (57/526) = 4.89cqw. */
.card2__label--mobile img { display: block; width: auto; height: 4.89cqw; }
.card2:is(:hover, :focus-visible, .is-hot) .card2__label {
  transform: translate(-50%, calc(52.24cqw - 50%)) scale(1.152);
}

/* the type floats against the tilt — the `translate` property composes
   with the transforms the label and caption already carry */
.card2 :is(.card2__label, .card2__cap, .card2__belt, .card2__btn) {
  translate: calc(var(--rx) * -10px) calc(var(--ry) * -10px);
}

/* --- caption --- */
.card2__cap {
  position: absolute;
  z-index: 2;
  left: 50%;
  top: 84.51%;
  transform: translateX(-50%);
  width: 78.35%;
  margin: 0;
  font-size: 3.35cqw;              /* 22px on the comp */
  font-weight: 584;
  line-height: 1.1;
  text-align: center;
  color: #fff;
  transition: opacity .45s var(--ease), transform .6s var(--ease);
}
.card2:is(:hover, :focus-visible, .is-hot) .card2__cap { opacity: 0; transform: translateX(-50%) translateY(14px); }

/* --- feature belt — rides the shipped .courses marquee (clone + measured
   duration come from main.js); these overrides refit the strip to the card */
.card2__belt {
  position: absolute;
  z-index: 2;
  left: 0;
  right: 0;
  top: 91.24%;
  height: 5.48cqw;                 /* 36px on the comp */
  margin: 0;
  transition: opacity .45s var(--ease), transform .6s var(--ease);
}
.card2:is(:hover, :focus-visible, .is-hot) .card2__belt { opacity: 0; transform: translateY(14px); }
.card2__belt .courses__set {
  gap: 4.87cqw;                    /* 32px on the comp */
  padding-right: 4.87cqw;
}
.chip2 {
  flex: none;
  font-size: 2.43cqw;              /* 16px on the comp */
  font-weight: 584;
  color: #fff;
  white-space: nowrap;
}
.chip2__sep {
  flex: none;
  width: max(1px, .203cqw);        /* 1.33px on the comp */
  height: 5.2cqw;                  /* fixed: a % here resolves against the
                                      set's auto height and collapses to 0 */
  background: linear-gradient(180deg, rgba(255,255,255,0), #fff 50%, rgba(255,255,255,0));
}

/* --- the hover pill (Figma 13053:9331), comp-scaled --- */
.card2__btn {
  position: absolute;
  z-index: 2;
  left: 50%;
  top: 56.9%;
  transform: translateX(-50%) translateY(12px);
  padding: 2.44cqw 3.35cqw;        /* 16 / 22 on the comp */
  border-radius: 999px;
  background: rgba(255, 255, 255, .1);
  border: 1px solid rgba(255, 255, 255, .1);
  font-size: 2.74cqw;              /* 18px on the comp */
  font-weight: 584;
  line-height: 1.2;
  color: #fff;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity .5s var(--ease) .06s, transform .6s var(--ease) .06s;
}
.card2:is(:hover, :focus-visible, .is-hot) .card2__btn { opacity: 1; transform: translateX(-50%); }

/* --- touch: the card is a plain link (user 2026-09-17) --------------------
   main.js skips the pointer wiring without a hovering pointer, but iOS and
   Android still fake :hover on a tap or a hold and Chrome focuses the link
   on tap, so every hover-state rule above is pinned back to its rest value
   here. Same selectors, later in the sheet: these win. The art keeps its
   idle breathing; the dot field and rim stay dark. */
@media (hover: none) {
  .card2 { transform: none; }
  .card2:is(:hover, :focus-visible, .is-hot) .card2__art {
    filter: blur(0);
    scale: 1.03;
    animation: card2-breathe 16s ease-in-out infinite;
  }
  .card2--mobile:is(:hover, :focus-visible, .is-hot) .card2__art { animation-delay: -8s; }
  .card2:is(:hover, :focus-visible, .is-hot) .card2__dots,
  .card2:is(:hover, :focus-visible, .is-hot) .card2__rim { opacity: 0; }
  .card2:is(:hover, :focus-visible, .is-hot) .card2__label { transform: translateX(-50%); }
  .card2:is(:hover, :focus-visible, .is-hot) .card2__cap { opacity: 1; transform: translateX(-50%); }
  .card2:is(:hover, :focus-visible, .is-hot) .card2__belt { opacity: 1; transform: none; }
  .card2:is(:hover, :focus-visible, .is-hot) .card2__btn { opacity: 0; transform: translateX(-50%) translateY(12px); }
}

/* tablet: the 57vw desktop column reads too small mid-range — +30% */
@media (max-width: 1100px) {
  .plushero__plates { width: min(898px, 74.3vw); }
}
@media (max-width: 700px) {
  /* stacked single column at the page's standard mobile card margin
     (= .container-wide's 94vw, what the carousel cards ride) */
  .plushero__plates { width: 94vw; gap: 24px; }
  .card2 { aspect-ratio: 4 / 5; }
}

@media (prefers-reduced-motion: reduce) {
  .card2 .card2__art { animation: none; }
  .card2__art, .card2__label, .card2__cap, .card2__belt, .card2__btn { transition-duration: .01ms; }
}

/* ==========================================================================
   VENUES CTA — Premier's ghost-CTA treatment (user 2026-09-01): the plain
   outline pill trades its border for a 1px ring drawn by ::before, with a
   bright comet segment sweeping it and the whole stroke lighting on hover.
   Ported verbatim from Premier/css/thisis-band.css .thisis__cta.
   ========================================================================== */
@property --cta-angle { syntax: "<angle>"; initial-value: 0deg; inherits: false; }
@property --cta-ring { syntax: "<number>"; initial-value: 0.28; inherits: true; }
.venues__cta,
.faq__more-btn {
  position: relative;
  border: 0;
  border-radius: 999px;
  background: #000;
  --cta-ring: .28;
  color: #fff;
  transition: --cta-ring .28s var(--ease);
}
.venues__cta:hover,
.faq__more-btn:hover { transform: none; background: #000; --cta-ring: 1; }
.venues__cta::before,
.faq__more-btn::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; } }
@media (prefers-reduced-motion: reduce) {
  .venues__cta::before,
  .faq__more-btn::before { animation: none; }
}

/* ==========================================================================
   FAQ "See more..." (user 2026-09-17) — centered under the two columns,
   pointing at the full FAQ on the help center. Shares the comet-ring
   capsule with .venues__cta (selectors extended above). The list's rows
   end in an open rule, so the button sits a row's worth of air below.
   ========================================================================== */
.faq--v3 .faq__more {
  display: flex;
  justify-content: center;
  margin-top: clamp(40px, 5vh, 64px);
}
@media (max-width: 700px) {
  .faq--v3 .faq__more { margin-top: 32px; }
}
