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:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user