<section class="demo">
<div class="demo__pin">
<div class="demo__tunnel" id="tunnel">
<div class="demo__frame" data-tone="a"></div>
<div class="demo__frame" data-tone="b"></div>
<div class="demo__frame" data-tone="c"></div>
<div class="demo__frame" data-tone="d"></div>
<div class="demo__frame" data-tone="e"></div>
<div class="demo__frame" data-tone="f"></div>
</div>
</div>
</section>.demo {
height: 280vh;
background: #08080a;
}
.demo__pin {
position: sticky;
top: 0;
height: 100vh;
display: grid;
place-items: center;
perspective: 900px;
}
.demo__tunnel {
position: relative;
width: min(70vw, 420px);
height: min(56vh, 360px);
transform-style: preserve-3d;
}
.demo__frame {
position: absolute;
inset: 0;
border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.1);
transform-origin: 50% 50%;
}
.demo__frame[data-tone="a"] { background: rgba(43, 47, 74, 0.35); }
.demo__frame[data-tone="b"] { background: rgba(27, 51, 80, 0.35); }
.demo__frame[data-tone="c"] { background: rgba(58, 49, 35, 0.35); }
.demo__frame[data-tone="d"] { background: rgba(74, 36, 34, 0.35); }
.demo__frame[data-tone="e"] { background: rgba(38, 64, 74, 0.35); }
.demo__frame[data-tone="f"] { background: rgba(20, 20, 24, 0.5); }const frames = gsap.utils.toArray(".demo__frame");
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
frames.forEach(function (frame, index) {
gsap.set(frame, { scale: 1.2 - index * 0.08, z: -index * 48, force3D: true });
});
if (!reduce && window.ScrollTrigger) {
const tl = gsap.timeline({
scrollTrigger: {
trigger: ".demo",
start: "top top",
end: "bottom bottom",
scrub: 0.4,
},
});
frames.forEach(function (frame, index) {
tl.to(
frame,
{
scale: 0.22 + index * 0.04,
z: -360 - index * 24,
opacity: 0.3,
ease: "none",
},
0
);
});
}