194 lines
8.1 KiB
TypeScript
194 lines
8.1 KiB
TypeScript
"use client";
|
|
|
|
import React, { useMemo } from "react";
|
|
import { motion } from "framer-motion";
|
|
import {
|
|
ChevronRight,
|
|
Terminal,
|
|
Cpu,
|
|
Network,
|
|
Code2
|
|
} 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", icon: Cpu },
|
|
"apolo-energia": { color: "text-emerald-400", bg: "bg-emerald-400/10", border: "border-emerald-400/20", icon: Network },
|
|
default: { color: "text-slate-400", bg: "bg-slate-400/10", border: "border-slate-400/20", icon: Code2 }
|
|
};
|
|
|
|
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">
|
|
{/* Background Gradients */}
|
|
<div className="absolute top-0 left-0 w-full h-full pointer-events-none">
|
|
<div className="absolute top-[20%] left-[-10%] w-[500px] h-[500px] bg-blue-900/10 rounded-full blur-[100px]" />
|
|
<div className="absolute bottom-[20%] right-[-10%] w-[500px] h-[500px] bg-indigo-900/10 rounded-full blur-[100px]" />
|
|
</div>
|
|
|
|
<div className="max-w-6xl mx-auto px-6 relative z-10">
|
|
|
|
{/* Header com Identidade Visual */}
|
|
<div className="mb-24 text-center md:text-left">
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-blue-950/30 border border-blue-500/20 text-blue-400 font-mono text-xs tracking-wider mb-6"
|
|
>
|
|
<Terminal className="w-3 h-3" />
|
|
<span>SYSTEM_LOGS.history</span>
|
|
</motion.div>
|
|
<motion.h2
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
transition={{ delay: 0.1 }}
|
|
className="text-4xl md:text-6xl font-black text-white tracking-tighter"
|
|
>
|
|
Trajetória <span className="bg-gradient-to-r from-blue-500 to-indigo-500 bg-clip-text text-transparent">Profissional</span>
|
|
</motion.h2>
|
|
</div>
|
|
|
|
<div className="relative">
|
|
{/* Timeline Desktop (Linha Central com Gradiente) */}
|
|
<div className="absolute left-[50%] top-0 bottom-0 w-[2px] bg-gradient-to-b from-blue-500/50 via-indigo-500/20 to-transparent hidden lg:block translate-x-[-50%]" />
|
|
|
|
<div className="space-y-24">
|
|
{sortedExperiences.map((exp, index) => (
|
|
<ExperienceCard key={exp.id} exp={exp} index={index} />
|
|
))}
|
|
</div>
|
|
</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;
|
|
const Icon = config.icon;
|
|
|
|
return (
|
|
<div className={`relative flex flex-col ${isEven ? 'lg:flex-row' : 'lg:flex-row-reverse'} items-center gap-8 lg:gap-16`}>
|
|
|
|
{/* Node Central (O "Ponto" da Timeline) */}
|
|
<motion.div
|
|
initial={{ scale: 0, opacity: 0 }}
|
|
whileInView={{ scale: 1, opacity: 1 }}
|
|
viewport={{ once: true }}
|
|
transition={{ delay: 0.2, duration: 0.5 }}
|
|
className="hidden lg:flex absolute left-[50%] top-0 translate-x-[-50%] w-4 h-4 rounded-full bg-blue-500 shadow-[0_0_20px_rgba(59,130,246,0.5)] z-20 items-center justify-center"
|
|
>
|
|
<div className="w-2 h-2 rounded-full bg-white animate-pulse" />
|
|
</motion.div>
|
|
|
|
{/* Indicador de Data na Timeline */}
|
|
<div className="hidden lg:flex flex-1 justify-end items-start pt-2">
|
|
<motion.div
|
|
initial={{ opacity: 0, x: isEven ? -20 : 20 }}
|
|
whileInView={{ opacity: 1, x: 0 }}
|
|
viewport={{ once: true }}
|
|
transition={{ delay: 0.3 }}
|
|
className={`${isEven ? 'text-right' : 'text-left'} flex-1`}
|
|
>
|
|
<span className="text-4xl font-black text-transparent bg-clip-text bg-gradient-to-b from-slate-700 to-slate-800 tracking-tighter opacity-50 select-none">
|
|
{exp.period.split(' ')[0]}
|
|
</span>
|
|
</motion.div>
|
|
</div>
|
|
|
|
{/* Card Principal */}
|
|
<div className="flex-1 w-full relative group perspective-1000">
|
|
<motion.div
|
|
initial={{ opacity: 0, x: isEven ? 50 : -50 }}
|
|
whileInView={{ opacity: 1, x: 0 }}
|
|
viewport={{ once: true, margin: "-100px" }}
|
|
transition={{ duration: 0.6, delay: 0.1 * index }}
|
|
whileHover={{ y: -5, transition: { duration: 0.2 } }}
|
|
className={`p-8 rounded-3xl bg-zinc-900/40 border border-white/5 backdrop-blur-md relative overflow-hidden transition-all duration-300 hover:shadow-[0_0_30px_rgba(59,130,246,0.1)] hover:border-blue-500/20`}
|
|
>
|
|
{/* Background Glow Effect on Hover */}
|
|
<div className="absolute inset-0 bg-gradient-to-br from-blue-500/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
|
|
|
|
{/* Badge de Empresa & Header */}
|
|
<div className="relative z-10">
|
|
<div className="flex flex-wrap justify-between items-start gap-4 mb-6">
|
|
<div className="space-y-2">
|
|
<div className="flex items-center gap-3">
|
|
<div className={`p-2 rounded-lg ${config.bg} ${config.color}`}>
|
|
<Icon className="w-5 h-5" />
|
|
</div>
|
|
<span className={`text-xs font-bold uppercase tracking-widest ${config.color}`}>
|
|
{exp.company}
|
|
</span>
|
|
</div>
|
|
<h3 className="text-2xl font-bold text-white group-hover:text-blue-400 transition-colors">
|
|
{exp.title}
|
|
</h3>
|
|
</div>
|
|
|
|
<div className="px-3 py-1 rounded-full bg-white/5 border border-white/10 text-[10px] font-mono font-bold text-slate-400 uppercase tracking-wide">
|
|
{exp.period}
|
|
</div>
|
|
</div>
|
|
|
|
<p className="text-slate-400 leading-relaxed mb-8 text-sm">
|
|
{exp.description}
|
|
</p>
|
|
|
|
{/* Conquistas (Checklist Técnico) */}
|
|
<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 items-start">
|
|
<div className="mt-1.5 w-1.5 h-1.5 rounded-full bg-blue-500 flex-shrink-0" />
|
|
<span>{ach}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Tags de Tech */}
|
|
<div className="flex flex-wrap gap-2 pt-6 border-t border-white/5">
|
|
{exp.technologies?.map((tech: string) => (
|
|
<span key={tech} className="px-3 py-1 bg-black/20 border border-white/5 text-slate-500 text-[10px] rounded-full font-mono hover:text-blue-400 hover:border-blue-400/30 transition-all cursor-default">
|
|
{tech}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
</div>
|
|
|
|
{/* Spacer para Timeline Desktop */}
|
|
<div className="hidden lg:block flex-1" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function ExperienceSkeleton() {
|
|
return (
|
|
<div className="h-96 flex flex-col items-center justify-center gap-4 text-slate-600 font-mono animate-pulse">
|
|
<Cpu className="w-8 h-8 animate-spin" />
|
|
<span>BOOTING_SYSTEM_HISTORY...</span>
|
|
</div>
|
|
);
|
|
} |