Components
A GSAP + ScrollTrigger scroll experience that pins each section and progressively scales and 3D-rotates content “cards” as you scroll.
Loading preview...
"use client";
import React, { useEffect, useRef } from "react";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
// Register plugin only in browser
if (typeof window !== "undefined") {
gsap.registerPlugin(ScrollTrigger);
}
export default function DefaultDemo() {
const rootRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (!rootRef.current) return;
const ctx = gsap.context(() => {
// Respect prefers-reduced-motion
if (window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
return;
}
const slideWrappers = gsap.utils.toArray<HTMLElement>(".Content__wrapper");
const slides = gsap.utils.toArray<HTMLElement>(".Content__slide");
if (!slideWrappers.length || slideWrappers.length !== slides.length) return;
slideWrappers.forEach((wrapper, i) => {
const card = slides[i];
let scale = 1;
let rotationZ = 0;
let rotationX = 0;
if (i !== slides.length - 1) {
scale = 0.4 + 0.025 * i;
rotationZ = 5;
rotationX = 40;
}
gsap.to(card, {
scale,
rotationX,
rotationZ,
transformOrigin: "50% center",
ease: "none",
scrollTrigger: {
trigger: wrapper,
start: "top top",
end: "bottom bottom",
endTrigger: slides[slides.length - 1],
scrub: 1,
pin: wrapper,
pinSpacing: false,
id: String(i + 1),
// markers: {
// indent: 100 * i,
// fontSize: "14px",
// },
},
});
});
// Ensure measurements are up to date
ScrollTrigger.refresh();
}, rootRef);
return () => ctx.revert();
}, []);
return (
<div ref={rootRef}>
<section className="Content">
<div className="Content__inner">
<div className="Content__wrapper">
<div className="Content__slide green">
<div className="Content__slide-inner">
<div>
<p className="Content__number">{`{ 01 }`}</p>
<h2 className="Content__title heading-lg">Bring Ideas to Life</h2>
</div>
<div>
<p className="Content__copy">
Animation isn’t just decoration — it’s how we guide attention,
create clarity, and make interfaces feel alive. In this session,
you’ll get hands-on with techniques that transform static layouts
into stories.
</p>
</div>
</div>
</div>
</div>
<div className="Content__wrapper">
<div className="Content__slide white">
<div className="Content__slide-inner">
<div>
<p className="Content__number">{`{ 02 }`}</p>
<h2 className="Content__title heading-lg">Learn by Doing</h2>
</div>
<div>
<p className="Content__copy">
Instead of watching slides fly past, you’ll code along,
experiment, and break things. Each exercise is designed to teach
you a practical skill you can immediately put into practice.
</p>
</div>
</div>
</div>
</div>
<div className="Content__wrapper">
<div className="Content__slide orange">
<div className="Content__slide-inner">
<div>
<p className="Content__number">{`{ 03 }`}</p>
<h2 className="Content__title heading-lg">Build Your Toolkit</h2>
</div>
<div>
<p className="Content__copy">
By the end of the workshop, you’ll hopefully walk away with some
helpful snippets, animation principles that stand the test of
time, and the confidence to design smoother, smarter interactions.
</p>
</div>
</div>
</div>
</div>
<div className="Content__wrapper">
<div className="Content__slide lilac">
<div className="Content__slide-inner">
<div>
<p className="Content__number">{`{ 04 }`}</p>
<h2 className="Content__title heading-lg">Find the Rhythm</h2>
</div>
<div>
<p className="Content__copy">
Every interaction has a tempo. Some movements are quick and
direct, others linger just long enough to create a sense of flow.
Learning to notice and shape this rhythm helps everything feel
more natural, more connected.
</p>
</div>
</div>
</div>
</div>
</div>
</section>
<div className="spacer" />
</div>
);
}