<section class="demo">
<div class="demo__pin">
<div class="demo__panels">
<article class="demo__panel"><span>01</span><strong>Hold</strong></article>
<article class="demo__panel"><span>02</span><strong>Pan</strong></article>
<article class="demo__panel"><span>03</span><strong>Frame</strong></article>
<article class="demo__panel"><span>04</span><strong>Release</strong></article>
</div>
</div>
</section>.demo {
min-height: 100vh;
background: #0c0c0e;
color: #f2f2f0;
font-family: "Segoe UI", "Helvetica Neue", system-ui, sans-serif;
}
.demo__pin {
min-height: 100vh;
display: flex;
align-items: center;
overflow: hidden;
}
.demo__panels {
display: flex;
gap: 20px;
padding: 0 8vw;
will-change: transform;
}
.demo__panel {
flex: 0 0 min(68vw, 400px);
height: min(56vh, 340px);
padding: 28px;
border-radius: 28px;
display: flex;
flex-direction: column;
justify-content: space-between;
background: #141416;
border: 1px solid rgb(255 255 255 / 0.08);
box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.05);
}
.demo__panel span {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 12px;
letter-spacing: 0.12em;
font-variant-numeric: tabular-nums;
color: rgb(242 242 240 / 0.48);
}
.demo__panel strong {
font-size: clamp(32px, 6vw, 56px);
font-weight: 600;
letter-spacing: -0.05em;
line-height: 1;
}const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const panels = document.querySelector(".demo__panels");
const distance = function () {
return Math.max(0, panels.scrollWidth - window.innerWidth);
};
if (reduce) {
panels.style.transform = "none";
} else {
gsap.to(panels, {
x: function () { return -distance(); },
ease: "none",
scrollTrigger: {
trigger: ".demo",
start: "top top",
end: function () { return "+=" + Math.max(window.innerHeight, distance()); },
pin: true,
scrub: 1,
invalidateOnRefresh: true,
},
});
}