Import source: src/components/Projects

Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
arsam
2026-09-18 23:05:53 +03:30
parent e2692431a5
commit c12cbc0a8d
+222
View File
@@ -0,0 +1,222 @@
'use client';
import { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { useTranslations } from 'next-intl';
import { ExternalLink } from 'lucide-react';
type Project = {
id: string;
category: 'ai' | 'web';
titleKey: string;
descKey: string;
tags: string[];
link: string;
};
export default function Projects() {
const t = useTranslations('Projects');
const [filter, setFilter] = useState<'all' | 'ai' | 'web'>('all');
const projectsList: Project[] = [
{
id: 'ai1',
category: 'ai',
titleKey: 'list.ai1.title',
descKey: 'list.ai1.description',
tags: ['LLM Ops', 'Evaluation', 'Python', 'HuggingFace'],
link: 'https://github.com/Coaxys',
},
{
id: 'ai2',
category: 'ai',
titleKey: 'list.ai2.title',
descKey: 'list.ai2.description',
tags: ['Inference', 'vLLM', 'Quantization', 'Docker'],
link: 'https://github.com/Coaxys',
},
{
id: 'ai3',
category: 'ai',
titleKey: 'list.ai3.title',
descKey: 'list.ai3.description',
tags: ['RAG', 'Agents', 'LangChain', 'VectorDB'],
link: 'https://github.com/Coaxys',
},
{
id: 'ai4',
category: 'ai',
titleKey: 'list.ai4.title',
descKey: 'list.ai4.description',
tags: ['Data Engineering', 'Ray', 'NLP', 'FastAPI'],
link: 'https://github.com/Coaxys',
},
{
id: 'ai5',
category: 'ai',
titleKey: 'list.ai5.title',
descKey: 'list.ai5.description',
tags: ['Speech', 'Whisper', 'PyTorch', 'Gradio'],
link: 'https://github.com/Coaxys',
},
{
id: 'web1',
category: 'web',
titleKey: 'list.web1.title',
descKey: 'list.web1.description',
tags: ['UI/UX', 'Next.js', 'Tailwind CSS', 'Framer Motion'],
link: 'https://teknav.ir',
},
{
id: 'web2',
category: 'web',
titleKey: 'list.web2.title',
descKey: 'list.web2.description',
tags: ['Branding', 'React', 'Chart.js', 'Figma'],
link: 'https://github.com/Coaxys',
},
{
id: 'web3',
category: 'web',
titleKey: 'list.web3.title',
descKey: 'list.web3.description',
tags: ['Open Source', 'MDX', 'Tailwind CSS', 'SEO'],
link: 'https://github.com/Coaxys',
},
{
id: 'web4',
category: 'web',
titleKey: 'list.web4.title',
descKey: 'list.web4.description',
tags: ['Frontend', 'Next.js', 'WebGL', 'Framer Motion'],
link: 'https://github.com/Coaxys',
},
{
id: 'web5',
category: 'web',
titleKey: 'list.web5.title',
descKey: 'list.web5.description',
tags: ['Branding', 'CSS Grid', 'Interactivity', 'Vite'],
link: 'https://github.com/Coaxys',
},
];
const filteredProjects = projectsList.filter(
(p) => filter === 'all' || p.category === filter
);
return (
<section id="projects" className="py-24 bg-warm-white dark:bg-near-black border-t border-sand/20 dark:border-charcoal/50 transition-colors duration-300">
<div className="max-w-[1200px] mx-auto px-6">
{/* Header */}
<div className="text-center max-w-2xl mx-auto mb-12">
<h2 className="text-xs font-semibold tracking-widest text-terracotta uppercase mb-2">
{t('title')}
</h2>
<h3 className="text-3xl font-extrabold tracking-tight text-charcoal dark:text-warm-white">
{t('subtitle')}
</h3>
</div>
{/* Tab Filters */}
<div className="flex justify-center mb-12">
<div className="inline-flex rounded-full border border-sand dark:border-charcoal p-1 bg-warm-white/50 dark:bg-charcoal/20 backdrop-blur-sm">
{(['all', 'ai', 'web'] as const).map((tab) => (
<button
key={tab}
onClick={() => setFilter(tab)}
className={`relative px-4 py-2 text-xs md:text-sm font-semibold rounded-full transition-colors cursor-pointer ${
filter === tab
? 'text-warm-white'
: 'text-charcoal/70 dark:text-warm-white/60 hover:text-teal-brand dark:hover:text-teal-brand'
}`}
>
{filter === tab && (
<motion.div
layoutId="activeTab"
className="absolute inset-0 bg-teal-brand rounded-full -z-10"
transition={{ type: 'spring', stiffness: 380, damping: 30 }}
/>
)}
{t(`tab${tab.charAt(0).toUpperCase() + tab.slice(1)}`)}
</button>
))}
</div>
</div>
{/* Projects Grid */}
<motion.div
layout
className="grid grid-cols-1 md:grid-cols-2 gap-6"
>
<AnimatePresence mode="popLayout">
{filteredProjects.map((p) => (
<motion.div
layout
key={p.id}
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.4 }}
className="group relative rounded-2xl border border-sand dark:border-charcoal bg-warm-white dark:bg-charcoal/10 overflow-hidden hover:shadow-md hover:border-teal-brand/30 dark:hover:border-teal-brand/30 transition-all duration-300 flex flex-col justify-between"
>
<div className="p-6">
{/* Category Pill */}
<span className={`inline-block px-2.5 py-0.5 rounded-full text-[10px] font-bold uppercase tracking-wider mb-4 ${
p.category === 'ai'
? 'bg-teal-brand/10 text-teal-brand'
: 'bg-terracotta/10 text-terracotta'
}`}>
{p.category === 'ai' ? t('aiLabel') : t('webLabel')}
</span>
{/* Title */}
<h4 className="text-xl font-bold text-charcoal dark:text-warm-white mb-2 group-hover:text-teal-brand dark:group-hover:text-teal-brand transition-colors">
{t(p.titleKey)}
</h4>
{/* Description */}
<p className="text-sm text-charcoal/70 dark:text-warm-white/60 leading-relaxed line-clamp-3 mb-6">
{t(p.descKey)}
</p>
</div>
<div className="px-6 pb-6 pt-0">
{/* Tech Tags */}
<div className="flex flex-wrap gap-1.5 mb-6">
{p.tags.map((tag) => (
<span
key={tag}
className="px-2 py-0.5 text-[10px] font-semibold rounded bg-sand/30 dark:bg-charcoal/40 text-charcoal/60 dark:text-warm-white/50 border border-sand/10"
>
{tag}
</span>
))}
</div>
{/* Card Actions / Hover overlay equivalent */}
<div className="flex items-center justify-between border-t border-sand/20 dark:border-charcoal/20 pt-4">
<a
href={p.link}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1.5 text-xs font-bold text-teal-brand hover:text-teal-brand/80 dark:hover:text-teal-brand/80 transition-colors"
>
<span>{t('viewProject')}</span>
<ExternalLink className="w-3.5 h-3.5" />
</a>
</div>
</div>
{/* Subtle Hover Action Overlay (as per spec: "Hover reveals a subtle overlay with a 'View' button") */}
<div className="absolute inset-0 bg-warm-white/5 dark:bg-near-black/5 opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none border border-teal-brand dark:border-teal-brand rounded-2xl" />
</motion.div>
))}
</AnimatePresence>
</motion.div>
</div>
</section>
);
}