/* ---------- The pinned particle narrative ----------
   One sticky canvas holds every state (sphere → signal → lock → graph → wordmark).
   Beats scroll over it; each beat's copy is centred and driven by the same
   normalized progress value that drives the geometry. */

/* The story reaches under the translucent header so the sticky stage and the
   first viewport share one frame; hero copy is padded clear of the nav. */
.page-home main {
  margin-top: calc(-1 * var(--header-h));
}

.story {
  position: relative;
  isolation: isolate;
}

.story__stage {
  position: sticky;
  top: 0;
  height: 100dvh;
  z-index: 0;
}

.story__canvas {
  width: 100%;
  height: 100%;
}

.story__beats {
  position: relative;
  z-index: 1;
  margin-top: -100dvh;
  pointer-events: none;
}

.story__beats a,
.story__beats button {
  pointer-events: auto;
}

.beat {
  position: relative;
  display: grid;
  min-height: 100dvh;
}

.beat__grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: clamp(16px, 2.4vw, 32px);
  min-height: 100dvh;
  align-items: center;
}

.beat__copy {
  position: sticky;
  top: 50dvh;
  transform: translateY(-50%);
  opacity: var(--beat-op, 1);
  transition: opacity 0.18s linear;
  grid-column: 1 / span 6;
}

.beat--right .beat__copy {
  grid-column: 7 / span 6;
  text-align: right;
  justify-items: end;
}

.beat--right .btnrow {
  justify-content: flex-end;
}

.beat--center .beat__copy {
  grid-column: 1 / span 12;
  align-self: start;
  top: 15dvh;
  transform: none;
  max-width: 46rem;
}

.beat--hero .beat__grid {
  padding-top: var(--header-h);
}

/* The hero beat is exactly one viewport tall, so its copy has nothing to stay
   pinned through — centring it in the space below the header keeps a long
   headline clear of the nav instead of sliding under it. */
.beat--hero .beat__copy {
  grid-column: 1 / span 7;
  position: static;
  transform: none;
}

/* Chapters get a little extra travel so each transition has room to read. */
.beat[data-beat='2'] {
  min-height: 112dvh;
}

.beat[data-beat='3'] {
  min-height: 124dvh;
}

.beat[data-beat='4'] {
  min-height: 150dvh;
}

/* Hero */

.hero__title {
  /* Also bounded by viewport height: on a short laptop screen the headline
     scales down rather than pushing the lede and buttons off the fold. */
  font-size: min(var(--step-hero), 12.5vh);
  line-height: 0.94;
  letter-spacing: -0.045em;
}

.hero__title span {
  display: block;
  opacity: 0;
  transform: translateY(0.22em);
  animation: heroline 0.9s var(--ease-out) forwards;
}

.hero__title span:nth-child(1) {
  animation-delay: 0.05s;
}
.hero__title span:nth-child(2) {
  animation-delay: 0.13s;
}
.hero__title span:nth-child(3) {
  animation-delay: 0.21s;
}
.hero__title span:nth-child(4) {
  animation-delay: 0.29s;
}

@keyframes heroline {
  to {
    opacity: 1;
    transform: none;
  }
}

.hero__lede {
  margin-top: clamp(22px, 2.4vw, 30px);
  max-width: 34rem;
  font-size: var(--step-lede);
  line-height: 1.6;
}

.hero__cue {
  position: absolute;
  right: var(--gutter);
  bottom: clamp(28px, 5vh, 56px);
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--muted);
  font-size: var(--step-micro);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: var(--cue-op, 1);
  transition: opacity 0.3s var(--ease);
}

.hero__mouse {
  position: relative;
  display: block;
  width: 15px;
  height: 23px;
  border: 1.5px solid var(--brand);
  border-radius: var(--r-pill);
}

.hero__mouse span {
  position: absolute;
  left: 50%;
  top: 5px;
  width: 3px;
  height: 3px;
  margin-left: -1.5px;
  border-radius: 50%;
  background: var(--brand);
  animation: wheel 1.9s var(--ease) infinite;
}

@keyframes wheel {
  0% {
    opacity: 0;
    transform: translateY(0);
  }
  30% {
    opacity: 1;
  }
  70% {
    opacity: 0;
    transform: translateY(8px);
  }
  100% {
    opacity: 0;
  }
}

/* Narrative beats */

.beat__title {
  font-size: var(--step-display);
  line-height: 1;
  letter-spacing: -0.04em;
}

.beat__title--wide {
  font-size: var(--step-display-wide);
}

.beat__body {
  margin-top: clamp(18px, 2vw, 26px);
  max-width: 30rem;
  font-size: var(--step-intro);
  line-height: 1.62;
}

.beat--right .beat__body {
  margin-left: auto;
}

.beat--center .beat__body {
  max-width: 34rem;
}

/* Modality labels pinned to graph nodes. Positioned in viewport space by the scene. */
.beat__labels {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 2;
}

.graphlabel {
  position: absolute;
  left: 0;
  top: 0;
  padding: 4px 9px 4px 8px;
  border-left: 2px solid var(--brand);
  color: var(--navy-muted);
  font-size: var(--step-tag);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
  opacity: 0;
  transform: translate(-50%, -50%);
  transition: opacity 0.35s var(--ease);
}

/* Static fallback when WebGL is unavailable or motion is reduced. */
.story__fallback {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
}

.story__fallback[hidden] {
  display: none;
}

/* Each static drawing sits where its live counterpart would, so the reduced-motion
   version keeps the same alternating composition. */
.story__fallback canvas {
  position: absolute;
  top: 50%;
  translate: 0 -50%;
  width: min(38vw, 420px);
  height: min(38vw, 420px);
  opacity: 0;
  transition: opacity 0.5s var(--ease);
}

.story__fallback canvas[data-state='sphere'],
.story__fallback canvas[data-state='isolatedSignal'] {
  right: max(var(--gutter), 6vw);
}

.story__fallback canvas[data-state='lock'] {
  left: max(var(--gutter), 8vw);
}

.story__fallback canvas[data-state='graph'] {
  left: 50%;
  top: 62%;
  translate: -50% -50%;
  width: min(94vw, 1180px);
  height: min(30vw, 340px);
}

.story__fallback canvas[data-state='wordmark'] {
  right: max(var(--gutter), 6vw);
  width: min(40vw, 460px);
  height: min(17vw, 190px);
}

@media (max-width: 900px) {
  .beat__grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .beat__copy,
  .beat--hero .beat__copy,
  .beat--right .beat__copy,
  .beat--center .beat__copy {
    grid-column: 1 / -1;
    top: calc(var(--header-h) + 5dvh);
    transform: none;
    align-self: start;
    text-align: left;
    max-width: none;
  }

  .beat__grid {
    align-items: start;
    padding-top: calc(var(--header-h) + 5dvh);
    min-height: 100dvh;
  }

  .beat--right .btnrow {
    justify-content: flex-start;
  }

  .beat--right .beat__body {
    margin-left: 0;
  }

  .hero__cue {
    left: var(--gutter);
    right: auto;
  }

  .story__fallback canvas,
  .story__fallback canvas[data-state='graph'],
  .story__fallback canvas[data-state='wordmark'] {
    left: 50%;
    right: auto;
    top: auto;
    bottom: 6dvh;
    translate: -50% 0;
    width: min(76vw, 340px);
    height: min(76vw, 340px);
  }

  .story__fallback canvas[data-state='graph'] {
    width: 94vw;
    height: min(46vw, 200px);
  }

  .story__fallback canvas[data-state='wordmark'] {
    width: min(80vw, 360px);
    height: min(30vw, 140px);
  }
}
