103 lines
3.9 KiB
TypeScript
103 lines
3.9 KiB
TypeScript
'use client';
|
||
|
||
import { motion, useInView } from 'framer-motion';
|
||
import { useRef } from 'react';
|
||
import { useLocale } from '@/lib/i18n/locale-context';
|
||
import { SectionHeader } from '@/components/ui/SectionHeader';
|
||
import { Counter } from '@/components/ui/Counter';
|
||
import { cn } from '@/lib/utils';
|
||
|
||
const FA_DIGITS = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'] as const;
|
||
const toFa = (s: string) =>
|
||
s.replace(/\d/g, (d) => FA_DIGITS[Number(d)]);
|
||
|
||
export function Expertise() {
|
||
const { t, locale } = useLocale();
|
||
const barsRef = useRef<HTMLDivElement>(null);
|
||
const inView = useInView(barsRef, { once: true, margin: '-80px' });
|
||
|
||
return (
|
||
<section id="expertise" className="relative px-5 py-28 sm:px-8">
|
||
<div className="mx-auto max-w-7xl">
|
||
<SectionHeader
|
||
eyebrow={t.expertise.eyebrow}
|
||
title={t.expertise.title}
|
||
sub={t.expertise.sub}
|
||
/>
|
||
|
||
<div className="mt-14 grid grid-cols-1 gap-10 lg:grid-cols-2">
|
||
{/* Metric tiles */}
|
||
<div className="grid grid-cols-2 gap-4 self-start">
|
||
{t.hero.metrics.map((m, i) => (
|
||
<motion.div
|
||
key={m.label}
|
||
initial={{ opacity: 0, y: 20 }}
|
||
whileInView={{ opacity: 1, y: 0 }}
|
||
viewport={{ once: true, margin: '-60px' }}
|
||
transition={{
|
||
duration: 0.5,
|
||
ease: [0.22, 1, 0.36, 1],
|
||
delay: 0.05 * i,
|
||
}}
|
||
className="glass relative overflow-hidden p-6"
|
||
>
|
||
<span
|
||
aria-hidden
|
||
className={cn(
|
||
'absolute inset-x-0 top-0 h-px',
|
||
'bg-gradient-to-r from-transparent via-electric/60 to-transparent',
|
||
)}
|
||
/>
|
||
<div
|
||
className={cn(
|
||
'font-display text-[clamp(1.8rem,3.5vw,2.6rem)] font-bold leading-none',
|
||
['text-electric', 'text-violet', 'text-magenta', 'text-emerald'][i % 4],
|
||
)}
|
||
>
|
||
<Counter value={m.value} locale={locale} />
|
||
</div>
|
||
<div className="mt-3 text-sm leading-snug text-slate-400">
|
||
{m.label}
|
||
</div>
|
||
</motion.div>
|
||
))}
|
||
</div>
|
||
|
||
{/* Skill bars */}
|
||
<div ref={barsRef} className="glass relative p-7 sm:p-8">
|
||
<span
|
||
aria-hidden
|
||
className="absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-magenta/60 to-transparent"
|
||
/>
|
||
<ul className="flex flex-col gap-6">
|
||
{t.expertise.bars.map((b, i) => (
|
||
<li key={b.label}>
|
||
<div className="mb-2 flex items-baseline justify-between text-sm">
|
||
<span className="text-slate-200">{b.label}</span>
|
||
<span className="font-mono text-xs text-slate-400">
|
||
{locale === 'fa' ? toFa(b.value.toString()) + '٪' : `${b.value}%`}
|
||
</span>
|
||
</div>
|
||
<div className="relative h-1.5 overflow-hidden rounded-full bg-white/[0.05]">
|
||
<motion.div
|
||
initial={{ width: 0 }}
|
||
animate={inView ? { width: `${b.value}%` } : { width: 0 }}
|
||
transition={{
|
||
duration: 1.2,
|
||
ease: [0.22, 1, 0.36, 1],
|
||
delay: 0.08 * i,
|
||
}}
|
||
className="absolute inset-y-0 start-0 rounded-full bg-brand-gradient"
|
||
style={{ backgroundSize: '200% 200%' }}
|
||
/>
|
||
</div>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|