Menu

Rich Testimonial Section

Recently added component.

Preview

Live interactive preview.

Installation

Add this component to your project using the CLI:

terminal
npx -y vui-registry-cli-v1@latest add rich-testimonial-section

Source Code

rich-testimonial-section.tsx
"use client";

import React, { useRef, useState } from "react";
import { motion, useScroll, useTransform, AnimatePresence } from "framer-motion";
import Image from "next/image";

const testimonials = [
  {
    name: "Alex Rivera",
    role: "Frontend Architect",
    text: "Velocity UI didn't just speed up our workflow; it completely redefined our standard for quality. The components feel truly premium out of the box.",
    avatar: "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=150&h=150&fit=crop&crop=faces"
  },
  {
    name: "Priya Sharma",
    role: "Design Lead",
    text: "As a designer, I'm extremely picky about typography and spacing. This is the first UI kit I've used that gets the microscopic details exactly right.",
    avatar: "https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=150&h=150&fit=crop&crop=faces"
  },
  {
    name: "Jordan Lee",
    role: "Indie Hacker",
    text: "Shipped my SaaS in a weekend. The dark mode implementation and glassmorphic details make it look like I had a full design team.",
    avatar: "https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=150&h=150&fit=crop&crop=faces"
  }
];

export default function RichTestimonialSection() {
  const containerRef = useRef<HTMLDivElement>(null);
  const [hoveredIdx, setHoveredIdx] = useState<number | null>(null);

  const { scrollYProgress } = useScroll({
    target: containerRef,
    offset: ["start end", "end start"]
  });

  const y1 = useTransform(scrollYProgress, [0, 1], [40, -40]);
  const y2 = useTransform(scrollYProgress, [0, 1], [80, -80]);

  return (
    <section ref={containerRef} className="relative w-full bg-background py-32 overflow-hidden selection:bg-foreground selection:text-background">
      
      <div className="max-w-7xl mx-auto px-6 relative z-20">
        
        {/* Header */}
        <div className="flex flex-col items-start mb-24">
          <motion.div 
            initial={{ opacity: 0, x: -20 }}
            whileInView={{ opacity: 1, x: 0 }}
            viewport={{ once: true }}
            className="flex items-center gap-4 mb-6"
          >
            <div className="w-8 h-[1px] bg-foreground/30" />
            <span className="text-[10px] font-bold uppercase tracking-[0.2em] text-muted-foreground">
              Social Proof
            </span>
          </motion.div>
          <motion.h2 
            initial={{ opacity: 0, y: 20 }}
            whileInView={{ opacity: 1, y: 0 }}
            viewport={{ once: true }}
            transition={{ delay: 0.1 }}
            className="text-4xl md:text-6xl font-bold tracking-tighter text-foreground max-w-3xl leading-[1.1]"
          >
            Loved by the best <br />
            <span className="text-muted-foreground font-medium">engineering teams.</span>
          </motion.h2>
        </div>

        {/* Testimonials Grid */}
        <div className="grid grid-cols-1 md:grid-cols-3 gap-8">
          {testimonials.map((t, idx) => (
            <motion.div 
              key={idx}
              style={{ y: idx === 1 ? y2 : y1 }}
              onMouseEnter={() => setHoveredIdx(idx)}
              onMouseLeave={() => setHoveredIdx(null)}
              className="relative group cursor-crosshair"
            >
              <div className="relative bg-card dark:bg-[#0A0A0A] p-8 h-full flex flex-col justify-between transition-all duration-500">
                {/* Corner Crosshairs */}
                <div className="absolute top-0 left-0 w-2 h-2 border-t border-l border-foreground/20 -translate-x-[1px] -translate-y-[1px]" />
                <div className="absolute top-0 right-0 w-2 h-2 border-t border-r border-foreground/20 translate-x-[1px] -translate-y-[1px]" />
                <div className="absolute bottom-0 left-0 w-2 h-2 border-b border-l border-foreground/20 -translate-x-[1px] translate-y-[1px]" />
                <div className="absolute bottom-0 right-0 w-2 h-2 border-b border-r border-foreground/20 translate-x-[1px] translate-y-[1px]" />

                {/* Border that fills on hover */}
                <div className="absolute inset-0 border border-border transition-colors duration-500 group-hover:border-foreground/20" />

                <div className="relative z-10">
                  <div className="flex justify-between items-start mb-8">
                    <svg className="w-6 h-6 text-foreground/20 group-hover:text-foreground transition-colors duration-500" fill="currentColor" viewBox="0 0 24 24">
                      <path d="M14.017 21v-7.391c0-5.704 3.731-9.57 8.983-10.609l.995 2.151c-2.432.917-3.995 3.638-3.995 5.849h4v10h-9.983zm-14.017 0v-7.391c0-5.704 3.748-9.57 9-10.609l.996 2.151c-2.433.917-3.996 3.638-3.996 5.849h3.983v10h-9.983z" />
                    </svg>
                    <span className="text-[9px] font-mono text-muted-foreground uppercase tracking-widest">
                      ID_0{idx + 1}
                    </span>
                  </div>
                  <p className="text-foreground text-lg leading-relaxed tracking-tight mb-8 font-medium">
                    "{t.text}"
                  </p>
                </div>
                
                <div className="relative z-10 flex items-center gap-4 pt-6 border-t border-border/50">
                  <div className="relative w-10 h-10 overflow-hidden">
                    <Image 
                      src={t.avatar} 
                      alt={t.name} 
                      fill 
                      className="object-cover grayscale group-hover:grayscale-0 group-hover:scale-110 transition-all duration-700 ease-out" 
                    />
                  </div>
                  <div>
                    <h4 className="text-sm font-bold text-foreground">{t.name}</h4>
                    <p className="text-[10px] font-bold text-muted-foreground uppercase tracking-widest mt-1">{t.role}</p>
                  </div>
                </div>
              </div>
            </motion.div>
          ))}
        </div>
      </div>
    </section>
  );
}