{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "animated-testimonials", "title": "Animated Testimonials", "description": "Stacked card testimonial carousel with word-by-word blur reveal animation.", "dependencies": [ "motion" ], "files": [ { "path": "registry/pioneerui/animated-testimonials.tsx", "content": "\"use client\"\n\nimport React, { useCallback, useEffect, useState } from \"react\"\nimport { AnimatePresence, motion } from \"motion/react\"\nimport { cn } from \"@/lib/utils\"\n\nexport interface Testimonial {\n quote: string\n name: string\n designation: string\n src: string\n}\n\nexport interface AnimatedTestimonialsProps {\n testimonials: Testimonial[]\n autoplay?: boolean\n className?: string\n}\n\nfunction ArrowLeft() {\n return (\n \n )\n}\n\nfunction ArrowRight() {\n return (\n \n )\n}\n\nfunction randomRotate(seed: number) {\n // Deterministic rotation offset based on index so stacked images fan out\n const offsets = [-8, -4, 4, 8, -6, 6, -2, 2]\n return offsets[seed % offsets.length]\n}\n\nexport function AnimatedTestimonials({\n testimonials,\n autoplay = true,\n className,\n}: AnimatedTestimonialsProps) {\n const [active, setActive] = useState(0)\n\n const handleNext = useCallback(() => {\n setActive((prev) => (prev + 1) % testimonials.length)\n }, [testimonials.length])\n\n const handlePrev = () => {\n setActive((prev) => (prev - 1 + testimonials.length) % testimonials.length)\n }\n\n const isActive = (index: number) => index === active\n\n useEffect(() => {\n if (!autoplay) return\n const interval = setInterval(handleNext, 5000)\n return () => clearInterval(interval)\n }, [autoplay, handleNext])\n\n const words = testimonials[active].quote.split(\" \")\n\n return (\n
\n {testimonials[active].designation}\n
\n