126 lines
3.1 KiB
TypeScript
126 lines
3.1 KiB
TypeScript
|
|
'use client';
|
||
|
|
|
||
|
|
import { motion } from 'framer-motion';
|
||
|
|
import { useTranslations } from 'next-intl';
|
||
|
|
import { Compass, Shield, Brain, MapPin } from 'lucide-react';
|
||
|
|
import { cn } from '@/lib/utils';
|
||
|
|
import {
|
||
|
|
revealVariants,
|
||
|
|
staggerContainerWide,
|
||
|
|
viewportOnce,
|
||
|
|
} from '@/lib/animations';
|
||
|
|
import type { LucideIcon } from 'lucide-react';
|
||
|
|
|
||
|
|
// Each item's icon scale-bounce on enter
|
||
|
|
const iconBounceVariants = {
|
||
|
|
hidden: { scale: 0.7, opacity: 0 },
|
||
|
|
visible: {
|
||
|
|
scale: 1,
|
||
|
|
opacity: 1,
|
||
|
|
transition: {
|
||
|
|
duration: 0.5,
|
||
|
|
ease: [0.34, 1.56, 0.64, 1] as [number, number, number, number], // spring-like overshoot
|
||
|
|
},
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
interface TrustItem {
|
||
|
|
key: string;
|
||
|
|
Icon: LucideIcon;
|
||
|
|
}
|
||
|
|
|
||
|
|
const ITEMS: TrustItem[] = [
|
||
|
|
{ key: 'customBuilt', Icon: Compass },
|
||
|
|
{ key: 'privateInfra', Icon: Shield },
|
||
|
|
{ key: 'aiPowered', Icon: Brain },
|
||
|
|
{ key: 'rivieraBased', Icon: MapPin },
|
||
|
|
];
|
||
|
|
|
||
|
|
interface TrustCardProps {
|
||
|
|
item: TrustItem;
|
||
|
|
index: number;
|
||
|
|
t: (key: string) => string;
|
||
|
|
}
|
||
|
|
|
||
|
|
function TrustCard({ item, index, t }: TrustCardProps) {
|
||
|
|
const { Icon, key } = item;
|
||
|
|
|
||
|
|
return (
|
||
|
|
<motion.div
|
||
|
|
variants={revealVariants}
|
||
|
|
className={cn(
|
||
|
|
'group flex flex-col items-start gap-4 p-6',
|
||
|
|
'rounded-2xl bg-surface-high shadow-subtle',
|
||
|
|
'transition-shadow duration-300 hover:shadow-card',
|
||
|
|
'cursor-default',
|
||
|
|
)}
|
||
|
|
>
|
||
|
|
{/* Icon with scale-bounce on scroll reveal */}
|
||
|
|
<motion.div
|
||
|
|
variants={iconBounceVariants}
|
||
|
|
className={cn(
|
||
|
|
'flex items-center justify-center w-12 h-12 rounded-xl',
|
||
|
|
'bg-primary/8',
|
||
|
|
'transition-transform duration-300 ease-out',
|
||
|
|
'group-hover:-translate-y-1',
|
||
|
|
)}
|
||
|
|
aria-hidden="true"
|
||
|
|
>
|
||
|
|
<Icon
|
||
|
|
size={28}
|
||
|
|
className="text-primary transition-colors duration-300"
|
||
|
|
strokeWidth={1.75}
|
||
|
|
/>
|
||
|
|
</motion.div>
|
||
|
|
|
||
|
|
{/* Title */}
|
||
|
|
<div>
|
||
|
|
<h3
|
||
|
|
className={cn(
|
||
|
|
'font-semibold text-on-surface text-base leading-snug mb-1',
|
||
|
|
'transition-colors duration-300 group-hover:text-primary-dark',
|
||
|
|
)}
|
||
|
|
>
|
||
|
|
{t(`${key}.title`)}
|
||
|
|
</h3>
|
||
|
|
|
||
|
|
{/* Description */}
|
||
|
|
<p className="text-sm text-outline leading-relaxed">
|
||
|
|
{t(`${key}.description`)}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
</motion.div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
export default function TrustBar() {
|
||
|
|
const t = useTranslations('trustBar');
|
||
|
|
|
||
|
|
return (
|
||
|
|
<section
|
||
|
|
aria-label="Trust indicators"
|
||
|
|
className="bg-surface-low py-16"
|
||
|
|
>
|
||
|
|
<div className="max-w-6xl mx-auto px-6">
|
||
|
|
{/* Stagger wrapper — triggers children revealVariants on scroll */}
|
||
|
|
<motion.div
|
||
|
|
variants={staggerContainerWide}
|
||
|
|
initial="hidden"
|
||
|
|
whileInView="visible"
|
||
|
|
viewport={viewportOnce}
|
||
|
|
className="grid grid-cols-2 md:grid-cols-4 gap-6 md:gap-8"
|
||
|
|
>
|
||
|
|
{ITEMS.map((item, index) => (
|
||
|
|
<TrustCard
|
||
|
|
key={item.key}
|
||
|
|
item={item}
|
||
|
|
index={index}
|
||
|
|
t={t}
|
||
|
|
/>
|
||
|
|
))}
|
||
|
|
</motion.div>
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
);
|
||
|
|
}
|