Retour au catalogue
About Cards Stack
Histoire racontee via des cartes empilees qui s'etalent au scroll.
aboutcomplex Both Responsive a11y
playfulboldagencysaasuniversalstacked
Theme
"use client";
import React, { useRef } from "react";
import { motion, useScroll, useTransform } from "framer-motion";
interface Card {
id: string;
title: string;
text: string;
image?: string;
accent?: boolean;
}
interface AboutCardsStackProps {
badge?: string;
title?: string;
cards?: Card[];
}
function StackCard({ card, index, total }: { card: Card; index: number; total: number }) {
const ref = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({ target: ref, offset: ["start end", "start 0.3"] });
const y = useTransform(scrollYProgress, [0, 1], [100, 0]);
const rotate = useTransform(scrollYProgress, [0, 1], [index % 2 === 0 ? 3 : -3, 0]);
const scale = useTransform(scrollYProgress, [0, 1], [0.9, 1]);
return (
<motion.div
ref={ref}
style={{
y, rotate, scale, zIndex: total - index,
...(card.accent
? { backgroundColor: "var(--color-background-dark)", borderColor: "var(--color-accent)" }
: { backgroundColor: "var(--color-background-card)", borderColor: "var(--color-border)" }
),
}}
className="rounded-[var(--radius-xl,1.5rem)] border overflow-hidden shadow-lg"
>
<div className="grid grid-cols-1 md:grid-cols-2 gap-0">
{card.image && (
<div className="aspect-[3/2] md:aspect-auto" style={{ backgroundColor: "var(--color-background-alt)", backgroundImage: `url(${card.image})`, backgroundSize: "cover", backgroundPosition: "center" }} />
)}
<div className="p-8 md:p-10 flex flex-col justify-center">
<span className="text-xs font-bold tracking-wider" style={{ color: "var(--color-accent)" }}>0{index + 1}</span>
<h3 className="mt-2 text-xl font-bold md:text-2xl" style={{ color: card.accent ? "var(--color-foreground)" : "var(--color-foreground)" }}>{card.title}</h3>
<p className="mt-3 text-sm leading-relaxed" style={{ color: "var(--color-foreground-muted)" }}>{card.text}</p>
</div>
</div>
</motion.div>
);
}
export default function AboutCardsStack({ badge, title, cards = [] }: AboutCardsStackProps) {
return (
<section className="py-[var(--section-padding-y,6rem)]" style={{ backgroundColor: "var(--color-background)" }}>
<div className="mx-auto max-w-4xl px-[var(--container-padding-x,1.5rem)]">
<motion.div initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ duration: 0.5 }} className="text-center max-w-2xl mx-auto mb-16">
{badge && <span className="inline-block text-xs font-medium tracking-wider uppercase px-3 py-1 rounded-full border" style={{ color: "var(--color-accent)", borderColor: "var(--color-border)" }}>{badge}</span>}
{title && <h2 className="mt-4 text-3xl font-bold tracking-tight md:text-4xl" style={{ color: "var(--color-foreground)" }}>{title}</h2>}
</motion.div>
<div className="space-y-8">
{cards.map((card, i) => <StackCard key={card.id} card={card} index={i} total={cards.length} />)}
</div>
</div>
</section>
);
}