81 lines
2.4 KiB
TypeScript
81 lines
2.4 KiB
TypeScript
"use client";
|
|
|
|
/**
|
|
* Process — "The Feedback Loop". The intro column pins (sticky) while the four
|
|
* steps scroll past; each step lights up as it reaches the viewport center, and
|
|
* a serif count in the sticky column advances with it. This is the
|
|
* scrollytelling beat. Degrades to a plain stacked list under reduced-motion
|
|
* (all steps shown active, no pin behaviour) and on narrow screens.
|
|
*/
|
|
|
|
import { useEffect, useRef, useState } from "react";
|
|
import { gsap } from "gsap";
|
|
import { ScrollTrigger } from "gsap/ScrollTrigger";
|
|
import { processSteps } from "../content";
|
|
|
|
gsap.registerPlugin(ScrollTrigger);
|
|
|
|
export default function Process() {
|
|
const root = useRef<HTMLElement>(null);
|
|
const [active, setActive] = useState(0);
|
|
|
|
useEffect(() => {
|
|
const el = root.current;
|
|
if (!el) return;
|
|
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
setActive(processSteps.length - 1);
|
|
return;
|
|
}
|
|
|
|
const ctx = gsap.context(() => {
|
|
gsap.utils.toArray<HTMLElement>(".pstep").forEach((step, i) => {
|
|
ScrollTrigger.create({
|
|
trigger: step,
|
|
start: "top 60%",
|
|
end: "bottom 60%",
|
|
onToggle: (self) => {
|
|
if (self.isActive) setActive(i);
|
|
},
|
|
});
|
|
});
|
|
}, el);
|
|
|
|
return () => ctx.revert();
|
|
}, []);
|
|
|
|
return (
|
|
<section id="process" className="process" ref={root} aria-label="How it works">
|
|
<div className="wrap process__inner">
|
|
<div className="process__sticky">
|
|
<p className="kicker">How it works</p>
|
|
<h2 className="section__title">
|
|
The <span className="serif-em">Feedback Loop.</span>
|
|
</h2>
|
|
<p
|
|
className="process__count"
|
|
aria-hidden="true"
|
|
key={active}
|
|
>
|
|
0{active + 1}
|
|
</p>
|
|
</div>
|
|
|
|
<ol className="process__steps">
|
|
{processSteps.map((p, i) => (
|
|
<li
|
|
className={`pstep${i <= active ? " is-active" : ""}`}
|
|
key={p.n}
|
|
aria-current={i === active ? "step" : undefined}
|
|
>
|
|
<div className="pstep__top">
|
|
<span className="pstep__n">{p.n}</span>
|
|
<h3 className="pstep__name">{p.name}</h3>
|
|
</div>
|
|
<p className="pstep__desc">{p.desc}</p>
|
|
</li>
|
|
))}
|
|
</ol>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|