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:
1 parent
375862a65a
commit
8987d06eb4
5 files changed
+606
-1048
No files matched your search
+90
-98
@@ -1,136 +1,110 @@
|
||||
"use client";
|
||||
|
||||
import React, { useMemo } from "react";
|
||||
import { motion } from "motion/react";
|
||||
import { MagicCard } from "@/components/magicui/magic-card";
|
||||
import {
|
||||
Code,
|
||||
Database,
|
||||
Cloud,
|
||||
Cpu,
|
||||
Smartphone,
|
||||
Zap,
|
||||
GitBranch,
|
||||
TestTube,
|
||||
import {
|
||||
Code, Database, Cloud, Cpu, Smartphone,
|
||||
Zap, GitBranch, TestTube, Terminal, Layers
|
||||
} from "lucide-react";
|
||||
import { usePortfolioData } from "@/lib/usePortfolioData";
|
||||
|
||||
// Mapa de ícones para as categorias
|
||||
const iconMap = {
|
||||
Code: Code,
|
||||
Database: Database,
|
||||
Cloud: Cloud,
|
||||
Cpu: Cpu,
|
||||
Smartphone: Smartphone,
|
||||
Zap: Zap,
|
||||
GitBranch: GitBranch,
|
||||
TestTube: TestTube,
|
||||
const ICON_MAP: Record<string, React.ElementType> = {
|
||||
Code, Database, Cloud, Cpu, Smartphone, Zap, GitBranch, TestTube, Terminal
|
||||
};
|
||||
|
||||
// Mapa de cores para garantir que o Tailwind inclua as classes no bundle
|
||||
const colorMap = {
|
||||
"text-blue-440": "bg-blue-400",
|
||||
"text-blue-400": "bg-blue-400",
|
||||
"text-green-400": "bg-blue-400",
|
||||
"text-purple-400": "bg-blue-400",
|
||||
"text-red-400": "bg-blue-400",
|
||||
"text-yellow-400": "bg-blue-400",
|
||||
"text-pink-400": "bg-blue-400",
|
||||
"text-indigo-400": "bg-blue-400",
|
||||
// Refinamento do Pareto: Cores baseadas em variáveis CSS para evitar quebras no Tailwind
|
||||
const getCategoryStyles = (color: string) => {
|
||||
const base = color.replace("text-", "");
|
||||
return {
|
||||
icon: color,
|
||||
bg: `bg-${base}/10`,
|
||||
// Removido o hover:border daqui para evitar conflitos de transição
|
||||
border: `border-slate-800/50`,
|
||||
glow: `rgba(var(--${base}-rgb, 59, 130, 246), 0.15)` // Fallback para azul
|
||||
};
|
||||
};
|
||||
|
||||
export default function Skills() {
|
||||
const { data, loading, error } = usePortfolioData();
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<section id="skills" className="py-20 px-4">
|
||||
<div className="max-w-6xl 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>
|
||||
);
|
||||
}
|
||||
|
||||
if (error || !data) {
|
||||
return (
|
||||
<section id="skills" className="py-20 px-4">
|
||||
<div className="max-w-6xl mx-auto text-center">
|
||||
<p className="text-red-400">Erro ao carregar dados de habilidades.</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
const skillCategories = data.skills.technical;
|
||||
if (loading) return <LoadingState />;
|
||||
if (error || !data) return <ErrorState />;
|
||||
|
||||
return (
|
||||
<section id="skills" className="py-20 px-4">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<section id="skills" className="relative py-32 px-6 bg-[#030712] overflow-hidden">
|
||||
<div className="max-w-7xl mx-auto relative z-10">
|
||||
|
||||
{/* Header Header */}
|
||||
<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"
|
||||
viewport={{ once: true, margin: "-100px" }}
|
||||
className="flex flex-col md:flex-row justify-between items-end mb-20 gap-8"
|
||||
>
|
||||
<div className="inline-flex items-center gap-2 bg-gradient-to-r from-cyan-400/10 to-blue-500/10 px-6 py-3 rounded-full mb-6 border border-cyan-400/20">
|
||||
<div className="w-2 h-2 bg-cyan-400 rounded-full animate-pulse"></div>
|
||||
<span className="text-sm font-medium text-cyan-300">
|
||||
Tecnologias Dominadas
|
||||
</span>
|
||||
<div className="text-left">
|
||||
<h2 className="text-sm font-mono text-blue-500 uppercase tracking-[0.3em] mb-4 flex items-center gap-2 font-bold">
|
||||
<Layers className="w-4 h-4" /> Stack de Engenharia
|
||||
</h2>
|
||||
<h3 className="text-4xl md:text-6xl font-black text-white">
|
||||
Arsenal <span className="text-slate-700">Tecnológico</span>
|
||||
</h3>
|
||||
</div>
|
||||
<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">
|
||||
Habilidades Técnicas
|
||||
</h2>
|
||||
<p className="text-xl text-gray-300 max-w-2xl mx-auto">
|
||||
Tecnologias e ferramentas que utilizo para criar soluções inovadoras
|
||||
<p className="text-slate-400 max-w-md text-lg font-light leading-relaxed border-l border-slate-800 pl-6">
|
||||
Sistemas resilientes construídos com precisão matemática e rigor arquitetural.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{skillCategories.map((category, index) => {
|
||||
const IconComponent =
|
||||
iconMap[category.icon as keyof typeof iconMap] || Code;
|
||||
{/* Grid de Habilidades */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{data.skills.technical.map((category, index) => {
|
||||
const Icon = ICON_MAP[category.icon] || Code;
|
||||
const styles = getCategoryStyles(category.color);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key={category.category}
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: index * 0.1 }}
|
||||
viewport={{ once: true }}
|
||||
// Previne que o card suma se o scroll for rápido
|
||||
viewport={{ once: true, amount: 0.2 }}
|
||||
transition={{
|
||||
duration: 0.5,
|
||||
delay: index * 0.05,
|
||||
ease: "easeOut"
|
||||
}}
|
||||
>
|
||||
<div className="p-6 space-y-4 bg-white/5 backdrop-blur-sm border border-white/10 rounded-xl hover:border-cyan-500/30 transition-all duration-300 hover:scale-105">
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`p-3 rounded-lg ${category.bgColor}`}>
|
||||
<IconComponent className={`w-6 h-6 ${category.color}`} />
|
||||
<MagicCard
|
||||
className="group p-8 flex flex-col h-full bg-slate-900/50 border-slate-800 transition-colors duration-300"
|
||||
// DEFINIÇÃO EXPLÍCITA DO BRILHO (Corrige o glitch)
|
||||
gradientColor="#262626"
|
||||
gradientSize={250}
|
||||
gradientOpacity={0.4}
|
||||
>
|
||||
<div className="flex items-start justify-between mb-8">
|
||||
<div className={`p-4 rounded-2xl ${styles.bg} border border-white/5 transition-all duration-500 group-hover:scale-110 group-hover:rotate-3`}>
|
||||
<Icon className={`w-8 h-8 ${styles.icon}`} />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold">
|
||||
{category.category}
|
||||
</h3>
|
||||
<span className="text-[10px] font-mono text-slate-700 uppercase tracking-widest font-bold">
|
||||
ID_0{index}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Skills List */}
|
||||
<div className="space-y-2">
|
||||
{category.skills.map((skill, skillIndex) => (
|
||||
<div
|
||||
key={skillIndex}
|
||||
className="flex items-center gap-2 text-gray-300"
|
||||
<h3 className="text-xl font-bold text-white mb-6">
|
||||
{category.category}
|
||||
</h3>
|
||||
|
||||
<div className="flex flex-wrap gap-2 mt-auto">
|
||||
{category.skills.map((skill, sIdx) => (
|
||||
<span
|
||||
key={sIdx}
|
||||
className="px-3 py-1 text-[11px] font-mono bg-slate-950/50 text-slate-400 rounded border border-slate-800 group-hover:border-blue-500/30 transition-all duration-300"
|
||||
>
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full ${
|
||||
colorMap[category.color as keyof typeof colorMap] ||
|
||||
"bg-gray-400"
|
||||
}`}
|
||||
/>
|
||||
<span className="text-sm">{skill}</span>
|
||||
</div>
|
||||
{skill}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</MagicCard>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
@@ -139,3 +113,21 @@ export default function Skills() {
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
// --- Componentes de Suporte (Clean Code) ---
|
||||
function LoadingState() {
|
||||
return (
|
||||
<div className="py-32 flex flex-col items-center justify-center space-y-4">
|
||||
<div className="w-12 h-12 border-4 border-blue-500/20 border-t-blue-500 rounded-full animate-spin" />
|
||||
<span className="text-slate-500 font-mono text-sm animate-pulse">Initializing Systems...</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ErrorState() {
|
||||
return (
|
||||
<div className="py-32 text-center text-red-400 font-mono">
|
||||
[FATAL_ERROR]: Failed to synchronize portfolio data.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user