feat(contact, experience, projects, skills, workModalities): enhance component structure and user experience

- Refactored the Contact component to improve form handling and user feedback, introducing a custom shimmer button and enhanced form validation.
- Updated the Experience component to optimize rendering and improve data handling with memoization, ensuring efficient performance.
- Enhanced the Projects component with a new layout and improved project filtering, utilizing memoization for better performance.
- Refined the Skills component to streamline the display of technical skills, incorporating a loading state and error handling for better user experience.
- Revamped the WorkModalities component to present service options more clearly, including a market comparison section for better user decision-making.
This commit is contained in:
Evandro Fonseca Junior committed 2026-01-12 16:05:02 -03:00
1 parent 375862a65a
commit 8987d06eb4
5 files changed
+606 -1048

No files matched your search

+91 -241
View File
@@ -1,5 +1,6 @@
"use client";
import React, { useMemo } from "react";
import { motion } from "motion/react";
import { BentoGrid, BentoGridItem } from "@/components/magicui/bento-grid";
import {
@@ -10,292 +11,141 @@ import {
Globe,
Cpu,
Code2,
Terminal,
} from "lucide-react";
import { usePortfolioData } from "@/lib/usePortfolioData";
// Configuração de cores e ícones por categoria
const categoryConfig = {
"full-stack": {
color: "from-blue-500 to-blue-600",
bgColor: "bg-blue-500/10",
borderColor: "border-blue-500/20",
icon: Globe,
iconColor: "text-blue-400",
},
iot: {
color: "from-purple-500 to-purple-600",
bgColor: "bg-purple-500/10",
borderColor: "border-purple-500/20",
icon: Cpu,
iconColor: "text-purple-400",
},
api: {
color: "from-orange-500 to-orange-600",
bgColor: "bg-orange-500/10",
borderColor: "border-orange-500/20",
icon: Code2,
iconColor: "text-orange-400",
},
web: {
color: "from-green-500 to-green-600",
bgColor: "bg-green-500/10",
borderColor: "border-green-500/20",
icon: Globe,
iconColor: "text-green-400",
},
desktop: {
color: "from-emerald-500 to-emerald-600",
bgColor: "bg-emerald-500/10",
borderColor: "border-emerald-500/20",
icon: Code2,
iconColor: "text-emerald-400",
},
};
// Função para gerar estrelas de complexidade
const ComplexityStars = ({ complexity }: { complexity: number }) => {
return (
<div className="flex items-center gap-1">
{[...Array(5)].map((_, i) => (
<Star
key={i}
className={`w-3 h-3 ${
i < complexity ? "text-yellow-400 fill-yellow-400" : "text-gray-600"
}`}
/>
))}
</div>
);
};
// Componente de Badge
const ProjectBadge = ({ badge, index }: { badge: string; index: number }) => {
const badgeColors = [
"bg-blue-500/20 text-blue-400 border-blue-500/30",
"bg-green-500/20 text-green-400 border-green-500/30",
"bg-purple-500/20 text-purple-400 border-purple-500/30",
"bg-orange-500/20 text-orange-400 border-orange-500/30",
"bg-red-500/20 text-red-400 border-red-500/30",
];
return (
<span
className={`px-2 py-1 text-xs font-medium rounded-full border ${
badgeColors[index % badgeColors.length]
}`}
>
{badge}
</span>
);
// --- 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 },
};
export default function Projects() {
const { data, loading, error } = usePortfolioData();
if (loading) {
return (
<section id="projects" className="py-20 px-4">
<div className="max-w-7xl mx-auto text-center">
<div className="animate-pulse">
<div className="h-8 bg-gray-700 rounded w-1/3 mx-auto mb-4"></div>
<div className="h-4 bg-gray-700 rounded w-1/2 mx-auto"></div>
</div>
</div>
</section>
);
}
const projects = useMemo(() =>
data?.projects.filter((p) => p.featured) || [],
[data]);
if (error || !data) {
return (
<section id="projects" className="py-20 px-4">
<div className="max-w-7xl mx-auto text-center">
<p className="text-red-400">Erro ao carregar dados de projetos.</p>
</div>
</section>
);
}
const projects = data.projects.filter((project) => project.featured);
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-20 px-4 mx-auto">
<div className="max-w-7xl mx-auto">
<section id="projects" className="py-32 px-6 bg-[#030712] relative overflow-hidden">
{/* 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: 30 }}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
viewport={{ once: true }}
className="text-center mb-16"
className="text-left mb-20"
>
<h2 className="text-4xl md:text-5xl lg:text-6xl font-bold mb-6 bg-gradient-to-r from-cyan-400 to-blue-500 bg-clip-text text-transparent">
Projetos em Destaque
<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>
<p className="text-xl text-gray-300 max-w-3xl mx-auto">
Uma seleção dos projetos que desenvolvi, demonstrando minha evolução
técnica e capacidade de entregar soluções completas em diferentes
domínios
</p>
</motion.div>
<BentoGrid className="gap-6">
<BentoGrid className="grid-cols-1 md:grid-cols-3 gap-4">
{projects.map((project, index) => {
const config =
categoryConfig[project.category as keyof typeof categoryConfig] ||
categoryConfig["web"];
const IconComponent = config.icon;
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}
colSpan={index <= 1 ? 2 : 1} // NutriScan e Twins Lab em destaque
className={`group hover:scale-105 transition-all duration-300 hover:shadow-2xl ${config.borderColor} border-2 bg-white/5 backdrop-blur-sm`}
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]`}
>
<motion.div
className="space-y-5 h-full"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: index * 0.1 }}
viewport={{ once: true }}
>
{/* Header com ícone e badges */}
<div className="flex items-start justify-between">
<div className="flex items-center gap-3">
<div className={`p-3 rounded-xl ${config.bgColor}`}>
<IconComponent
className={`w-6 h-6 ${config.iconColor}`}
/>
{/* 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-xl font-bold text-white mb-1">
{project.title}
</h3>
<p className="text-sm text-gray-400 font-medium">
{project.subtitle}
</p>
<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.demoUrl && (
<a
href={project.demoUrl}
target="_blank"
rel="noopener noreferrer"
className="p-2 rounded-lg bg-green-500/20 text-green-400 hover:bg-green-500/30 transition-all duration-300 hover:scale-110"
aria-label="Ver demo"
>
<ExternalLink className="w-4 h-4" />
</a>
)}
{project.githubUrl && (
<a
href={project.githubUrl}
target="_blank"
rel="noopener noreferrer"
className="p-2 rounded-lg bg-gray-500/20 text-gray-400 hover:bg-gray-500/30 transition-all duration-300 hover:scale-110"
aria-label="Ver código"
>
<Github className="w-4 h-4" />
</a>
)}
{project.githubUrl && <ProjectLink href={project.githubUrl} icon={Github} />}
{project.demoUrl && <ProjectLink href={project.demoUrl} icon={ExternalLink} highlight />}
</div>
</div>
{/* Badges */}
<div className="flex flex-wrap gap-2">
{project.badges.map((badge, idx) => (
<ProjectBadge key={badge} badge={badge} index={idx} />
{/* 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>
{/* Description */}
<p className="text-gray-300 leading-relaxed text-sm">
<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>
{/* Tecnologias principais */}
<div>
<h4 className="text-xs font-semibold text-gray-400 mb-2 uppercase tracking-wide">
Stack Principal
</h4>
<div className="flex flex-wrap gap-1">
{project.technologies.slice(0, 4).map((tech) => (
<span
key={tech}
className="px-2 py-1 bg-gray-800/50 text-gray-300 text-xs rounded border border-gray-700/50"
>
{tech}
</span>
))}
{project.technologies.length > 4 && (
<span className="px-2 py-1 bg-gray-800/30 text-gray-500 text-xs rounded">
+{project.technologies.length - 4}
</span>
)}
</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>
{/* Features principais */}
<div>
<h4 className="text-xs font-semibold text-gray-400 mb-2 uppercase tracking-wide">
Destaques
</h4>
<ul className="space-y-1">
{project.features.slice(0, 3).map((feature, idx) => (
<li
key={idx}
className="text-xs text-gray-400 flex items-start gap-2"
>
<Zap className="w-3 h-3 text-yellow-400 mt-0.5 flex-shrink-0" />
<span>{feature}</span>
</li>
))}
</ul>
</div>
{/* Footer com complexidade e status */}
<div className="pt-4 border-t border-white/10 flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-xs text-gray-500">
Complexidade:
</span>
<ComplexityStars
complexity={project.metrics?.complexity || 3}
/>
</div>
<span
className={`text-xs font-medium px-2 py-1 rounded-full ${
project.status === "Em Produção"
? "bg-green-500/20 text-green-400 border border-green-500/30"
: "bg-blue-500/20 text-blue-400 border border-blue-500/30"
}`}
>
{project.status}
</span>
</div>
</motion.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>
{/* Call to Action */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.5 }}
viewport={{ once: true }}
className="text-center mt-16"
>
<div className="inline-flex items-center gap-2 bg-gradient-to-r from-blue-500/10 to-purple-500/10 px-6 py-3 rounded-full border border-blue-500/20">
<div className="w-2 h-2 bg-blue-400 rounded-full animate-pulse"></div>
<span className="text-sm text-blue-300 font-medium">
Todos os projetos são desenvolvidos individualmente, demonstrando
capacidade full-stack completa
</span>
</div>
</motion.div>
</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>
);
}