/* ==========================================================================
   motion.css — "live" motion for the page

   The hero and two of the feature cards use real video (public/videos/),
   declared inline in the markup. This file holds the CSS motion: the
   word-by-word text reveal, section snapping, and the hero scrim and video
   sizing rules.
   ========================================================================== */

/* ---- Word-by-word text reveal ----
   main.js wraps each word in <span class="word">. Words start faded and light
   up in sequence once the block scrolls into view. */
.js-reveal .word {
  opacity: 0.15;
  transition: opacity 0.5s var(--ease);
}
.js-reveal.is-revealed .word {
  opacity: 1;
  transition-delay: calc(var(--i) * 45ms);
}

@media (prefers-reduced-motion: reduce) {
  .js-reveal .word { opacity: 1; transition: none; }
}

/* ==========================================================================
   Step reveal
   Each section holds a few numbered groups (data-step). main.js runs a
   step-lock scroll controller: one wheel/swipe reveals the next group, and
   once a section is fully revealed the next scroll glides to the next
   section. Elements start scaled-down and blurred, then settle.
   ========================================================================== */
[data-step] {
  opacity: 0;
  /* Slides up into place while scaling and un-blurring. */
  transform: translateY(48px) scale(0.96);
  filter: blur(10px);
  transition:
    opacity 0.6s var(--ease),
    transform 0.6s var(--ease),
    filter 0.6s var(--ease);
  transition-delay: calc(var(--stagger, 0) * 130ms);
}
[data-step].is-in {
  opacity: 1;
  transform: none;
  filter: none;
}

/* ---- Per-object staggering ----
   SCOPED to the three containers that actually need it. An earlier version
   used `[data-step].is-in > *`, which also hit every .word span in the intro
   paragraph (overriding the word-reveal's own --i timing), the hero caption
   and each card's <video> — that is what made everything feel broken. */
.stats[data-step] > .stat,
.accordion[data-step] > .acc-item,
.community-head[data-step] > * {
  opacity: 0;
  transform: translateY(36px);
  transition:
    opacity 0.7s var(--ease),
    transform 0.7s var(--ease);
}
.stats[data-step].is-in > .stat,
.accordion[data-step].is-in > .acc-item,
.community-head[data-step].is-in > * {
  opacity: 1;
  transform: none;
}
.stats[data-step].is-in > .stat:nth-child(1),
.accordion[data-step].is-in > .acc-item:nth-child(1),
.community-head[data-step].is-in > *:nth-child(1) { transition-delay: 0.10s; }
.stats[data-step].is-in > .stat:nth-child(2),
.accordion[data-step].is-in > .acc-item:nth-child(2),
.community-head[data-step].is-in > *:nth-child(2) { transition-delay: 0.25s; }
.stats[data-step].is-in > .stat:nth-child(3),
.accordion[data-step].is-in > .acc-item:nth-child(3) { transition-delay: 0.40s; }
.stats[data-step].is-in > .stat:nth-child(4) { transition-delay: 0.55s; }

/* The spec panels are BOTH a reveal target and a width animation. The generic
   `[data-step]` rule above has the same specificity as `.spec-item` and sits in
   a later file, so it was overwriting the flex-basis transition entirely and
   the panels snapped open. Compose the full list here at higher specificity. */
.spec-item[data-step] {
  transition:
    flex-basis 1s cubic-bezier(0.32, 0, 0.12, 1),
    background 0.6s var(--ease),
    opacity 0.6s var(--ease),
    transform 0.6s var(--ease),
    filter 0.6s var(--ease);
}

/* Feature cards rise up from below the fold, one per scroll.
   NOTE: every variant below must reset BOTH transform and filter on .is-in.
   These selectors match the generic `[data-step].is-in` specificity, so a
   missing reset here wins on source order and the element stays blurred. */
.feature-card--rise[data-step] {
  transform: translateY(120px) scale(0.97);
  filter: blur(6px);
  transition-duration: 1.8s;                       /* slow and unhurried */
  transition-delay: calc(var(--stagger, 0) * 340ms);
}
.feature-card--rise[data-step].is-in {
  transform: none;
  filter: none;
}

/* Hero headlines fly in from opposite edges before settling. */
.hero-title--top[data-step]    { transform: translate(-70px, 24px) scale(0.96); }
.hero-title--bottom[data-step] { transform: translate(70px, 24px) scale(0.96); }
.hero-title--top[data-step].is-in,
.hero-title--bottom[data-step].is-in {
  transform: none;
  filter: none;
}

/* ---- Hero entrance ----
   Deliberately slower than the rest of the page: this is the first thing seen,
   so the product and headlines take their time settling. Higher specificity
   than the generic [data-step] rule, so only duration and stagger change —
   the per-element transforms above still apply. */
.hero [data-step] {
  transition-duration: 1.6s;
  transition-delay: calc(var(--stagger, 0) * 300ms);
}
/* The spin canvas is large; a heavy blur on it costs a lot per frame for very
   little visual gain, so it slides and fades without one. */
.hero .product-spin[data-step] {
  filter: none;
  transform: translateY(80px) scale(0.97);
}
.hero .product-spin[data-step].is-in { transform: none; }

/* Mobile: plain native scrolling (no step-lock), with objects easing in as
   they enter the viewport. A lighter fade-and-rise rather than the desktop
   scale+blur — a large blur filter is expensive on phone GPUs. */
@media (max-width: 640px) {
  [data-step],
  .feature-card--rise[data-step],
  .hero-title--top[data-step],
  .hero-title--bottom[data-step] {
    opacity: 0;
    transform: translateY(28px);
    filter: none;
    transition:
      opacity 0.5s var(--ease),
      transform 0.5s var(--ease);
    transition-delay: calc(var(--stagger, 0) * 110ms);
  }
  [data-step].is-in,
  [data-step].is-in > .stat,
  [data-step].is-in > .acc-item,
  .community-head[data-step].is-in > * {
    opacity: 1;
    transform: none;
  }

  /* The hero is above the fold — show it immediately rather than animating
     content the user is already looking at. */
  .hero [data-step] {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  [data-step] {
    opacity: 1;
    transform: none;
    filter: none;
    transition: none;
  }
}


/* ---- Mobile scrolling ----
   Sections taller than the screen scroll normally; once you reach a section
   boundary the scroll snaps on to the next one. `proximity` (not `mandatory`)
   is the important part — mandatory would fight the user mid-section on any
   screen where the content is taller than the viewport. */
@media (max-width: 640px) {
  html {
    scroll-snap-type: y proximity;
    scroll-padding-top: 0;
  }
  main > section {
    scroll-snap-align: start;
    scroll-snap-stop: normal;
  }
  .site-footer { scroll-snap-align: end; }
}

/* ---- Hero background video ----
   Declared inline in index.html (same pattern as .card-media on the feature
   cards) so its styling is plain CSS rather than something JS has to attach.
   `cover` makes it fill the section at any viewport ratio — never letterboxed,
   never squashed. */
.hero-media {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  min-width: 100%;
  min-height: 100%;
  object-fit: cover;        /* fills both axes, crops overflow, never letterboxes */
  object-position: center;
  z-index: 0;
  pointer-events: none;
}
/* The spin mount reuses .hero-media for layout but must receive pointer
   events — it is dragged, not decorative. */
.hero-media.product-spin {
  pointer-events: auto;
  object-fit: unset;
}

