"use client"; import { useRef, type ReactNode } from "react"; import { useReducedMotion } from "motion/react"; type ImageTrailProps = { /** Picture addresses. They are decoration, so keep nothing important only here. */ images: string[]; /** The area the trail shows over. */ children?: ReactNode; /** Pixels the pointer moves before the next picture appears. */ gap?: number; /** Width of each picture in pixels. */ size?: number; /** Seconds a picture lives. */ life?: number; /** Set the size here. */ className?: string; }; const SLOTS = 8; /* ImageTrail: moving the pointer or a finger drops pictures along the way. Each pops up, holds, and fades. */ export function ImageTrail({ images, children, gap = 90, size = 150, life = 1, className = "h-80" }: ImageTrailProps) { const reduce = useReducedMotion(); const slots = useRef<(HTMLImageElement | null)[]>([]); const last = useRef<{ x: number; y: number } | null>(null); const turn = useRef(0); const pile = useRef(0); const pool = Array.from({ length: Math.max(images.length, SLOTS) }, (_, i) => images[i % images.length]); const drop = (e: React.PointerEvent) => { const img = slots.current[turn.current]; if (reduce || !img) return; const r = e.currentTarget.getBoundingClientRect(); const x = e.clientX - r.left; const y = e.clientY - r.top; if (last.current && Math.hypot(x - last.current.x, y - last.current.y) < gap) return; last.current = { x, y }; turn.current = (turn.current + 1) % pool.length; pile.current += 1; const tilt = `translate(-50%, -50%) rotate(${(Math.random() - 0.5) * 16}deg)`; img.style.left = `${x}px`; img.style.top = `${y}px`; img.style.zIndex = String(pile.current); img.animate( [ { opacity: 0, transform: `${tilt} scale(0.5)` }, { opacity: 1, transform: `${tilt} scale(1)`, offset: 0.15 }, { opacity: 1, transform: `${tilt} scale(1)`, offset: 0.6 }, { opacity: 0, transform: `${tilt} scale(0.85)` }, ], { duration: life * 1000, easing: "cubic-bezier(0.2, 0.8, 0.2, 1)" }, ); }; return (
(last.current = null)} className={`relative isolate touch-pan-y select-none overflow-hidden ${className}`} >
{pool.map((src, i) => ( // eslint-disable-next-line @next/next/no-img-element { slots.current[i] = el; }} src={src} alt="" draggable={false} className="absolute aspect-[4/5] rounded-xl object-cover opacity-0 shadow-xl shadow-black/40" style={{ width: size }} /> ))}
{children}
); }