"use client"; import React, { useState, useRef, useCallback, useId, memo } from "react"; import { motion, AnimatePresence, useMotionValue, useReducedMotion, useSpring, } from "motion/react"; import { ChevronDown } from "lucide-react"; import { cn } from "@/lib/utils"; export interface Testimonial { id: number; author: string; role: string; company: string; avatar: string; quote: string; } export const DEVCLUB_INITIAL_TESTIMONIALS: Testimonial[] = [ { id: 0, author: "Yash Sharma", role: "Founding Engineer", company: "DevClub NST", avatar: "https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=120&h=120&auto=format&fit=crop&crop=faces&q=80", quote: "DevClub UI saved my sleep schedule and centered all my divs on the very first try. Genuinely 10/10.", }, { id: 1, author: "Jensen Huang", role: "GPU Overlord", company: "NVIDIA", avatar: "https://images.unsplash.com/photo-1552374196-c4e7ffc6e126?w=120&h=120&auto=format&fit=crop&crop=faces&q=80", quote: "The more DevClub components you copy, the more time you save. It's the law of modern frontend physics.", }, { id: 2, author: "Priya Nair", role: "Ex-Burnout Lead", company: "0 to 1 Startup", avatar: "https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=120&h=120&auto=format&fit=crop&crop=faces&q=80", quote: "I copied three buttons from DevClub NST and suddenly our seed investors thought we hired Apple's design lead.", }, { id: 3, author: "Cristiano Ronaldo", role: "Perfectionist", company: "CR7 Brand", avatar: "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=120&h=120&auto=format&fit=crop&crop=faces&q=80", quote: "Siiiuuu! The micro-interactions in this library have better curve control than my best free kicks.", }, ]; export const DEVCLUB_ADDITIONAL_TESTIMONIALS: Testimonial[] = [ { id: 4, author: "Kabir Sen", role: "Figma Whisperer", company: "Studio Craft", avatar: "https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=120&h=120&auto=format&fit=crop&crop=faces&q=80", quote: "Our product designer actually cried happy tears when my PR matched their Figma spring transitions on day one.", }, { id: 5, author: "Sarah Chen", role: "Junior Developer", company: "HyperScale", avatar: "https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=120&h=120&auto=format&fit=crop&crop=faces&q=80", quote: "I still don't fully understand GSAP timelines, but DevClub UI makes everyone think I have 8 years of WebGL mastery.", }, { id: 6, author: "Vikram Das", role: "Fullstack Vibe Engineer", company: "NST Hackers", avatar: "https://images.unsplash.com/photo-1522075469751-3a6694fb2f61?w=120&h=120&auto=format&fit=crop&crop=faces&q=80", quote: "If DevClub NST goes offline, 90% of college startup landing pages will instantly collapse into plain HTML tables.", }, { id: 7, author: "Elena Rostova", role: "Head of Motion", company: "Neon Dynamics", avatar: "https://images.unsplash.com/photo-1517841905240-472988babdf9?w=120&h=120&auto=format&fit=crop&crop=faces&q=80", quote: "The spring dampening curves in these components are so silky they should honestly require a safety permit.", }, { id: 8, author: "Aisha Patel", role: "Product Manager", company: "FlowState", avatar: "https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?w=120&h=120&auto=format&fit=crop&crop=faces&q=80", quote: "My CEO asked why our staging site looks like Linear and Apple had a baby. I just smiled and whispered 'DevClub'.", }, { id: 9, author: "Devansh Rao", role: "Hackathon Speedrunner", company: "Speedrunners Inc", avatar: "https://images.unsplash.com/photo-1519085360753-af0119f7cbe7?w=120&h=120&auto=format&fit=crop&crop=faces&q=80", quote: "Shipped 4 MVP submissions in 36 hours. Judges thought we didn't sleep for a week. We were just chilling with DevClub UI.", }, { id: 10, author: "Sneha Reddy", role: "VP of Centering Divs", company: "CloudSync", avatar: "https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=120&h=120&auto=format&fit=crop&crop=faces&q=80", quote: "I used to spend half my sprint debugging Safari blur artifacts. With DevClub UI, everything just works out of the box.", }, { id: 11, author: "Antony Raphy", role: "Chief Vibe Officer", company: "Indie Hacker", avatar: "https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?w=120&h=120&auto=format&fit=crop&crop=faces&q=80", quote: "Replaced 4,000 lines of spaghetti animation boilerplate with a single DevClub primitive. Finally touched grass.", }, ]; const TestimonialSpanItem = memo(function TestimonialSpanItem({ item, isHovered, hasHover, onHover, }: { item: Testimonial; isHovered: boolean; hasHover: boolean; onHover: (id: number) => void; }) { const stateClass = !hasHover ? "opacity-80 blur-0 text-zinc-600 dark:text-zinc-400" : isHovered ? "opacity-100 blur-0 text-zinc-950 dark:text-zinc-50" : "opacity-25 blur-[1.5px] text-zinc-400 dark:text-zinc-600"; const avatarClass = !hasHover ? "opacity-90 grayscale-[15%] scale-100" : isHovered ? "opacity-100 grayscale-0 scale-105 shadow-sm" : "opacity-30 grayscale-[75%] blur-[0.6px] scale-[0.96]"; return ( onHover(item.id)} className={cn( "inline cursor-pointer select-none transition-all duration-300 ease-out will-change-[opacity,filter,color] motion-reduce:transition-none motion-reduce:blur-none", stateClass, )} > {item.author} {item.quote} {" "} ); }); export interface FocusTestimonialsProps { initialItems?: Testimonial[]; additionalItems?: Testimonial[]; className?: string; cardClassName?: string; } export function FocusTestimonials({ initialItems = DEVCLUB_INITIAL_TESTIMONIALS, additionalItems = DEVCLUB_ADDITIONAL_TESTIMONIALS, className, cardClassName, }: FocusTestimonialsProps) { const [hoveredId, setHoveredId] = useState(null); const [showMore, setShowMore] = useState(false); const containerRef = useRef(null); const quotesId = useId(); const reduceMotion = useReducedMotion(); const rawMouseX = useMotionValue(0); const rawMouseY = useMotionValue(0); const springConfig = { damping: 28, stiffness: 340, mass: 0.4 }; const smoothX = useSpring(rawMouseX, springConfig); const smoothY = useSpring(rawMouseY, springConfig); const allItems = React.useMemo( () => [...initialItems, ...additionalItems], [initialItems, additionalItems], ); const activeItem = hoveredId !== null ? allItems.find((t) => t.id === hoveredId) : null; const handleMouseMove = useCallback( (e: React.MouseEvent) => { if (!containerRef.current) return; const rect = containerRef.current.getBoundingClientRect(); rawMouseX.set(e.clientX - rect.left); rawMouseY.set(e.clientY - rect.top); }, [rawMouseX, rawMouseY], ); const handleMouseLeave = useCallback(() => { setHoveredId(null); }, []); const handleHover = useCallback((id: number) => { setHoveredId(id); }, []); const handleToggleShowMore = () => { setShowMore((prev) => { const nextState = !prev; if ( !nextState && hoveredId !== null && hoveredId >= initialItems.length ) { setHoveredId(null); } return nextState; }); }; const hasHover = hoveredId !== null; return (
{activeItem && (
{activeItem.author}
{activeItem.author} {activeItem.role} ยท{" "} {activeItem.company}
)}
setHoveredId(null)} className="relative flex-1 text-xl sm:text-2xl md:text-3xl lg:text-[32px] font-normal leading-[160%] tracking-tight text-zinc-900 dark:text-zinc-100" > {initialItems.map((item) => ( ))} {showMore && ( {" "} {additionalItems.map((item) => ( ))} )}
); } export default FocusTestimonials;