207 lines
8.5 KiB
TypeScript
207 lines
8.5 KiB
TypeScript
"use client";
|
|
|
|
import React, { useMemo } from "react";
|
|
import { motion } from "motion/react";
|
|
import { BentoGrid, BentoGridItem } from "@/components/magicui/bento-grid";
|
|
import {
|
|
ExternalLink,
|
|
Github,
|
|
Star,
|
|
Zap,
|
|
Globe,
|
|
Cpu,
|
|
Code2,
|
|
Terminal,
|
|
} from "lucide-react";
|
|
import { usePortfolioData } from "@/lib/usePortfolioData";
|
|
|
|
// --- Configuração de Cores (Harmonizado com a seção anterior) ---
|
|
const CATEGORY_THEMES: Record<string, any> = {
|
|
"full-stack": { color: "text-blue-400", bg: "bg-blue-500/10", border: "border-blue-500/20", icon: Globe },
|
|
iot: { color: "text-purple-400", bg: "bg-purple-500/10", border: "border-purple-500/20", icon: Cpu },
|
|
api: { color: "text-orange-400", bg: "bg-orange-500/10", border: "border-orange-500/20", icon: Code2 },
|
|
web: { color: "text-green-400", bg: "bg-green-500/10", border: "border-green-500/20", icon: Globe },
|
|
desktop: { color: "text-emerald-400", bg: "bg-emerald-500/10", border: "border-emerald-500/20", icon: Terminal },
|
|
};
|
|
|
|
// Funções auxiliares para Schema
|
|
function getApplicationCategory(category: string): string {
|
|
const map: Record<string, string> = {
|
|
"full-stack": "WebApplication",
|
|
"iot": "DesktopApplication",
|
|
"api": "WebApplication",
|
|
"web": "WebApplication",
|
|
"desktop": "DesktopApplication"
|
|
};
|
|
return map[category.toLowerCase()] || "WebApplication";
|
|
}
|
|
|
|
function getOperatingSystem(category: string): string {
|
|
if (category.toLowerCase() === "desktop") return "Windows, Linux";
|
|
return "Web Browser";
|
|
}
|
|
|
|
export default function Projects() {
|
|
const { data, loading, error } = usePortfolioData();
|
|
|
|
const projects = useMemo(() =>
|
|
data?.projects.filter((p) => p.featured) || [],
|
|
[data]);
|
|
|
|
const projectsJsonLd = useMemo(() => {
|
|
if (!projects || projects.length === 0) return null;
|
|
|
|
return {
|
|
"@context": "https://schema.org",
|
|
"@type": "ItemList",
|
|
"itemListElement": projects.map((project, index) => ({
|
|
"@type": "ListItem",
|
|
"position": index + 1,
|
|
"item": {
|
|
"@type": "SoftwareApplication",
|
|
"name": project.title,
|
|
"description": project.description,
|
|
"applicationCategory": getApplicationCategory(project.category),
|
|
"operatingSystem": getOperatingSystem(project.category),
|
|
"url": project.demoUrl || project.githubUrl || `https://evandro.dev.br/#projects`,
|
|
"offers": {
|
|
"@type": "Offer",
|
|
"price": "0",
|
|
"priceCurrency": "BRL"
|
|
},
|
|
"softwareVersion": "1.0.0",
|
|
"author": {
|
|
"@type": "Person",
|
|
"name": "Evandro Fonseca Junior",
|
|
"url": "https://evandro.dev.br"
|
|
}
|
|
}
|
|
}))
|
|
};
|
|
}, [projects]);
|
|
|
|
if (loading) return <div className="py-20 text-center animate-pulse text-slate-500 font-mono">Loading_Assets...</div>;
|
|
if (error) return <div className="py-20 text-center text-red-500">System_Error: Failed to sync projects.</div>;
|
|
|
|
return (
|
|
<section id="projects" className="py-32 px-6 relative">
|
|
{/* Background Decor */}
|
|
<div className="absolute inset-0 bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-blue-500/5 via-transparent to-transparent pointer-events-none" />
|
|
|
|
<div className="max-w-7xl mx-auto relative z-10">
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
className="text-left mb-20"
|
|
>
|
|
<span className="text-blue-500 font-mono text-sm tracking-[0.3em] uppercase block mb-4">Produção_Intelectual</span>
|
|
<h2 className="text-5xl md:text-7xl font-black text-white tracking-tighter">
|
|
Projetos <span className="text-slate-700 italic">Principais</span>
|
|
</h2>
|
|
</motion.div>
|
|
|
|
<BentoGrid className="grid-cols-1 md:grid-cols-3 gap-4">
|
|
{projects.map((project, index) => {
|
|
const theme = CATEGORY_THEMES[project.category.toLowerCase()] || CATEGORY_THEMES.web;
|
|
|
|
// Lógica de Grid Inteligente:
|
|
// Index 0: NutriScan (Destaque - 2 colunas)
|
|
// Index 1: Twins Lab (Complemento - 1 coluna)
|
|
// Demais: 1 coluna cada (Preenche a linha 2 perfeitamente)
|
|
const colSpan = index === 0 ? "md:col-span-2" : "md:col-span-1";
|
|
|
|
return (
|
|
<BentoGridItem
|
|
key={project.id}
|
|
className={`${colSpan} group relative flex flex-col justify-between overflow-hidden rounded-3xl border border-slate-800 bg-slate-900/40 backdrop-blur-xl p-8 transition-all duration-500 hover:border-blue-500/30 min-h-[22rem]`}
|
|
>
|
|
{/* Header: Icon & Links */}
|
|
<div className="relative z-10">
|
|
<div className="flex items-start justify-between mb-8">
|
|
<div className="flex items-center gap-4">
|
|
<div className={`p-4 rounded-2xl ${theme.bg} border ${theme.border} group-hover:scale-110 transition-transform duration-500`}>
|
|
<theme.icon className={`w-6 h-6 ${theme.color}`} />
|
|
</div>
|
|
<div>
|
|
<h3 className="text-2xl font-bold text-white tracking-tight">{project.title}</h3>
|
|
<p className="text-sm text-slate-500 font-medium">{project.subtitle}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex gap-2">
|
|
{project.githubUrl && <ProjectLink href={project.githubUrl} icon={Github} />}
|
|
{project.demoUrl && <ProjectLink href={project.demoUrl} icon={ExternalLink} highlight />}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Badges de Tecnologias */}
|
|
<div className="flex flex-wrap gap-2 mb-6">
|
|
{project.technologies.slice(0, 5).map((tech: string) => (
|
|
<span key={tech} className="px-2 py-1 text-[10px] font-mono font-bold bg-slate-950 text-slate-400 border border-slate-800 rounded-md">
|
|
{tech}
|
|
</span>
|
|
))}
|
|
</div>
|
|
|
|
<p className="text-slate-400 text-sm leading-relaxed mb-6 line-clamp-3 group-hover:line-clamp-none transition-all duration-500">
|
|
{project.description}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Footer: Metrics & Status */}
|
|
<div className="relative z-10 pt-6 border-t border-slate-800/50 mt-auto flex items-center justify-between">
|
|
<div className="flex items-center gap-3">
|
|
<span className="text-[10px] uppercase tracking-widest text-slate-600 font-bold">Complexity</span>
|
|
<ComplexityStars rating={project.metrics?.complexity || 3} />
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<div className={`w-2 h-2 rounded-full animate-pulse ${project.status === 'Em Produção' ? 'bg-emerald-500' : 'bg-blue-500'}`} />
|
|
<span className="text-[10px] font-bold text-slate-400 uppercase">{project.status}</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Background Hover Glow */}
|
|
<div className="absolute inset-0 bg-gradient-to-br from-blue-500/5 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-700" />
|
|
</BentoGridItem>
|
|
);
|
|
})}
|
|
</BentoGrid>
|
|
|
|
{/* JSON-LD Schema for Projects */}
|
|
{projectsJsonLd && (
|
|
<script
|
|
type="application/ld+json"
|
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(projectsJsonLd) }}
|
|
/>
|
|
)}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
// --- Subcomponentes Auxiliares ---
|
|
|
|
function ProjectLink({ href, icon: Icon, highlight }: { href: string; icon: any; highlight?: boolean }) {
|
|
return (
|
|
<a
|
|
href={href}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className={`p-2 rounded-xl transition-all duration-300 hover:scale-110 ${highlight ? 'bg-blue-600 text-white shadow-lg shadow-blue-600/20' : 'bg-slate-900 border border-slate-800 text-slate-500 hover:text-white'
|
|
}`}
|
|
>
|
|
<Icon className="w-4 h-4" />
|
|
</a>
|
|
);
|
|
}
|
|
|
|
function ComplexityStars({ rating }: { rating: number }) {
|
|
return (
|
|
<div className="flex gap-1">
|
|
{[...Array(5)].map((_, i) => (
|
|
<div key={i} className={`w-1.5 h-1.5 rounded-full ${i < rating ? 'bg-yellow-500' : 'bg-slate-800'}`} />
|
|
))}
|
|
</div>
|
|
);
|
|
} |