Project cards that flip on their X axis as the section scrolls, with a large watermark title drifting behind the card in view. Scroll inside the preview to flip through them.
Perspective showcase presents a deck of project cards that flip on their X axis as the section scrolls. A scroll-linked spring drives the rotation, so each card turns face down and hands off to the next, while a large watermark title drifts behind the deck for the card in view.
The effect is self-contained. It ships a fixed-height box with its own internal scroller and a runway several times taller than the frame, so the flip is driven by scrolling inside the preview rather than by the page. The box clips the perspective fan and the watermark to its rounded bounds. Scroll inside the frame to flip through the cards.
Import the pattern from loosethreads:
import { PerspectiveShowcase } from "@pulp/loosethreads/patterns"Drop it in and scroll. The component renders its own project deck, scroll box, and animation, so it takes no props:
import { PerspectiveShowcase } from "@pulp/loosethreads/patterns"
function Showcase() {
return <PerspectiveShowcase />
}This is a self-contained showcase with no props. It embeds its own sample deck, where each project is a { title, tags, bgText, bg } record: title and tags render over the card, bgText is the watermark word behind it, and bg is the iris gradient tile. To show your own projects, edit that array in the source.
A sticky stage pins the deck to the top of the box and centers it while the runway scrolls past. Scroll progress feeds a spring, which drives the deck's rotateX and a normalized index the cards read from. Each card is a gradient tile with a dark bottom overlay, a serif title, and pill tags; behind the deck an aria-hidden marquee scrolls the current project's watermark word.
role="group" with an aria-label that describes the interaction; the watermark marquee is aria-hidden as decoration.prefers-reduced-motion in the current source, so it keeps looping even when reduced motion is requested. Gate it on window.matchMedia('(prefers-reduced-motion: reduce)') before shipping to a motion-sensitive audience.Next: Scroll Scrubber. Back to Scroll effects.