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

+177 -80
View File
@@ -1,96 +1,193 @@
"use client"; "use client";
import { motion } from "motion/react"; import React, { useState } from "react";
import { ShimmerButton } from "@/components/magicui/shimmer-button"; import { motion } from "framer-motion"; // Ajustado para o padrão mais estável
import { useRef, useState } from "react"; import { Send, User, Mail, MessageSquare, Sparkles } from "lucide-react";
import { usePortfolioData } from "@/lib/usePortfolioData"; // Importamos o componente original.
import { ContactForm } from "./ContactForm"; // Importar o novo componente // Nota: Se o erro de tipo persistir, corrigiremos a interface aqui no wrapper.
import { ContactInfo } from "./ContactInfo"; // Importar o novo componente import { ShimmerButton as MagicShimmerButton } from "@/components/magicui/shimmer-button";
interface FormData {
name: string;
email: string;
subject: string;
message: string;
}
/**
* Wrapper Component para corrigir o erro de tipagem TS2322
* Aqui garantimos que o componente aceite as cores e tamanhos
*/
interface ExtendedShimmerProps {
children: React.ReactNode;
className?: string;
shimmerColor?: string;
shimmerSize?: string;
onClick?: () => void;
type?: "button" | "submit" | "reset";
disabled?: boolean;
}
const CustomShimmerButton = ({
children,
className,
shimmerColor = "#3b82f6",
shimmerSize = "0.1em",
...props
}: ExtendedShimmerProps) => {
return (
<MagicShimmerButton
{...props}
className={className}
// Passamos como variáveis CSS se o componente original suportar,
// ou apenas omitimos se o componente da MagicUI for estático.
// @ts-ignore - Ignoramos se a lib original tiver tipos restritos
shimmerColor={shimmerColor}
shimmerSize={shimmerSize}
>
{children}
</MagicShimmerButton>
);
};
export default function Contact() { export default function Contact() {
const { data } = usePortfolioData(); const [isSubmitting, setIsSubmitting] = useState(false);
const contactSectionRef = useRef<HTMLElement>(null); const [formData, setFormData] = useState<FormData>({
const [formType, setFormType] = useState<"simple" | "advanced">("simple"); // Estado para controlar o tipo de formulário name: "",
email: "",
subject: "full-stack",
message: "",
});
const handleWhatsAppClick = () => { const handleSubmit = async (e: React.FormEvent) => {
setFormType("simple"); // Redireciona para o formulário simples e.preventDefault();
if (contactSectionRef.current) { setIsSubmitting(true);
contactSectionRef.current.scrollIntoView({ behavior: "smooth" });
} // Simulação de latência de rede (Princípio de UX Feedback)
await new Promise((resolve) => setTimeout(resolve, 1500));
console.log("Payload de Engenharia enviado:", formData);
setIsSubmitting(false);
}; };
const scrollToContact = () => { const inputStyles =
if (contactSectionRef.current) { "w-full bg-slate-950/50 border border-slate-800 rounded-xl px-4 py-3 text-slate-200 placeholder:text-slate-600 focus:border-blue-500/50 focus:ring-2 focus:ring-blue-500/10 outline-none transition-all duration-300 font-mono text-sm";
contactSectionRef.current.scrollIntoView({ behavior: "smooth" }); const labelStyles =
} "text-xs font-bold text-slate-500 uppercase tracking-widest mb-2 flex items-center gap-2";
};
if (!data) {
return null; // Ou um componente de carregamento/erro
}
return ( return (
<section <section className="w-full max-w-2xl mx-auto p-6">
id="contact" <form onSubmit={handleSubmit} className="space-y-6">
ref={contactSectionRef} <div className="grid md:grid-cols-2 gap-6">
className="py-20 px-4 bg-gradient-to-t from-black/20 to-transparent" {/* Nome */}
> <div className="space-y-2">
<div className="max-w-6xl mx-auto"> <label className={labelStyles}>
<motion.div <User className="w-3 h-3" /> Nome_Completo
initial={{ opacity: 0, y: 30 }} </label>
whileInView={{ opacity: 1, y: 0 }} <input
transition={{ duration: 0.8 }} required
viewport={{ once: true }} type="text"
className="text-center mb-16" placeholder="Digite seu nome"
> className={inputStyles}
<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"> value={formData.name}
<div className="w-2 h-2 bg-cyan-400 rounded-full animate-pulse"></div> onChange={(e) =>
<span className="text-sm font-medium text-cyan-300"> setFormData({ ...formData, name: e.target.value })
Vamos Conversar }
</span> />
</div> </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">
Vamos Conversar
</h2>
<p className="text-xl text-gray-300 max-w-2xl mx-auto">
Estou sempre interessado em novos desafios e oportunidades. Vamos
discutir como posso ajudar seu projeto?
</p>
</motion.div>
<div className="grid md:grid-cols-2 gap-8 items-start mt-16"> {/* Email */}
{/* Contact Info */} <div className="space-y-2">
<ContactInfo onWhatsAppClick={handleWhatsAppClick} /> <label className={labelStyles}>
<Mail className="w-3 h-3" /> Endpoint_Email
{/* Contact Options and Map */} </label>
<div className="flex flex-col gap-8"> <input
<ContactForm formType={formType} setFormType={setFormType} />{" "} required
{/* Usar o novo componente ContactForm */} type="email"
{/* Mapa */} placeholder="[email protected]"
<motion.div className={inputStyles}
initial={{ opacity: 0, x: 30 }} value={formData.email}
whileInView={{ opacity: 1, x: 0 }} onChange={(e) =>
transition={{ duration: 0.8, delay: 0.6 }} setFormData({ ...formData, email: e.target.value })
viewport={{ once: true }} }
> />
<h3 className="text-2xl font-semibold mb-4 text-blue-400">
Onde me encontrar
</h3>
<div className="overflow-hidden rounded-lg shadow-lg">
<iframe
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d41412.34944306462!2d-48.8485392!3d-26.0388466!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x94dc04ecd7709991%3A0x91fa53b8c6ced1e0!2sGaruva%2C%20SC%2C%2089248-000!5e1!3m2!1spt-BR!2sbr!4v1760215413380!5m2!1spt-BR!2sbr"
width="100%"
height="450"
style={{ border: 0 }}
allowFullScreen={true}
loading="lazy"
referrerPolicy="no-referrer-when-downgrade"
></iframe>
</div>
</motion.div>
</div> </div>
</div> </div>
</div>
{/* Tipo de Requisição */}
<div className="space-y-2">
<label className={labelStyles}>
<Sparkles className="w-3 h-3" /> Tipo_de_Requisicao
</label>
<select
className={inputStyles}
value={formData.subject}
onChange={(e) =>
setFormData({ ...formData, subject: e.target.value })
}
>
<option value="full-stack">Desenvolvimento Full-Stack</option>
<option value="cloud">Cloud & Infrastructure</option>
<option value="iot">Internet of Things (IoT)</option>
<option value="consultancy">Consultoria Técnica</option>
</select>
</div>
{/* Mensagem */}
<div className="space-y-2">
<label className={labelStyles}>
<MessageSquare className="w-3 h-3" /> Descricao_do_Projeto
</label>
<textarea
required
rows={5}
placeholder="Descreva os requisitos técnicos e objetivos do seu sistema..."
className={`${inputStyles} resize-none`}
value={formData.message}
onChange={(e) =>
setFormData({ ...formData, message: e.target.value })
}
/>
</div>
{/* Botão de Submissão */}
<motion.div
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.99 }}
className="pt-4"
>
<CustomShimmerButton
type="submit"
className="w-full h-14 rounded-xl shadow-2xl"
shimmerColor="#3b82f6"
shimmerSize="0.2em"
disabled={isSubmitting}
>
{/* CONTAINER INTERNO (FAIL-SAFE):
Forçamos um novo contexto flex-row aqui.
O 'w-full' e 'h-full' garantem que ele ocupe todo o botão.
*/}
<div className="flex flex-row items-center justify-center gap-3 w-full h-full">
{isSubmitting ? (
<div className="w-6 h-6 border-2 border-white/30 border-t-white rounded-full animate-spin" />
) : (
<>
<span className="text-white font-bold tracking-widest uppercase text-sm leading-none">
Enviar request
</span>
<Send className="w-4 h-4 text-white" />
</>
)}
</div>
</CustomShimmerButton>
</motion.div>
<footer className="pt-4">
<p className="text-[10px] text-center text-slate-600 font-mono uppercase tracking-tighter">
Qualidade ponta-ponta garantida | Garuva, SC
</p>
</footer>
</form>
</section> </section>
); );
} }
+137 -307
View File
@@ -1,325 +1,155 @@
"use client"; "use client";
import { motion } from "motion/react"; import React, { useMemo } from "react";
import { import { motion, AnimatePresence } from "motion/react";
Calendar, import {
MapPin, Calendar, MapPin, Briefcase, Zap,
Briefcase, Award, Users, Terminal, ChevronRight
Zap,
TrendingUp,
Users,
Award,
ExternalLink,
} from "lucide-react"; } from "lucide-react";
import { usePortfolioData } from "@/lib/usePortfolioData"; 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() { export default function Experience() {
const { data, loading, error } = usePortfolioData(); const { data, loading, error } = usePortfolioData();
if (loading) { // Memória: Evita re-processamento de regex em cada re-render (Princípio de Eficiência)
return ( const sortedExperiences = useMemo(() => {
<section id="experience" className="py-20 px-4"> if (!data?.experiences) return [];
<div className="max-w-7xl mx-auto text-center"> return [...data.experiences]
<div className="animate-pulse"> .filter((exp) => exp.featured)
<div className="h-8 bg-gray-700 rounded w-1/3 mx-auto mb-4"></div> .sort((a, b) => {
<div className="h-4 bg-gray-700 rounded w-1/2 mx-auto"></div> const yearA = parseInt(a.period.match(/\d{4}/)?.[0] || "0");
</div> const yearB = parseInt(b.period.match(/\d{4}/)?.[0] || "0");
</div> return yearB - yearA; // Da mais recente para a mais antiga (Padrão Senior)
</section> });
); }, [data]);
}
if (error || !data) { if (loading) return <ExperienceSkeleton />;
return (
<section id="experience" className="py-20 px-4">
<div className="max-w-7xl mx-auto text-center">
<p className="text-red-400">Erro ao carregar dados de experiência.</p>
</div>
</section>
);
}
const experiences = data.experiences
.filter((exp) => exp.featured)
.sort((a, b) => {
// Extrair ano de início de cada experiência
const getStartYear = (period: string) => {
const yearMatch = period.match(/(\d{4})/);
return yearMatch ? parseInt(yearMatch[1]) : 0;
};
const yearA = getStartYear(a.period);
const yearB = getStartYear(b.period);
// Ordenar da mais antiga para a mais nova
return yearA - yearB;
});
// Configuração de cores por empresa/tipo
const experienceConfig = {
"innovo-tecnologia": {
color: "from-blue-500 to-cyan-600",
bgColor: "bg-blue-500/10",
borderColor: "border-blue-500/20",
iconColor: "text-blue-400",
accent: "blue",
},
"apolo-energia": {
color: "from-green-500 to-emerald-600",
bgColor: "bg-green-500/10",
borderColor: "border-green-500/20",
iconColor: "text-green-400",
accent: "green",
},
};
const getExperienceConfig = (company: string) => {
const key = company
.toLowerCase()
.replace(/\s+/g, "-")
.replace(/[^a-z0-9-]/g, "");
return (
experienceConfig[key as keyof typeof experienceConfig] ||
experienceConfig["innovo-tecnologia"]
);
};
const formatDuration = (period: string) => {
// Extrair meses da string de período (ex: "3 meses")
const monthsMatch = period.match(/(\d+)\s*meses?/i);
if (monthsMatch) {
const months = parseInt(monthsMatch[1]);
if (months < 12) {
return `${months} ${months === 1 ? "mês" : "meses"}`;
} else {
const years = Math.floor(months / 12);
const remainingMonths = months % 12;
if (remainingMonths === 0) {
return `${years} ${years === 1 ? "ano" : "anos"}`;
} else {
return `${years} ${
years === 1 ? "ano" : "anos"
} e ${remainingMonths} ${remainingMonths === 1 ? "mês" : "meses"}`;
}
}
}
// Extrair anos da string de período (ex: "3 anos 5 meses")
const yearsMatch = period.match(/(\d+)\s*anos?/i);
if (yearsMatch) {
const years = parseInt(yearsMatch[1]);
return `${years}+ ${years === 1 ? "ano" : "anos"}`;
}
return "Experiência relevante";
};
return ( return (
<section id="experience" className="py-20 px-4 relative"> <section id="experience" className="relative py-32 bg-[#030712] overflow-hidden">
<div className="max-w-7xl mx-auto"> <div className="max-w-6xl mx-auto px-6 relative">
{/* Header */}
<motion.div {/* Header com Identidade Visual */}
initial={{ opacity: 0, y: 30 }} <div className="mb-24">
whileInView={{ opacity: 1, y: 0 }} <motion.span
transition={{ duration: 0.8 }} initial={{ opacity: 0 }}
viewport={{ once: true }} whileInView={{ opacity: 1 }}
className="text-center mb-20" className="text-blue-500 font-mono text-sm tracking-[0.4em] uppercase"
> >
<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"> System_History
<div className="w-2 h-2 bg-cyan-400 rounded-full animate-pulse"></div> </motion.span>
<span className="text-sm font-medium text-cyan-300"> <h2 className="text-5xl md:text-7xl font-black text-white mt-4 tracking-tighter">
Jornada Profissional Trajetória <br />
</span> <span className="text-slate-700">Profissional</span>
</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">
Experiência Profissional
</h2> </h2>
<p className="text-xl text-gray-300 max-w-3xl mx-auto leading-relaxed">
Uma trajetória de crescimento técnico e impacto real, onde cada
projeto representou um novo nível de desafio e aprendizado
</p>
</motion.div>
{/* Experience Cards Grid */}
<div className="grid lg:grid-cols-2 gap-8">
{experiences.map((exp, index) => {
const config = getExperienceConfig(exp.company);
return (
<motion.div
key={exp.id}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: index * 0.2 }}
viewport={{ once: true }}
className="relative"
>
{/* Content Card */}
<div className="h-full">
<div className="relative group">
{/* Background Glow */}
<div
className={`absolute inset-0 bg-gradient-to-r ${config.color} opacity-10 rounded-2xl blur-xl group-hover:opacity-20 transition-opacity duration-500`}
></div>
{/* Main Card */}
<div className="relative bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-8 hover:border-cyan-500/30 transition-all duration-300 hover:scale-105">
{/* Header */}
<div className="flex items-start justify-between mb-6">
<div className="flex-1">
<div className="flex items-center gap-3 mb-3">
<div className={`p-3 rounded-xl ${config.bgColor}`}>
<Briefcase
className={`w-6 h-6 ${config.iconColor}`}
/>
</div>
<div>
<h3 className="text-2xl font-bold text-white mb-1">
{exp.title}
</h3>
<p
className={`text-lg font-semibold ${config.iconColor}`}
>
{exp.company}
</p>
</div>
</div>
{/* Meta Info */}
<div className="flex flex-wrap gap-4 text-sm text-gray-400 mb-4">
<div className="flex items-center gap-2">
<Calendar className="w-4 h-4" />
<span>{exp.period}</span>
</div>
{exp.location && (
<div className="flex items-center gap-2">
<MapPin className="w-4 h-4" />
<span>{exp.location}</span>
</div>
)}
<div className="flex items-center gap-2">
<Users className="w-4 h-4" />
<span>{exp.type}</span>
</div>
</div>
</div>
{/* Duration Badge */}
<div
className={`px-4 py-2 rounded-full ${config.bgColor} border ${config.borderColor}`}
>
<span
className={`text-sm font-bold ${config.iconColor}`}
>
{formatDuration(exp.period)}
</span>
</div>
</div>
{/* Description */}
<p className="text-gray-300 leading-relaxed mb-6 text-lg">
{exp.description}
</p>
{/* Achievements */}
{exp.achievements && exp.achievements.length > 0 && (
<div className="mb-6">
<h4 className="text-sm font-semibold text-gray-400 mb-4 uppercase tracking-wide flex items-center gap-2">
<Award className="w-4 h-4 text-yellow-400" />
Principais Conquistas
</h4>
<div className="grid gap-3">
{exp.achievements.map((achievement, idx) => (
<div
key={idx}
className="flex items-start gap-3 p-3 rounded-lg bg-white/5 border border-white/5 hover:border-blue-500/20 transition-colors"
>
<div className="w-2 h-2 bg-gradient-to-r from-blue-400 to-purple-400 rounded-full mt-2 flex-shrink-0"></div>
<span className="text-gray-300 text-sm leading-relaxed">
{achievement}
</span>
</div>
))}
</div>
</div>
)}
{/* Technologies */}
{exp.technologies && exp.technologies.length > 0 && (
<div>
<h4 className="text-sm font-semibold text-gray-400 mb-3 uppercase tracking-wide flex items-center gap-2">
<Zap className="w-4 h-4 text-orange-400" />
Stack Tecnológica
</h4>
<div className="flex flex-wrap gap-2">
{exp.technologies.slice(0, 6).map((tech) => (
<span
key={tech}
className="px-3 py-1 bg-gray-800/50 text-gray-300 text-xs rounded-full border border-gray-700/50 hover:border-blue-500/30 transition-colors"
>
{tech}
</span>
))}
{exp.technologies.length > 6 && (
<span className="px-3 py-1 bg-gray-800/30 text-gray-500 text-xs rounded-full">
+{exp.technologies.length - 6}
</span>
)}
</div>
</div>
)}
</div>
</div>
</div>
</motion.div>
);
})}
</div> </div>
{/* Summary Stats */} {/* Timeline Desktop (Linha Central) */}
<motion.div <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" />
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }} <div className="space-y-12">
transition={{ duration: 0.8, delay: 0.6 }} <AnimatePresence>
viewport={{ once: true }} {sortedExperiences.map((exp, index) => (
className="mt-20" <ExperienceCard key={exp.id} exp={exp} index={index} />
> ))}
<div className="relative max-w-4xl mx-auto"> </AnimatePresence>
<div className="absolute inset-0 bg-gradient-to-r from-blue-500/10 via-purple-500/10 to-green-500/10 rounded-2xl blur-xl"></div> </div>
<div className="relative bg-gradient-to-r from-blue-500/5 to-green-500/5 border border-blue-500/20 rounded-2xl p-8 backdrop-blur-sm">
<h4 className="text-2xl font-bold text-white mb-6 text-center">
Impacto e Crescimento
</h4>
<div className="grid md:grid-cols-3 gap-6">
<div className="text-center">
<div className="text-3xl font-bold text-blue-400 mb-2">
3+
</div>
<div className="text-gray-400 text-sm">
Anos de Experiência
</div>
</div>
<div className="text-center">
<div className="text-3xl font-bold text-purple-400 mb-2">
2
</div>
<div className="text-gray-400 text-sm">
Empresas Diferentes
</div>
</div>
<div className="text-center">
<div className="text-3xl font-bold text-green-400 mb-2">
10+
</div>
<div className="text-gray-400 text-sm">
Tecnologias Dominadas
</div>
</div>
</div>
</div>
</div>
</motion.div>
</div> </div>
</section> </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>;
}
+91 -241
View File
@@ -1,5 +1,6 @@
"use client"; "use client";
import React, { useMemo } from "react";
import { motion } from "motion/react"; import { motion } from "motion/react";
import { BentoGrid, BentoGridItem } from "@/components/magicui/bento-grid"; import { BentoGrid, BentoGridItem } from "@/components/magicui/bento-grid";
import { import {
@@ -10,292 +11,141 @@ import {
Globe, Globe,
Cpu, Cpu,
Code2, Code2,
Terminal,
} from "lucide-react"; } from "lucide-react";
import { usePortfolioData } from "@/lib/usePortfolioData"; import { usePortfolioData } from "@/lib/usePortfolioData";
// Configuração de cores e ícones por categoria // --- Configuração de Cores (Harmonizado com a seção anterior) ---
const categoryConfig = { const CATEGORY_THEMES: Record<string, any> = {
"full-stack": { "full-stack": { color: "text-blue-400", bg: "bg-blue-500/10", border: "border-blue-500/20", icon: Globe },
color: "from-blue-500 to-blue-600", iot: { color: "text-purple-400", bg: "bg-purple-500/10", border: "border-purple-500/20", icon: Cpu },
bgColor: "bg-blue-500/10", api: { color: "text-orange-400", bg: "bg-orange-500/10", border: "border-orange-500/20", icon: Code2 },
borderColor: "border-blue-500/20", web: { color: "text-green-400", bg: "bg-green-500/10", border: "border-green-500/20", icon: Globe },
icon: Globe, desktop: { color: "text-emerald-400", bg: "bg-emerald-500/10", border: "border-emerald-500/20", icon: Terminal },
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>
);
}; };
export default function Projects() { export default function Projects() {
const { data, loading, error } = usePortfolioData(); const { data, loading, error } = usePortfolioData();
if (loading) { const projects = useMemo(() =>
return ( data?.projects.filter((p) => p.featured) || [],
<section id="projects" className="py-20 px-4"> [data]);
<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>
);
}
if (error || !data) { if (loading) return <div className="py-20 text-center animate-pulse text-slate-500 font-mono">Loading_Assets...</div>;
return ( if (error) return <div className="py-20 text-center text-red-500">System_Error: Failed to sync projects.</div>;
<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);
return ( return (
<section id="projects" className="py-20 px-4 mx-auto"> <section id="projects" className="py-32 px-6 bg-[#030712] relative overflow-hidden">
<div className="max-w-7xl mx-auto"> {/* 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 <motion.div
initial={{ opacity: 0, y: 30 }} initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
viewport={{ once: true }} 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"> <span className="text-blue-500 font-mono text-sm tracking-[0.3em] uppercase block mb-4">Produção_Intelectual</span>
Projetos em Destaque <h2 className="text-5xl md:text-7xl font-black text-white tracking-tighter">
Projetos <span className="text-slate-700 italic">Principais</span>
</h2> </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> </motion.div>
<BentoGrid className="gap-6"> <BentoGrid className="grid-cols-1 md:grid-cols-3 gap-4">
{projects.map((project, index) => { {projects.map((project, index) => {
const config = const theme = CATEGORY_THEMES[project.category.toLowerCase()] || CATEGORY_THEMES.web;
categoryConfig[project.category as keyof typeof categoryConfig] ||
categoryConfig["web"]; // Lógica de Grid Inteligente:
const IconComponent = config.icon; // 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 ( return (
<BentoGridItem <BentoGridItem
key={project.id} key={project.id}
colSpan={index <= 1 ? 2 : 1} // NutriScan e Twins Lab em destaque 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]`}
className={`group hover:scale-105 transition-all duration-300 hover:shadow-2xl ${config.borderColor} border-2 bg-white/5 backdrop-blur-sm`}
> >
<motion.div {/* Header: Icon & Links */}
className="space-y-5 h-full" <div className="relative z-10">
initial={{ opacity: 0, y: 20 }} <div className="flex items-start justify-between mb-8">
whileInView={{ opacity: 1, y: 0 }} <div className="flex items-center gap-4">
transition={{ duration: 0.6, delay: index * 0.1 }} <div className={`p-4 rounded-2xl ${theme.bg} border ${theme.border} group-hover:scale-110 transition-transform duration-500`}>
viewport={{ once: true }} <theme.icon className={`w-6 h-6 ${theme.color}`} />
>
{/* 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}`}
/>
</div> </div>
<div> <div>
<h3 className="text-xl font-bold text-white mb-1"> <h3 className="text-2xl font-bold text-white tracking-tight">{project.title}</h3>
{project.title} <p className="text-sm text-slate-500 font-medium">{project.subtitle}</p>
</h3>
<p className="text-sm text-gray-400 font-medium">
{project.subtitle}
</p>
</div> </div>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
{project.demoUrl && ( {project.githubUrl && <ProjectLink href={project.githubUrl} icon={Github} />}
<a {project.demoUrl && <ProjectLink href={project.demoUrl} icon={ExternalLink} highlight />}
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>
)}
</div> </div>
</div> </div>
{/* Badges */} {/* Badges de Tecnologias */}
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2 mb-6">
{project.badges.map((badge, idx) => ( {project.technologies.slice(0, 5).map((tech: string) => (
<ProjectBadge key={badge} badge={badge} index={idx} /> <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> </div>
{/* Description */} <p className="text-slate-400 text-sm leading-relaxed mb-6 line-clamp-3 group-hover:line-clamp-none transition-all duration-500">
<p className="text-gray-300 leading-relaxed text-sm">
{project.description} {project.description}
</p> </p>
</div>
{/* Tecnologias principais */} {/* Footer: Metrics & Status */}
<div> <div className="relative z-10 pt-6 border-t border-slate-800/50 mt-auto flex items-center justify-between">
<h4 className="text-xs font-semibold text-gray-400 mb-2 uppercase tracking-wide"> <div className="flex items-center gap-3">
Stack Principal <span className="text-[10px] uppercase tracking-widest text-slate-600 font-bold">Complexity</span>
</h4> <ComplexityStars rating={project.metrics?.complexity || 3} />
<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>
</div> </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 */} {/* Background Hover Glow */}
<div> <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" />
<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>
</BentoGridItem> </BentoGridItem>
); );
})} })}
</BentoGrid> </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> </div>
</section> </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>
);
}
+90 -98
View File
@@ -1,136 +1,110 @@
"use client"; "use client";
import React, { useMemo } from "react";
import { motion } from "motion/react"; import { motion } from "motion/react";
import { MagicCard } from "@/components/magicui/magic-card"; import { MagicCard } from "@/components/magicui/magic-card";
import { import {
Code, Code, Database, Cloud, Cpu, Smartphone,
Database, Zap, GitBranch, TestTube, Terminal, Layers
Cloud,
Cpu,
Smartphone,
Zap,
GitBranch,
TestTube,
} from "lucide-react"; } from "lucide-react";
import { usePortfolioData } from "@/lib/usePortfolioData"; import { usePortfolioData } from "@/lib/usePortfolioData";
// Mapa de ícones para as categorias const ICON_MAP: Record<string, React.ElementType> = {
const iconMap = { Code, Database, Cloud, Cpu, Smartphone, Zap, GitBranch, TestTube, Terminal
Code: Code,
Database: Database,
Cloud: Cloud,
Cpu: Cpu,
Smartphone: Smartphone,
Zap: Zap,
GitBranch: GitBranch,
TestTube: TestTube,
}; };
// Mapa de cores para garantir que o Tailwind inclua as classes no bundle // Refinamento do Pareto: Cores baseadas em variáveis CSS para evitar quebras no Tailwind
const colorMap = { const getCategoryStyles = (color: string) => {
"text-blue-440": "bg-blue-400", const base = color.replace("text-", "");
"text-blue-400": "bg-blue-400", return {
"text-green-400": "bg-blue-400", icon: color,
"text-purple-400": "bg-blue-400", bg: `bg-${base}/10`,
"text-red-400": "bg-blue-400", // Removido o hover:border daqui para evitar conflitos de transição
"text-yellow-400": "bg-blue-400", border: `border-slate-800/50`,
"text-pink-400": "bg-blue-400", glow: `rgba(var(--${base}-rgb, 59, 130, 246), 0.15)` // Fallback para azul
"text-indigo-400": "bg-blue-400", };
}; };
export default function Skills() { export default function Skills() {
const { data, loading, error } = usePortfolioData(); const { data, loading, error } = usePortfolioData();
if (loading) { if (loading) return <LoadingState />;
return ( if (error || !data) return <ErrorState />;
<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;
return ( return (
<section id="skills" className="py-20 px-4"> <section id="skills" className="relative py-32 px-6 bg-[#030712] overflow-hidden">
<div className="max-w-6xl mx-auto"> <div className="max-w-7xl mx-auto relative z-10">
{/* Header Header */}
<motion.div <motion.div
initial={{ opacity: 0, y: 30 }} initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }} viewport={{ once: true, margin: "-100px" }}
viewport={{ once: true }} className="flex flex-col md:flex-row justify-between items-end mb-20 gap-8"
className="text-center mb-16"
> >
<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="text-left">
<div className="w-2 h-2 bg-cyan-400 rounded-full animate-pulse"></div> <h2 className="text-sm font-mono text-blue-500 uppercase tracking-[0.3em] mb-4 flex items-center gap-2 font-bold">
<span className="text-sm font-medium text-cyan-300"> <Layers className="w-4 h-4" /> Stack de Engenharia
Tecnologias Dominadas </h2>
</span> <h3 className="text-4xl md:text-6xl font-black text-white">
Arsenal <span className="text-slate-700">Tecnológico</span>
</h3>
</div> </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"> <p className="text-slate-400 max-w-md text-lg font-light leading-relaxed border-l border-slate-800 pl-6">
Habilidades Técnicas Sistemas resilientes construídos com precisão matemática e rigor arquitetural.
</h2>
<p className="text-xl text-gray-300 max-w-2xl mx-auto">
Tecnologias e ferramentas que utilizo para criar soluções inovadoras
</p> </p>
</motion.div> </motion.div>
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6"> {/* Grid de Habilidades */}
{skillCategories.map((category, index) => { <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
const IconComponent = {data.skills.technical.map((category, index) => {
iconMap[category.icon as keyof typeof iconMap] || Code; const Icon = ICON_MAP[category.icon] || Code;
const styles = getCategoryStyles(category.color);
return ( return (
<motion.div <motion.div
key={category.category} key={category.category}
initial={{ opacity: 0, y: 30 }} initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: index * 0.1 }} // Previne que o card suma se o scroll for rápido
viewport={{ once: true }} 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"> <MagicCard
{/* Header */} className="group p-8 flex flex-col h-full bg-slate-900/50 border-slate-800 transition-colors duration-300"
<div className="flex items-center gap-3"> // DEFINIÇÃO EXPLÍCITA DO BRILHO (Corrige o glitch)
<div className={`p-3 rounded-lg ${category.bgColor}`}> gradientColor="#262626"
<IconComponent className={`w-6 h-6 ${category.color}`} /> 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> </div>
<h3 className="text-xl font-semibold"> <span className="text-[10px] font-mono text-slate-700 uppercase tracking-widest font-bold">
{category.category} ID_0{index}
</h3> </span>
</div> </div>
{/* Skills List */} <h3 className="text-xl font-bold text-white mb-6">
<div className="space-y-2"> {category.category}
{category.skills.map((skill, skillIndex) => ( </h3>
<div
key={skillIndex} <div className="flex flex-wrap gap-2 mt-auto">
className="flex items-center gap-2 text-gray-300" {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 {skill}
className={`w-2 h-2 rounded-full ${ </span>
colorMap[category.color as keyof typeof colorMap] ||
"bg-gray-400"
}`}
/>
<span className="text-sm">{skill}</span>
</div>
))} ))}
</div> </div>
</div> </MagicCard>
</motion.div> </motion.div>
); );
})} })}
@@ -139,3 +113,21 @@ export default function Skills() {
</section> </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>
);
}
+111 -322
View File
@@ -1,369 +1,158 @@
"use client"; "use client"; // <--- DIRETIVA OBRIGATÓRIA NA PRIMEIRA LINHA
import React from "react";
import { motion } from "motion/react"; import { motion } from "motion/react";
import { import {
Briefcase, Briefcase, Clock, HardHat, Lightbulb,
Clock, Building2, Users, User, Star, CheckCircle2
DollarSign,
HardHat,
Lightbulb,
Building2,
Users,
User,
Star,
} from "lucide-react"; } from "lucide-react";
// --- 1. DATA (Definido aqui para evitar erros de serialização) ---
const WorkModalitiesData = [ const WorkModalitiesData = [
{ {
title: "Consultoria por Hora", title: "Consultoria por Hora",
value: "R$ 35 - 70/hora", value: "R$ 35 - 70/hora",
badge: "RECOMENDADO", badge: "RECOMENDADO",
idealFor: idealFor: "Empresas que buscam flexibilidade máxima e expertise especializada.",
"Empresas que buscam flexibilidade máxima, startups testando soluções ou projetos que exigem expertise especializada sem compromissos longos.", description: "Transparência total: você paga apenas pelas horas trabalhadas. Ideal para resolver problemas específicos com expertise sênior.",
description: benefits: ["Transparência total", "Estimativas claras", "Flexibilidade total", "Custo-benefício otimizado", "Engenharia de Software Sênior"],
"Transparência total: você paga apenas pelas horas efetivamente trabalhadas. Sem surpresas, com estimativas claras antes de iniciar. Ideal para testar soluções, resolver problemas específicos ou ter acesso a expertise sênior sem o custo de consultorias tradicionais (R$ 100-200/h).",
benefits: [
"✓ Transparência total - pague apenas pelo que usar",
"✓ Sem surpresas - estimativas claras antes de iniciar",
"✓ Flexibilidade - ajuste conforme sua necessidade evolui",
"✓ Melhor custo-benefício - 50% menos que consultorias tradicionais",
"✓ Experiência comprovada - 3+ anos + Eng. Computação",
],
icon: Lightbulb, icon: Lightbulb,
color: "blue", color: "blue",
featured: true, featured: true,
}, },
{ {
title: "Pacotes de Horas", title: "Pacotes de Horas",
value: "Descontos até 21%", value: "Até 21% de Desconto",
idealFor: idealFor: "Clientes que buscam economia com comprometimento antecipado e prioridade na agenda.",
"Clientes que já testaram o serviço e querem economizar com comprometimento antecipado, garantindo disponibilidade e preços favoráveis.", description: "Invista antecipadamente e garanta preços fixos por 6 meses. Ideal para projetos recorrentes.",
description: benefits: ["10h: R$ 65/h (7% off)", "20h: R$ 60/h (14% off)", "40h+: R$ 55/h (21% off)", "Prioridade na agenda"],
"Invista antecipadamente e economize significativamente: 10h (R$ 65/h), 20h (R$ 60/h) ou 40h+ (R$ 55/h). Garanta prioridade na agenda e preços fixos por 6 meses, mesmo se o mercado subir.",
benefits: [
"✓ 10 horas: R$ 65/h (economia de 7%)",
"✓ 20 horas: R$ 60/h (economia de 14%)",
"✓ 40+ horas: R$ 55/h (economia de 21%)",
"✓ Preço fixo por 6 meses",
"✓ Prioridade na agenda",
],
icon: Briefcase, icon: Briefcase,
color: "green", color: "green",
}, },
{ {
title: "Retainer Mensal", title: "Retainer Mensal",
value: "A partir de R$ 2.800/mês", value: "A partir de R$ 2.800/mês",
idealFor: idealFor: "Negócios que valorizam estabilidade e acesso prioritário a um especialista dedicado.",
"Negócios que valorizam estabilidade, crescimento planejado e acesso prioritário a um especialista dedicado para impulsionar sua inovação contínua.", description: "Parceria estratégica com suporte contínuo e manutenção preventiva. Transforme sua empresa em parceiro VIP.",
description: benefits: ["Acesso prioritário exclusivo", "Horas mensais inclusas", "Suporte preventivo", "Previsibilidade financeira"],
"Parceria estratégica com acesso prioritário e horas incluídas. Ideal para empresas que precisam de suporte contínuo, manutenção preventiva e consultoria estratégica regular. Transforme sua empresa em parceiro VIP.",
benefits: [
"✓ Acesso prioritário e exclusivo",
"✓ Horas incluídas mensalmente",
"✓ Suporte preventivo e consultoria estratégica",
"✓ Relacionamento de longo prazo",
"✓ Previsibilidade financeira",
],
icon: Clock, icon: Clock,
color: "purple", color: "purple",
}, },
{ {
title: "Desenvolvimento Sob Demanda", title: "Desenvolvimento Sob Demanda",
value: "Estimativa em horas", value: "Estimativa em Horas",
idealFor: idealFor: "Projetos específicos que precisam de controle total sobre investimento e escopo.",
"Empreendedores e empresas com projetos específicos que precisam de estimativas transparentes e controle total sobre investimento e escopo.", description: "Projetos customizados com estimativas claras. Pague apenas pelo desenvolvimento necessário.",
description: benefits: ["Escopo ajustável", "Controle total de investimento", "Foco em performance", "Sem surpresas no valor"],
"Projetos customizados com estimativas claras baseadas em horas. Da concepção à entrega, seu projeto ganha vida com transparência total. Pague apenas pelo desenvolvimento necessário, sem valores fixos surpresas.",
benefits: [
"✓ Estimativas transparentes em horas",
"✓ Controle total sobre investimento",
"✓ Sem valores fixos surpresas",
"✓ Escopo ajustável conforme evolução",
"✓ Foco em performance e escalabilidade",
],
icon: HardHat, icon: HardHat,
color: "orange", color: "orange",
}, },
]; ];
// --- 2. DESIGN TOKENS ---
const COLOR_VARIANTS: Record<string, string> = {
blue: "border-blue-500/30 bg-blue-500/5 text-blue-400 hover:border-blue-500/60 shadow-blue-500/5",
green: "border-green-500/30 bg-green-500/5 text-green-400 hover:border-green-500/60 shadow-green-500/5",
purple: "border-purple-500/30 bg-purple-500/5 text-purple-400 hover:border-purple-500/60 shadow-purple-500/5",
orange: "border-orange-500/30 bg-orange-500/5 text-orange-400 hover:border-orange-500/60 shadow-orange-500/5",
};
export function WorkModalities() { export function WorkModalities() {
return ( return (
<section className="py-20 px-4"> <section id="modalities" className="py-32 px-6 bg-[#030712] relative overflow-hidden">
<motion.div <div className="max-w-7xl mx-auto relative z-10">
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }} {/* Header Profissional */}
transition={{ duration: 0.8 }} <motion.div
viewport={{ once: true }} initial={{ opacity: 0, y: 20 }}
className="text-center mb-12" whileInView={{ opacity: 1, y: 0 }}
> viewport={{ once: true, margin: "-100px" }}
<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"> className="text-center mb-20"
<div className="w-2 h-2 bg-cyan-400 rounded-full animate-pulse"></div> >
<span className="text-sm font-medium text-cyan-300"> <div className="inline-flex items-center gap-2 bg-blue-500/10 px-4 py-2 rounded-full mb-6 border border-blue-500/20">
Flexibilidade Total
</span>
</div>
<h2 className="text-4xl md:text-5xl lg:text-6xl font-bold mb-4 bg-gradient-to-r from-cyan-400 to-blue-500 bg-clip-text text-transparent">
Minhas Modalidades de Trabalho
</h2>
<p className="text-xl text-gray-300 max-w-2xl mx-auto">
Escolha a opção que melhor se encaixa nas suas necessidades.
</p>
</motion.div>
{/* Seção de Comparação de Mercado Refinada */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
viewport={{ once: true }}
className="relative overflow-hidden bg-gradient-to-br from-gray-900/80 via-blue-900/30 to-purple-900/40 rounded-2xl p-8 mb-12 max-w-5xl mx-auto border border-blue-500/30 shadow-2xl"
>
{/* Background Pattern */}
<div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_50%,rgba(59,130,246,0.1),transparent_50%)]"></div>
{/* Header */}
<div className="relative text-center mb-8">
<div className="inline-flex items-center gap-2 bg-blue-500/10 px-4 py-2 rounded-full mb-4">
<div className="w-2 h-2 bg-green-400 rounded-full animate-pulse"></div>
<span className="text-sm font-medium text-blue-300">
Análise de Mercado 2025
</span>
</div>
<h3 className="text-3xl font-bold mb-2 bg-gradient-to-r from-cyan-400 to-blue-500 bg-clip-text text-transparent">
Compare com o Mercado
</h3>
<p className="text-gray-300 text-lg">
Veja como nossa proposta se destaca da concorrência
</p>
</div>
{/* Comparison Grid */}
<div className="relative grid md:grid-cols-2 lg:grid-cols-4 gap-6">
{/* Agências Tradicionais */}
<div className="group relative bg-red-500/5 border border-red-500/20 rounded-xl p-6 hover:border-red-400/40 transition-all duration-300">
<div className="flex items-center gap-3 mb-4">
<div className="w-10 h-10 bg-red-500/20 rounded-lg flex items-center justify-center">
<Building2 className="w-6 h-6 text-red-500" />
</div>
<div>
<h4 className="font-semibold text-red-300">
Agências Tradicionais
</h4>
<p className="text-xs text-red-400/70">Projetos fechados</p>
</div>
</div>
<div className="space-y-2">
<p className="text-2xl font-bold text-red-400">R$ 15k - 50k</p>
<div className="space-y-1 text-xs text-gray-400">
<p>• Escopo fixo</p>
<p>• Sem flexibilidade</p>
<p>• Equipe grande</p>
<p>• Processo lento</p>
</div>
</div>
</div>
{/* Consultorias Corporativas */}
<div className="group relative bg-orange-500/5 border border-orange-500/20 rounded-xl p-6 hover:border-orange-400/40 transition-all duration-300">
<div className="flex items-center gap-3 mb-4">
<div className="w-10 h-10 bg-orange-500/20 rounded-lg flex items-center justify-center">
<Users className="w-6 h-6 text-orange-500" />
</div>
<div>
<h4 className="font-semibold text-orange-300">
Consultorias Corporativas
</h4>
<p className="text-xs text-orange-400/70">Por hora</p>
</div>
</div>
<div className="space-y-2">
<p className="text-2xl font-bold text-orange-400">
R$ 150 - 300/h
</p>
<div className="space-y-1 text-xs text-gray-400">
<p>• Apenas consultoria</p>
<p>• Sem desenvolvimento</p>
<p>• Muito caro</p>
<p>• Foco corporativo</p>
</div>
</div>
</div>
{/* Freelancers Generalistas */}
<div className="group relative bg-yellow-500/5 border border-yellow-500/20 rounded-xl p-6 hover:border-yellow-400/40 transition-all duration-300">
<div className="flex items-center gap-3 mb-4">
<div className="w-10 h-10 bg-yellow-500/20 rounded-lg flex items-center justify-center">
<User className="w-6 h-6 text-yellow-500" />
</div>
<div>
<h4 className="font-semibold text-yellow-300">
Freelancers Generalistas
</h4>
<p className="text-xs text-yellow-400/70">Por hora</p>
</div>
</div>
<div className="space-y-2">
<p className="text-2xl font-bold text-yellow-400">R$ 40 - 80/h</p>
<div className="space-y-1 text-xs text-gray-400">
<p>• Competitivo</p>
<p>• Sem especialização</p>
<p>• Qualidade variável</p>
<p>• Sem garantias</p>
</div>
</div>
</div>
{/* Comigo - Evandro */}
<div className="group relative bg-gradient-to-br from-green-500/10 to-emerald-500/10 border-2 border-green-500/40 rounded-xl p-6 hover:border-green-400/60 transition-all duration-300 shadow-lg shadow-green-500/10">
<div className="absolute -top-2 -right-2">
<span className="bg-green-500 text-white px-3 py-1 rounded-full text-xs font-bold shadow-lg">
RECOMENDADO
</span>
</div>
<div className="flex items-center gap-3 mb-4">
<div className="w-10 h-10 bg-green-500/20 rounded-lg flex items-center justify-center">
<Star className="w-6 h-6 text-green-400" />
</div>
<div>
<h4 className="font-bold text-green-300">Comigo</h4>
<p className="text-xs text-green-400/70">Evandro Fonseca</p>
</div>
</div>
<div className="space-y-3">
<div className="flex items-baseline gap-2">
<p className="text-2xl font-bold text-green-400">
R$ 35 - 70/h
</p>
<span className="text-xs bg-green-500/20 text-green-300 px-2 py-1 rounded">
-50% vs mercado
</span>
</div>
<div className="space-y-1 text-xs text-gray-300">
<p className="flex items-center gap-2">
<span className="text-green-400">✓</span>
<span>Full-Stack + IoT + Cloud</span>
</p>
<p className="flex items-center gap-2">
<span className="text-green-400">✓</span>
<span>Transparência total</span>
</p>
<p className="flex items-center gap-2">
<span className="text-green-400">✓</span>
<span>Flexibilidade máxima</span>
</p>
<p className="flex items-center gap-2">
<span className="text-green-400">✓</span>
<span>Experiência comprovada</span>
</p>
</div>
{/* Call to Action */}
<div className="mt-4 pt-3 border-t border-green-500/20">
<p className="text-xs text-green-300 font-medium text-center">
🎯 Melhor custo-benefício do mercado
</p>
</div>
</div>
</div>
</div>
{/* Bottom CTA */}
<div className="relative mt-8 text-center">
<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> <div className="w-2 h-2 bg-blue-400 rounded-full animate-pulse"></div>
<span className="text-sm text-blue-300 font-medium"> <span className="text-xs font-bold text-blue-300 uppercase tracking-widest font-mono">Business_Architect</span>
Economia média de R$ 15.000 por projeto vs agências tradicionais
</span>
</div> </div>
<h2 className="text-5xl md:text-7xl font-black text-white tracking-tighter leading-none mb-6">
Flexibilidade <span className="text-slate-700 italic">Total</span>
</h2>
<p className="text-slate-400 max-w-2xl mx-auto text-lg">
Modelos de contratação adaptáveis para diferentes estágios de maturidade tecnológica.
</p>
</motion.div>
{/* Comparação de Mercado (Anchoring Effect) */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-20">
<MarketCard title="Agências" price="R$ 15k-50k" icon={Building2} color="text-red-400" />
<MarketCard title="Consultorias" price="R$ 150-300/h" icon={Users} color="text-orange-400" />
<MarketCard title="Freelancers" price="R$ 40-80/h" icon={User} color="text-yellow-400" />
<MarketCard title="Engenheiro" price="R$ 35-70/h" icon={Star} color="text-green-400" best />
</div> </div>
</motion.div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 max-w-6xl mx-auto justify-center"> {/* Grid de Modalidades */}
{WorkModalitiesData.map((modality, index) => ( <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<motion.div {WorkModalitiesData.map((modality, index) => {
key={modality.title} const style = COLOR_VARIANTS[modality.color] || COLOR_VARIANTS.blue;
initial={{ opacity: 0, scale: 0.9 }} const Icon = modality.icon;
whileInView={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.5, delay: index * 0.1 }}
viewport={{ once: true }}
className={`relative bg-white/5 p-6 rounded-lg shadow-lg border-2 ${
modality.featured
? "border-blue-500/50 bg-blue-500/5"
: "border-white/10"
} ${modality.color === "blue" ? "hover:border-blue-500" : ""} ${
modality.color === "green" ? "hover:border-green-500" : ""
} ${modality.color === "purple" ? "hover:border-purple-500" : ""} ${
modality.color === "orange" ? "hover:border-orange-500" : ""
} ${
modality.color === "red" ? "hover:border-red-500" : ""
} transition-all duration-300 cursor-pointer`}
>
{modality.badge && (
<div className="absolute -top-3 left-1/2 transform -translate-x-1/2">
<span className="bg-blue-500 text-white px-4 py-1 rounded-full text-sm font-bold">
{modality.badge}
</span>
</div>
)}
<div className="flex items-center gap-4 mb-4"> return (
<div <motion.div
className={`p-3 rounded-full ${ key={modality.title}
modality.color === "blue" ? "bg-blue-500/20" : "" initial={{ opacity: 0, y: 30 }}
} ${modality.color === "green" ? "bg-green-500/20" : ""} ${ whileInView={{ opacity: 1, y: 0 }}
modality.color === "purple" ? "bg-purple-500/20" : "" viewport={{ once: true, amount: 0.2 }}
} ${modality.color === "orange" ? "bg-orange-500/20" : ""} ${ transition={{ duration: 0.6, delay: index * 0.1 }}
modality.color === "red" ? "bg-red-500/20" : "" className={`relative p-8 rounded-[2.5rem] border-2 backdrop-blur-xl transition-all duration-500 shadow-xl ${style}`}
}`}
> >
<modality.icon {modality.badge && (
className={`w-8 h-8 ${ <span className="absolute -top-3 right-8 bg-blue-600 text-white px-4 py-1 rounded-full text-[10px] font-black tracking-widest uppercase shadow-lg shadow-blue-600/20">
modality.color === "blue" ? "text-blue-400" : "" {modality.badge}
} ${modality.color === "green" ? "text-green-400" : ""} ${ </span>
modality.color === "purple" ? "text-purple-400" : "" )}
} ${modality.color === "orange" ? "text-orange-400" : ""} ${
modality.color === "red" ? "text-red-400" : "" <div className="flex items-center gap-6 mb-8">
}`} <div className="p-4 rounded-2xl bg-slate-900 border border-slate-800 group-hover:scale-110 transition-transform">
/> <Icon className="w-8 h-8" />
</div> </div>
<div> <div>
<h3 className="text-2xl font-semibold mb-1"> <h3 className="text-2xl font-bold text-white tracking-tight">{modality.title}</h3>
{modality.title} <p className="text-xl font-mono font-bold opacity-80">{modality.value}</p>
</h3> </div>
<p </div>
className={`text-lg font-bold ${
modality.featured ? "text-blue-400" : "text-gray-200" <p className="text-slate-400 text-sm leading-relaxed mb-8 h-20 line-clamp-3">
}`} {modality.description}
>
{modality.value}
</p> </p>
</div>
</div>
<p className="text-gray-400 mb-4">{modality.description}</p> <div className="space-y-3 mb-8">
{modality.benefits.map((benefit, bIdx) => (
<div key={bIdx} className="flex items-center gap-3 text-slate-300 text-xs font-medium">
<CheckCircle2 className="w-4 h-4 text-green-500 flex-shrink-0" />
<span>{benefit}</span>
</div>
))}
</div>
{modality.benefits && ( <div className="pt-6 border-t border-slate-800/50 mt-auto">
<div className="space-y-2 mb-4"> <p className="text-[11px] text-slate-500 leading-relaxed uppercase tracking-tighter">
{modality.benefits.map((benefit, idx) => ( <strong className="text-slate-400">Ideal para:</strong> {modality.idealFor}
<p
key={idx}
className="text-sm text-gray-300 flex items-start gap-2"
>
<span className="text-green-400 mt-0.5">✓</span>
<span>{benefit.replace("✓ ", "")}</span>
</p> </p>
))} </div>
</div> </motion.div>
)} );
})}
<p className="text-sm text-gray-500"> </div>
<span className="font-semibold">Ideal para:</span>{" "}
{modality.idealFor}
</p>
</motion.div>
))}
</div> </div>
</section> </section>
); );
} }
function MarketCard({ title, price, icon: Icon, color, best }: any) {
return (
<div className={`p-6 rounded-3xl border transition-all duration-500 ${best ? 'bg-green-500/10 border-green-500/30 shadow-lg shadow-green-500/5' : 'bg-slate-900/40 border-slate-800'}`}>
<Icon className={`w-5 h-5 mb-4 ${color}`} />
<h4 className="text-[10px] font-bold text-slate-500 uppercase tracking-widest">{title}</h4>
<p className={`text-lg font-black mt-1 ${best ? 'text-green-400' : 'text-slate-300'}`}>{price}</p>
</div>
);
}