- 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.
155 lines
6.3 KiB
TypeScript
155 lines
6.3 KiB
TypeScript
"use client";
|
|
|
|
import React, { useMemo } from "react";
|
|
import { motion, AnimatePresence } from "motion/react";
|
|
import {
|
|
Calendar, MapPin, Briefcase, Zap,
|
|
Award, Users, Terminal, ChevronRight
|
|
} from "lucide-react";
|
|
import { usePortfolioData } from "@/lib/usePortfolioData";
|
|
|
|
// --- Tipagem e Configuração (Otimização Pareto 80/20) ---
|
|
const CONFIG = {
|
|
"innovo-tecnologia": { color: "text-blue-400", bg: "bg-blue-400/10", border: "border-blue-400/20" },
|
|
"apolo-energia": { color: "text-emerald-400", bg: "bg-emerald-400/10", border: "border-emerald-400/20" },
|
|
default: { color: "text-slate-400", bg: "bg-slate-400/10", border: "border-slate-400/20" }
|
|
};
|
|
|
|
export default function Experience() {
|
|
const { data, loading, error } = usePortfolioData();
|
|
|
|
// Memória: Evita re-processamento de regex em cada re-render (Princípio de Eficiência)
|
|
const sortedExperiences = useMemo(() => {
|
|
if (!data?.experiences) return [];
|
|
return [...data.experiences]
|
|
.filter((exp) => exp.featured)
|
|
.sort((a, b) => {
|
|
const yearA = parseInt(a.period.match(/\d{4}/)?.[0] || "0");
|
|
const yearB = parseInt(b.period.match(/\d{4}/)?.[0] || "0");
|
|
return yearB - yearA; // Da mais recente para a mais antiga (Padrão Senior)
|
|
});
|
|
}, [data]);
|
|
|
|
if (loading) return <ExperienceSkeleton />;
|
|
|
|
return (
|
|
<section id="experience" className="relative py-32 bg-[#030712] overflow-hidden">
|
|
<div className="max-w-6xl mx-auto px-6 relative">
|
|
|
|
{/* Header com Identidade Visual */}
|
|
<div className="mb-24">
|
|
<motion.span
|
|
initial={{ opacity: 0 }}
|
|
whileInView={{ opacity: 1 }}
|
|
className="text-blue-500 font-mono text-sm tracking-[0.4em] uppercase"
|
|
>
|
|
System_History
|
|
</motion.span>
|
|
<h2 className="text-5xl md:text-7xl font-black text-white mt-4 tracking-tighter">
|
|
Trajetória <br />
|
|
<span className="text-slate-700">Profissional</span>
|
|
</h2>
|
|
</div>
|
|
|
|
{/* Timeline Desktop (Linha Central) */}
|
|
<div className="absolute left-[50%] top-[400px] bottom-0 w-[1px] bg-gradient-to-b from-blue-500/50 via-slate-800 to-transparent hidden lg:block" />
|
|
|
|
<div className="space-y-12">
|
|
<AnimatePresence>
|
|
{sortedExperiences.map((exp, index) => (
|
|
<ExperienceCard key={exp.id} exp={exp} index={index} />
|
|
))}
|
|
</AnimatePresence>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
// --- Sub-componente Card (Encapsulamento de Lógica de Transição) ---
|
|
function ExperienceCard({ exp, index }: { exp: any, index: number }) {
|
|
const isEven = index % 2 === 0;
|
|
const config = (CONFIG as any)[exp.company.toLowerCase().replace(/\s+/g, "-")] || CONFIG.default;
|
|
|
|
return (
|
|
<motion.div
|
|
// CORREÇÃO DO BUG: viewport com margin e once garante que a animação termine
|
|
initial={{ opacity: 0, y: 50 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true, amount: 0.3, margin: "0px 0px -100px 0px" }}
|
|
transition={{
|
|
duration: 0.8,
|
|
delay: index * 0.1,
|
|
ease: [0.16, 1, 0.3, 1] // Custom Ease-Out para suavidade premium
|
|
}}
|
|
className={`relative flex flex-col ${isEven ? 'lg:flex-row' : 'lg:flex-row-reverse'} items-center gap-8 lg:gap-16`}
|
|
>
|
|
{/* Indicador de Data na Timeline */}
|
|
<div className="hidden lg:flex flex-1 justify-end items-center">
|
|
<div className={`${isEven ? 'text-right' : 'text-left'} flex-1`}>
|
|
<span className="text-2xl font-mono font-bold text-slate-800 tracking-tighter group-hover:text-blue-500 transition-colors">
|
|
{exp.period.split(' ')[0]}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Card Principal */}
|
|
<div className="flex-1 w-full">
|
|
<motion.div
|
|
whileHover={{ y: -5 }} // Hover sutil sem quebrar o layout
|
|
className={`p-8 rounded-3xl bg-slate-900/40 border ${config.border} backdrop-blur-xl relative overflow-hidden group transition-all`}
|
|
>
|
|
{/* Badge de Empresa */}
|
|
<div className="flex flex-wrap justify-between items-start gap-4 mb-6">
|
|
<div className="space-y-1">
|
|
<h3 className="text-2xl font-bold text-white group-hover:text-blue-400 transition-colors">
|
|
{exp.title}
|
|
</h3>
|
|
<div className="flex items-center gap-2">
|
|
<span className={`text-sm font-bold uppercase tracking-widest ${config.color}`}>
|
|
{exp.company}
|
|
</span>
|
|
<span className="text-slate-600">•</span>
|
|
<span className="text-xs text-slate-500 font-mono">{exp.type}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className={`px-4 py-1.5 rounded-full ${config.bg} border ${config.border} text-[10px] font-bold text-white uppercase`}>
|
|
{exp.period}
|
|
</div>
|
|
</div>
|
|
|
|
<p className="text-slate-400 leading-relaxed mb-8 text-sm">
|
|
{exp.description}
|
|
</p>
|
|
|
|
{/* Conquistas (Técnica de Checklist de Engenharia) */}
|
|
<div className="grid grid-cols-1 gap-3 mb-8">
|
|
{exp.achievements?.map((ach: string, i: number) => (
|
|
<div key={i} className="flex gap-3 text-sm text-slate-300 group/item">
|
|
<ChevronRight className={`w-4 h-4 mt-0.5 flex-shrink-0 transition-transform group-hover/item:translate-x-1 ${config.color}`} />
|
|
<span>{ach}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Tags de Tech (Aparência de Chip de Hardware) */}
|
|
<div className="flex flex-wrap gap-2 pt-6 border-t border-slate-800/50">
|
|
{exp.technologies?.map((tech: string) => (
|
|
<span key={tech} className="px-3 py-1 bg-slate-950 border border-slate-800 text-slate-500 text-[10px] rounded-md font-mono hover:text-blue-400 hover:border-blue-400/30 transition-all">
|
|
{tech}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</motion.div>
|
|
</div>
|
|
|
|
{/* Spacer para Timeline Desktop */}
|
|
<div className="hidden lg:block flex-1" />
|
|
</motion.div>
|
|
);
|
|
}
|
|
|
|
function ExperienceSkeleton() {
|
|
return <div className="h-96 flex items-center justify-center text-slate-600 font-mono animate-pulse">Loading_System_Nodes...</div>;
|
|
} |