first commit
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
'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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user