Next.js 14 website with standalone output configured for Docker deployment. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
85 lines
2.6 KiB
TypeScript
85 lines
2.6 KiB
TypeScript
"use client";
|
|
|
|
import { motion, useInView } from "framer-motion";
|
|
import { useRef, useState } from "react";
|
|
import { SectionHeader } from "@/components/shared/SectionHeader";
|
|
import { testimonials } from "@/lib/constants";
|
|
|
|
function TestimonialCard({
|
|
testimonial,
|
|
index,
|
|
}: {
|
|
testimonial: (typeof testimonials)[0];
|
|
index: number;
|
|
}) {
|
|
const ref = useRef(null);
|
|
const isInView = useInView(ref, { once: true, margin: "-50px" });
|
|
|
|
return (
|
|
<motion.div
|
|
ref={ref}
|
|
className="bg-white rounded-3xl p-8 shadow-sm border border-deep-nazar/5 flex flex-col"
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
|
transition={{ delay: index * 0.1, duration: 0.5 }}
|
|
>
|
|
<div className="flex gap-1 mb-4">
|
|
{Array.from({ length: testimonial.rating }).map((_, i) => (
|
|
<span key={i} className="text-sun-yolk text-lg">
|
|
★
|
|
</span>
|
|
))}
|
|
</div>
|
|
|
|
<p className="text-deep-nazar/80 text-base leading-relaxed flex-1 mb-6 italic">
|
|
“{testimonial.quote}”
|
|
</p>
|
|
|
|
<div className="flex items-center gap-3 pt-4 border-t border-deep-nazar/5">
|
|
<div className="w-10 h-10 rounded-full bg-bosphorus/10 flex items-center justify-center text-lg">
|
|
{testimonial.flag}
|
|
</div>
|
|
<div>
|
|
<p className="font-semibold text-sm text-deep-nazar">
|
|
{testimonial.name}
|
|
</p>
|
|
<p className="text-xs text-deep-nazar/50">{testimonial.type}</p>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
);
|
|
}
|
|
|
|
export function Testimonials() {
|
|
const [showAll, setShowAll] = useState(false);
|
|
const displayed = showAll ? testimonials : testimonials.slice(0, 3);
|
|
|
|
return (
|
|
<section className="py-20 md:py-32 section-padding">
|
|
<div className="max-w-7xl mx-auto">
|
|
<SectionHeader
|
|
eyebrow="Guest Stories"
|
|
title="Don't Take Our Word for It"
|
|
subtitle="200+ guests. 4.9 average rating. Here's what they say."
|
|
/>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
|
{displayed.map((t, i) => (
|
|
<TestimonialCard key={i} testimonial={t} index={i} />
|
|
))}
|
|
</div>
|
|
|
|
{!showAll && testimonials.length > 3 && (
|
|
<div className="text-center mt-8">
|
|
<button
|
|
onClick={() => setShowAll(true)}
|
|
className="text-bosphorus font-semibold text-sm hover:underline"
|
|
>
|
|
Show all {testimonials.length} reviews →
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|