lovart3/components/lovart/hover-stack.tsx
Your Name f6b7758ec9
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
feat: extend lovart rebuild with blog and changelog experiences
2026-02-12 12:11:47 +08:00

78 lines
2.5 KiB
TypeScript

"use client";
import { motion, useMotionValue, useSpring, useTransform } from "framer-motion";
type StackItem = {
id: string;
imageUrl: string;
prompt: string;
modelId: string;
};
export function HoverStack({ items }: { items: StackItem[] }) {
const offsetX = useMotionValue(0);
const offsetY = useMotionValue(0);
const smoothX = useSpring(offsetX, { stiffness: 180, damping: 20, mass: 0.5 });
const smoothY = useSpring(offsetY, { stiffness: 180, damping: 20, mass: 0.5 });
const rotateY = useTransform(smoothX, [-160, 160], [-8, 8]);
const rotateX = useTransform(smoothY, [-120, 120], [7, -7]);
const cards = items.slice(0, 3);
if (cards.length === 0) {
return (
<div className="rounded-2xl border border-white/15 bg-black/30 p-6 text-sm text-zinc-300">
暂无可展示样例
</div>
);
}
return (
<motion.div
className="relative h-[290px] w-full [perspective:1200px]"
onMouseLeave={() => {
offsetX.set(0);
offsetY.set(0);
}}
onMouseMove={(event) => {
const rect = event.currentTarget.getBoundingClientRect();
const x = event.clientX - (rect.left + rect.width / 2);
const y = event.clientY - (rect.top + rect.height / 2);
offsetX.set(x);
offsetY.set(y);
}}
style={{ rotateX, rotateY, transformStyle: "preserve-3d" }}
>
{cards.map((item, index) => {
const offset = index * 18;
return (
<motion.article
key={item.id}
animate={{ y: [offset, offset - 7, offset], scale: [1, 1.01, 1] }}
className="absolute left-0 right-0 mx-auto w-[92%] overflow-hidden rounded-2xl border border-white/15 bg-black/35 shadow-[0_20px_60px_-24px_rgba(14,165,233,0.55)]"
style={{
zIndex: cards.length - index,
transform: `translateZ(${(cards.length - index) * 28}px)`,
top: `${index * 24}px`,
}}
transition={{
duration: 4 + index,
ease: "easeInOut",
repeat: Number.POSITIVE_INFINITY,
}}
>
<img alt={item.prompt} className="h-36 w-full object-cover" src={item.imageUrl} />
<div className="space-y-1 p-3">
<p className="line-clamp-2 text-xs text-zinc-200">{item.prompt}</p>
<p className="text-[11px] text-zinc-400">{item.modelId}</p>
</div>
</motion.article>
);
})}
</motion.div>
);
}