feat: Implement initial portfolio website structure with core components and a new post creator tool.

This commit is contained in:
Evandro Fonseca Junior committed 2026-03-07 06:50:56 -03:00
1 parent 036363b521
commit aa07d6faeb
15 files changed
+1337 -1855

No files matched your search

+62 -166
View File
@@ -1,194 +1,90 @@
"use client";
import { Section } from "./Section";
import React from "react";
import { motion } from "framer-motion";
import { LucideIcon, Brain, Zap, Target, Rocket, Users, Lightbulb, Code, Shield, Terminal, Cpu } from "lucide-react";
import Squares from "@/components/Squares";
// --- Types & Interfaces (Tipagem Forte para Previsibilidade) ---
interface StoryStep {
icon: LucideIcon;
title: string;
content: string;
gradient: string;
accent: string;
id: string;
}
interface CoreValue {
icon: LucideIcon;
title: string;
description: string;
}
// --- Data (Desacoplado para facilitar manutenção/CMS) ---
const STORY_DATA: StoryStep[] = [
const STORY_DATA = [
{
id: "KERNEL_INIT",
icon: Brain,
title: "Engenharia de Fundamentos",
content: "Minha mentalidade é pautada pela redução de entropia. Não apenas escrevo código; projeto sistemas onde a escalabilidade é uma propriedade intrínseca da arquitetura, não um ajuste posterior.",
gradient: "from-blue-600 to-cyan-500",
accent: "text-blue-400",
title: "Engenharia de fundamentos",
content:
"Projeto sistemas com foco em previsibilidade, manutenção e crescimento real. Escala entra na arquitetura desde o início.",
},
{
id: "RUNTIME_EXEC",
icon: Zap,
title: "Entrega sob Restrições",
content: "Em 3 anos de atuação profissional, especializei-me em cenários de alta criticidade. Atuei na ponte entre sistemas legados e microserviços, garantindo a resolução do problema.",
gradient: "from-purple-600 to-pink-600",
accent: "text-purple-400",
title: "Entrega sob restrições",
content:
"Atuo bem em cenários com legado, prazos apertados e decisões de produto em movimento, sem sacrificar legibilidade técnica.",
},
];
const VALUES_DATA: CoreValue[] = [
const VALUES_DATA = [
{
icon: Code,
title: "Pragmatismo Arquitetural",
description: "Priorizo a manutenibilidade e a resiliência. Código limpo é o meio, sistemas que geram ROI são o fim."
title: "Pragmatismo arquitetural",
description:
"Decisões guiadas por ROI, operabilidade e clareza para o time que mantém o sistema depois.",
},
{
icon: Brain,
title: "Fundacionalismo Técnico",
description: "Domínio profundo de estruturas de dados e protocolos. Tecnologias mudam, fundamentos são perenes."
title: "Fundação técnica",
description:
"Fundamentos fortes em dados, protocolos e modelagem para evitar soluções frágeis e modismos vazios.",
},
{
icon: Users,
title: "Propriedade e Ownership",
description: "Comunicação de baixa latência e responsabilidade radical sobre cada entrega e decisão técnica."
title: "Ownership real",
description:
"Responsabilidade sobre entrega, comunicação e qualidade final, não só sobre o trecho de código escrito.",
},
{
icon: Shield,
title: "Security by Design",
description: "Integridade e conformidade técnica (GDPR/LGPD) tratadas como requisitos funcionais, não opcionais."
title: "Security by design",
description:
"Integridade, proteção de dados e conformidade tratadas como parte do produto, não como remendo.",
},
];
// --- Micro-componentes (Feynman Technique: Simplicidade e Reuso) ---
const SectionHeader = () => (
<div className="text-center md:text-left mb-24 relative z-10">
<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>WHOAMI.sys</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"
>
Engenharia <br />
<span className="text-transparent bg-clip-text bg-gradient-to-r from-blue-500 to-cyan-400">Nuclear</span>
</motion.h2>
<p className="mt-6 text-lg text-slate-400 max-w-2xl leading-relaxed font-light border-l-2 border-slate-800 pl-6">
"Transformando complexidade técnica em valor de negócio através da engenharia."
</p>
</div>
);
const StoryCard = ({ step, index }: { step: StoryStep; index: number }) => {
const isEven = index % 2 === 0;
return (
<motion.div
initial={{ opacity: 0, x: isEven ? -30 : 30 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true, margin: "-100px" }}
className={`flex flex-col ${isEven ? "md:flex-row" : "md:flex-row-reverse"} items-stretch gap-8 mb-20 relative`}
>
<div className="flex-1 w-full">
<div className="group relative h-full p-8 rounded-3xl bg-zinc-900/40 border border-white/5 hover:border-blue-500/30 transition-all duration-500 shadow-xl backdrop-blur-xl overflow-hidden">
{/* Background Glow */}
<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" />
<div className="relative z-10">
<div className="flex items-center gap-4 mb-6">
<div className="p-3 rounded-xl bg-blue-500/10 border border-blue-500/20 text-blue-400 group-hover:scale-110 transition-transform duration-300">
<step.icon className="w-6 h-6" />
</div>
<span className="text-xs font-mono text-slate-500 uppercase tracking-widest">
{step.id}
</span>
</div>
<h3 className="text-2xl font-bold text-white mb-4 group-hover:text-blue-400 transition-colors">{step.title}</h3>
<p className="text-slate-400 leading-relaxed text-sm">{step.content}</p>
</div>
</div>
</div>
{/* Connection Line & Node */}
<div className="hidden md:flex flex-col items-center justify-center w-12 relative">
<div className="absolute top-0 bottom-0 w-[1px] bg-slate-800 group-hover:bg-blue-500/50 transition-colors" />
<div className="w-4 h-4 rounded-full bg-slate-900 border-2 border-slate-700 z-10 group-hover:border-blue-500 transition-colors" />
</div>
<div className="flex-1 hidden md:block" />
</motion.div>
);
};
export default function About() {
return (
<section id="about" className="relative py-32">
{/* Background Gradients */}
<div className="absolute top-0 left-0 w-full h-full pointer-events-none">
<div className="absolute top-[-10%] right-[-10%] w-[600px] h-[600px] bg-blue-900/10 rounded-full blur-[120px]" />
<div className="absolute bottom-[-10%] left-[-10%] w-[600px] h-[600px] bg-purple-900/10 rounded-full blur-[120px]" />
</div>
<div className="container max-w-6xl mx-auto px-6 relative z-10">
<SectionHeader />
<div className="relative mt-20">
{/* Center Line for Mobile/Desktop */}
<div className="absolute left-6 md:left-1/2 top-0 bottom-0 w-[1px] bg-slate-800 md:-translate-x-1/2 hidden md:block" />
{STORY_DATA.map((step, idx) => (
<StoryCard key={idx} step={step} index={idx} />
<Section
id="about"
eyebrow="WHOAMI.sys"
title="Engenharia orientada por clareza, não por ruído"
description="Transformo complexidade técnica em sistemas estáveis, operáveis e alinhados ao negócio."
aside={
<div className="mt-sp-4 ds-metric">
<span className="inline-block h-1.5 w-1.5 rounded-full bg-slash" />
Garuva, SC · Remote worldwide
</div>
}
>
<div className="ds-grid-2">
<div className="ds-grid-2">
{STORY_DATA.map((step) => (
<article key={step.id} className="ds-card">
<div className="ds-kicker">{step.id}</div>
<h3 className="ds-card-title">{step.title}</h3>
<p className="ds-copy">{step.content}</p>
</article>
))}
</div>
{/* Grid de Valores com Pareto 80/20 */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 mt-32">
{VALUES_DATA.map((val, i) => (
<motion.div
key={i}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: i * 0.1 }}
className="p-6 rounded-2xl bg-zinc-900/30 border border-white/5 hover:border-blue-500/20 hover:bg-zinc-900/50 transition-all duration-300 group"
>
<div className="mb-4 p-2 w-fit rounded-lg bg-slate-800/50 text-slate-400 group-hover:text-blue-400 group-hover:bg-blue-500/10 transition-colors">
<val.icon className="w-5 h-5" />
</div>
<h4 className="text-white font-bold mb-2 text-sm uppercase tracking-wide group-hover:text-blue-200 transition-colors">{val.title}</h4>
<p className="text-slate-500 text-xs leading-relaxed group-hover:text-slate-400 transition-colors">{val.description}</p>
</motion.div>
))}
</div>
{/* SEO Semantic Content - Visible but subtle */}
<div className="mt-24 pt-8 border-t border-slate-900">
<div className="flex items-center justify-center gap-2 text-slate-700 text-xs font-mono uppercase tracking-widest mb-4">
<Cpu className="w-3 h-3" />
<span>System_Signature</span>
</div>
<p className="text-[10px] text-slate-700 text-center max-w-2xl mx-auto leading-relaxed font-mono">
Evandro Fonseca Junior :: Engenheiro Full-Stack <br />
Next.js / React / Angular / Node.js / Tauri / PostgreSQL / IoT <br />
Garuva, Santa Catarina, Brasil :: Lat: -26.0264, Long: -48.8544
<div className="ds-card">
<div className="ds-kicker">System Signature</div>
<h3 className="ds-card-title">Base técnica e postura de entrega</h3>
<p className="ds-copy">
Evandro Fonseca Junior. Engenheiro Full-Stack com foco em Next.js,
React, Node.js, Tauri, integrações e arquitetura pragmática.
</p>
<div className="ds-grid-2 mt-sp-6">
{VALUES_DATA.map((value) => (
<div
key={value.title}
className="border-t border-[var(--border)] pt-sp-4"
>
<div className="text-[11px] font-medium uppercase tracking-[0.08em]">
{value.title}
</div>
<p className="ds-copy !mt-sp-2">{value.description}</p>
</div>
))}
</div>
</div>
</div>
</section>
</Section>
);
}
}
+98 -198
View File
@@ -1,223 +1,123 @@
"use client";
"use client";
import React, { useState } from "react";
import { motion } from "framer-motion";
import { Send, User, Mail, MessageSquare, Sparkles } from "lucide-react";
import { ShimmerButton as MagicShimmerButton } from "@/components/magicui/shimmer-button";
import Squares from "@/components/Squares";
import type React from "react";
import { useState } from "react";
import { Section } from "./Section";
// --- CONFIGURAÇÃO DE ENGENHARIA ---
// Substitua pelo seu número real: CódigoPaís + DDD + Número (sem espaços ou símbolos)
const WHATSAPP_NUMBER = "5547992742365";
interface FormData {
name: string;
email: string;
subject: string;
message: string;
}
/**
* Wrapper Component: Resolve o erro de tipagem TS2322 (IntrinsicAttributes)
* Estendemos a interface para aceitar propriedades de estilo customizadas.
*/
interface ExtendedShimmerProps extends React.ComponentProps<typeof MagicShimmerButton> {
shimmerColor?: string;
shimmerSize?: string;
type?: "button" | "submit" | "reset";
disabled?: boolean;
}
const CustomShimmerButton = ({
children,
className,
shimmerColor = "#3b82f6",
shimmerSize = "0.1em",
...props
}: ExtendedShimmerProps) => {
return (
<MagicShimmerButton
{...props}
className={className}
// @ts-ignore - Ignora restrição de tipos da lib original se necessário
shimmerColor={shimmerColor}
shimmerSize={shimmerSize}
>
{children}
</MagicShimmerButton>
);
};
export default function Contact() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [formData, setFormData] = useState<FormData>({
const [formData, setFormData] = useState({
name: "",
email: "",
subject: "full-stack",
message: "",
});
const handleSubmit = async (e: React.FormEvent) => {
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setIsSubmitting(true);
await new Promise((resolve) => setTimeout(resolve, 800));
// Definimos os emojis via Unicode Escape para máxima compatibilidade
const icons = {
rocket: "\u{1F680}",
person: "\u{1F464}",
email: "\u{1F4E7}",
tools: "\u{1F6E0}",
chat: "\u{1F4AC}"
};
const messageTemplate = `*NOVA SOLICITAÇÃO DE PROJETO*
*Origem:* Portfólio Profissional
*Prioridade:* Alta (Lead Qualificado)
---
*CLIENTE:* ${formData.name}
*EMAIL:* ${formData.email}
*ÁREA:* ${formData.subject.toUpperCase()}
*DESCRIÇÃO:*
"${formData.message}"
---
_Mensagem gerada via Evandro.dev | Garuva, SC_`;
const messageTemplate = `*NOVA SOLICITACAO DE PROJETO*\n\n*Origem:* Portfolio Profissional\n*Prioridade:* Alta\n\n---\n*CLIENTE:* ${formData.name}\n*EMAIL:* ${formData.email}\n*AREA:* ${formData.subject.toUpperCase()}\n\n*DESCRICAO:*\n"${formData.message}"\n---\n_Mensagem gerada via Evandro.dev | Garuva, SC_`;
const encodedMessage = encodeURIComponent(messageTemplate);
const whatsappUrl = `https://wa.me/${WHATSAPP_NUMBER}?text=${encodedMessage}`;
window.open(whatsappUrl, "_blank", "noopener,noreferrer");
setIsSubmitting(false);
};
const inputStyles =
"w-full bg-black/40 border border-white/5 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 focus:bg-black/60 outline-none transition-all duration-300 font-mono text-sm backdrop-blur-sm group-hover:border-white/10";
const labelStyles =
"text-[10px] font-bold text-slate-500 uppercase tracking-widest mb-2 flex items-center gap-2 transition-colors group-hover:text-slate-400";
return (
<section id="contact" className="w-full py-24 relative">
<div className="w-full max-w-4xl mx-auto px-6 relative z-10">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
className="relative bg-zinc-900/40 backdrop-blur-xl border border-white/10 rounded-3xl p-8 md:p-12 shadow-2xl overflow-hidden"
>
<div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-blue-500/0 via-blue-500/50 to-blue-500/0" />
<div className="mb-10 text-center space-y-2">
<h2 className="text-3xl md:text-4xl font-bold bg-clip-text text-transparent bg-gradient-to-b from-white to-white/60">
Vamos Construir o Futuro
</h2>
<p className="text-slate-400 text-sm md:text-base max-w-lg mx-auto">
Descreva seu desafio técnico. Retorno com uma arquitetura sólida em até 24h.
</p>
<Section
id="contact"
eyebrow="CONTACT_PROTOCOL.init"
title="Vamos discutir o próximo sistema"
description="Descreva o contexto, o gargalo ou o produto. O contato abre direto no WhatsApp com briefing estruturado."
dark
>
<div className="ds-grid-2">
<div className="ds-card">
<div className="ds-kicker">Response SLA</div>
<h3 className="ds-card-title">Retorno inicial em até 24h</h3>
<p className="ds-copy">
Ideal para discovery técnico, revisão de arquitetura, rescue de
projeto, otimização de performance ou construção de produto do
zero.
</p>
<div className="mt-sp-6 space-y-sp-4 text-[12px] leading-7 text-[rgba(255,255,255,.7)]">
<div>WhatsApp: +55 47 99274-2365</div>
<div>E-mail: evandrofonsecajunior@gmail.com</div>
<div>Base: Garuva, Santa Catarina · atuação remota</div>
</div>
</div>
<form onSubmit={handleSubmit} className="space-y-8 relative">
<div className="grid md:grid-cols-2 gap-8">
<div className="space-y-3 group">
<label className={labelStyles}>
<User className="w-3 h-3 text-blue-500" /> NOME COMPLETO
</label>
<input
required
type="text"
placeholder="Ex: Alan Turing"
className={inputStyles}
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
/>
</div>
<div className="space-y-3 group">
<label className={labelStyles}>
<Mail className="w-3 h-3 text-blue-500" /> E-MAIL CORPORATIVO
</label>
<input
required
type="email"
placeholder="[email protected]"
className={inputStyles}
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
/>
</div>
</div>
<div className="space-y-3 group">
<label className={labelStyles}>
<Sparkles className="w-3 h-3 text-blue-500" /> TIPO DE PROJETO
</label>
<div className="relative">
<select
className={`${inputStyles} appearance-none cursor-pointer`}
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 Sênior</option>
</select>
<div className="absolute right-4 top-1/2 -translate-y-1/2 pointer-events-none text-slate-500">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m6 9 6 6 6-6" /></svg>
</div>
</div>
</div>
<div className="space-y-3 group">
<label className={labelStyles}>
<MessageSquare className="w-3 h-3 text-blue-500" /> DETALHES DO PROJETO
</label>
<textarea
required
rows={5}
placeholder="Descreva os requisitos técnicos e objetivos do sistema..."
className={`${inputStyles} resize-none`}
value={formData.message}
onChange={(e) => setFormData({ ...formData, message: e.target.value })}
/>
</div>
<motion.div whileHover={{ scale: 1.01 }} whileTap={{ scale: 0.99 }} className="pt-6">
<CustomShimmerButton
type="submit"
className="w-full h-14 rounded-xl shadow-lg shadow-blue-500/20"
shimmerColor="#60a5fa"
shimmerSize="0.1em"
disabled={isSubmitting}
>
<div className="flex flex-row items-center justify-center gap-3 w-full h-full">
{isSubmitting ? (
<div className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
) : (
<>
<span className="text-white font-bold tracking-widest uppercase text-xs md:text-sm">
Iniciar Protocolo de Contato
</span>
<Send className="w-4 h-4 text-white" />
</>
)}
</div>
</CustomShimmerButton>
</motion.div>
<footer className="pt-2">
<p className="text-[10px] text-center text-slate-600 font-mono uppercase tracking-widest opacity-60">
Secured by End-to-End Encryption
</p>
</footer>
</form>
</motion.div>
<form onSubmit={handleSubmit} className="ds-card space-y-sp-5">
<div>
<label htmlFor="contact-name" className="ds-kicker block">
Nome completo
</label>
<input
id="contact-name"
required
type="text"
value={formData.name}
onChange={(e) =>
setFormData({ ...formData, name: e.target.value })
}
className="mt-sp-2 w-full border border-[rgba(255,255,255,.12)] bg-[rgba(255,255,255,.03)] px-sp-4 py-[14px] text-[12px] text-white outline-none transition-colors focus:border-slash"
/>
</div>
<div>
<label htmlFor="contact-email" className="ds-kicker block">
E-mail corporativo
</label>
<input
id="contact-email"
required
type="email"
value={formData.email}
onChange={(e) =>
setFormData({ ...formData, email: e.target.value })
}
className="mt-sp-2 w-full border border-[rgba(255,255,255,.12)] bg-[rgba(255,255,255,.03)] px-sp-4 py-[14px] text-[12px] text-white outline-none transition-colors focus:border-slash"
/>
</div>
<div>
<label htmlFor="contact-subject" className="ds-kicker block">
Tipo de projeto
</label>
<select
id="contact-subject"
value={formData.subject}
onChange={(e) =>
setFormData({ ...formData, subject: e.target.value })
}
className="mt-sp-2 w-full border border-[rgba(255,255,255,.12)] bg-[rgba(255,255,255,.03)] px-sp-4 py-[14px] text-[12px] text-white outline-none transition-colors focus:border-slash"
>
<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 Sênior</option>
</select>
</div>
<div>
<label htmlFor="contact-message" className="ds-kicker block">
Detalhes do projeto
</label>
<textarea
id="contact-message"
required
rows={6}
value={formData.message}
onChange={(e) =>
setFormData({ ...formData, message: e.target.value })
}
className="mt-sp-2 w-full resize-none border border-[rgba(255,255,255,.12)] bg-[rgba(255,255,255,.03)] px-sp-4 py-[14px] text-[12px] text-white outline-none transition-colors focus:border-slash"
/>
</div>
<button type="submit" className="ds-link ds-link-accent border-0">
Iniciar protocolo de contato
</button>
</form>
</div>
</section>
</Section>
);
}
}
+44 -170
View File
@@ -1,194 +1,68 @@
"use client";
import React, { useMemo } from "react";
import { motion } from "framer-motion";
import {
ChevronRight,
Terminal,
Cpu,
Network,
Code2
} from "lucide-react";
import { useMemo } from "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 }
};
import { Section } from "./Section";
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)
const yearA = parseInt(a.period.match(/\d{4}/)?.[0] || "0", 10);
const yearB = parseInt(b.period.match(/\d{4}/)?.[0] || "0", 10);
return yearB - yearA;
});
}, [data]);
if (loading) return <ExperienceSkeleton />;
if (loading) return <div className="ds-loading">Loading system logs</div>;
if (error || !data)
return <div className="ds-error">Error loading system logs</div>;
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>
<Section
id="experience"
eyebrow="SYSTEM_LOGS.history"
title="Trajetória focada em produto, legado e crescimento sustentável"
description="Experiências recentes onde combinei entrega rápida com responsabilidade arquitetural."
>
<div className="space-y-sp-6">
{sortedExperiences.map((exp) => (
<article key={exp.id} className="ds-card">
<div className="ds-grid-2">
<div>
<div className="ds-kicker">{exp.period}</div>
<h3 className="ds-card-title">{exp.title}</h3>
<div className="mt-sp-3 text-[11px] uppercase tracking-[0.1em] text-meta">
{exp.company} · {exp.type}
</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}
<div className="flex flex-wrap items-start justify-start gap-sp-3 lg:justify-end">
<span className="ds-tag">{exp.location}</span>
<span className="ds-tag">
{exp.technologies.length} tecnologias
</span>
))}
</div>
</div>
</div>
</motion.div>
<p className="ds-copy">{exp.description}</p>
<ul className="ds-list">
{exp.achievements?.map((achievement) => (
<li key={achievement}>{achievement}</li>
))}
</ul>
<ul className="ds-tags mt-sp-6">
{exp.technologies?.map((tech) => (
<li key={tech} className="ds-tag">
{tech}
</li>
))}
</ul>
</article>
))}
</div>
{/* Spacer para Timeline Desktop */}
<div className="hidden lg:block flex-1" />
</div>
</Section>
);
}
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>
);
}
+106 -150
View File
@@ -1,171 +1,127 @@
"use client";
"use client";
import React, { useState, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { MenuIcon, XIcon } from "lucide-react";
import Image from "next/image";
import { cn } from "@/lib/utils";
import { useState } from "react";
import { Logo } from "./Logo";
// Definição de Tipos para consistência de dados
interface NavItem {
href: string;
label: string;
}
const navItems: NavItem[] = [
const navItems = [
{ href: "#home", label: "Home" },
{ href: "#about", label: "Sobre" },
{ href: "#skills", label: "Habilidades" },
{ href: "#experience", label: "Experiência" },
{ href: "#experience", label: "Experiencia" },
{ href: "#projects", label: "Projetos" },
{ href: "#modalities", label: "Contratacao" },
{ href: "#contact", label: "Contato" },
];
export function Header() {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [scrolled, setScrolled] = useState(false);
// Hook de detecção de scroll para mudança de estado do Header
useEffect(() => {
const handleScroll = () => {
setScrolled(window.scrollY > 20);
};
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
return (
<>
<motion.header
initial={{ y: -100 }}
animate={{ y: 0 }}
className={cn(
"fixed top-0 left-0 right-0 z-[100] transition-all duration-500 ease-in-out px-6 py-4",
scrolled
? "bg-black/40 backdrop-blur-xl border-b border-white/5 py-3"
: "bg-transparent py-6"
)}
>
<nav className="max-w-7xl mx-auto flex items-center justify-between">
{/* Logo com Identidade Visual de Engenheiro */}
<motion.a
href="#home"
onClick={(e) => {
e.preventDefault();
const element = document.querySelector("#home");
if (element) {
element.scrollIntoView({ behavior: "smooth", block: "start" });
}
}}
whileHover={{ scale: 1.05 }}
className="flex items-center gap-2 cursor-pointer"
<header
className="fixed left-0 right-0 top-0 z-50 border-b backdrop-blur-xl"
style={{
borderColor: "rgba(255,255,255,.1)",
background: "rgba(10,10,10,.96)",
}}
>
<div className="mx-auto flex h-[52px] w-full max-w-[1240px] items-center justify-between px-6 lg:px-16">
<a href="#home" className="flex items-center gap-3 no-underline">
<Logo variant="mark" size="sm" theme="dark" />
<span
className="font-display text-sm font-extrabold tracking-tight"
style={{ color: "#FFFFFF" }}
>
<div className="w-8 h-8 flex items-center justify-center">
<Image
src="/edev_logo.svg"
alt="Evandro.dev Logo"
width={32}
height={32}
className="w-8 h-8"
priority
/>
</div>
<span className="text-xl font-black tracking-tighter text-white">
EVANDRO<span className="text-blue-500">.DEV</span>
</span>
</motion.a>
evandro<span style={{ color: "#E8FF00" }}>.dev.br</span>
</span>
</a>
{/* Desktop Navigation - Focada em Espaçamento Ótimo */}
<div className="hidden md:flex items-center gap-1 bg-white/5 p-1 rounded-full border border-white/10 backdrop-blur-md">
<nav className="hidden items-center lg:flex">
<ul className="flex list-none items-center gap-2">
{navItems.map((item) => (
<a
key={item.href}
href={item.href}
onClick={(e) => {
e.preventDefault();
const element = document.querySelector(item.href);
if (element) {
element.scrollIntoView({ behavior: "smooth", block: "start" });
}
}}
className="px-5 py-2 text-xs font-bold uppercase tracking-widest text-slate-300 hover:text-white hover:bg-white/10 rounded-full transition-all duration-300 cursor-pointer"
>
{item.label}
</a>
<li key={item.href}>
<a
href={item.href}
className="rounded-sm px-3 py-2 text-[10px] uppercase tracking-[0.14em] no-underline transition-all duration-200 hover:bg-white/10"
style={{ color: "rgba(255,255,255,.86)" }}
>
{item.label}
</a>
</li>
))}
</div>
{/* Botão de Ação CTA (Call to Action) */}
<div className="hidden md:block">
<a
href="/Profile.pdf"
download="Evandro_Fonseca_Junior_CV.pdf"
className="px-6 py-2 bg-white text-black text-xs font-bold uppercase tracking-widest rounded-full hover:bg-blue-500 hover:text-white transition-all duration-500 inline-block"
>
Download CV
</a>
</div>
{/* Mobile Toggle */}
<button
onClick={() => setIsMenuOpen(!isMenuOpen)}
className="md:hidden p-2 text-white bg-white/5 rounded-lg border border-white/10"
>
{isMenuOpen ? <XIcon size={20} /> : <MenuIcon size={20} />}
</button>
</ul>
</nav>
</motion.header>
{/* Mobile Menu Overlay com Framer Motion para fluidez */}
<AnimatePresence>
{isMenuOpen && (
<motion.div
initial={{ opacity: 0, x: "100%" }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: "100%" }}
transition={{ type: "spring", damping: 25, stiffness: 200 }}
className="fixed inset-0 z-[110] bg-black/95 backdrop-blur-2xl md:hidden flex flex-col items-center justify-center gap-8"
<div className="flex items-center gap-4">
<span
className="hidden rounded-full border px-[10px] py-1 text-[9px] uppercase tracking-[0.12em] lg:inline-block"
style={{
borderColor: "rgba(255,255,255,.15)",
color: "rgba(255,255,255,.35)",
}}
>
<button
onClick={() => setIsMenuOpen(false)}
className="absolute top-8 right-8 p-4 text-white/50 hover:text-white"
>
<XIcon size={32} />
</button>
{navItems.map((item, i) => (
<motion.a
key={item.href}
href={item.href}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: i * 0.1 }}
onClick={(e) => {
e.preventDefault();
setIsMenuOpen(false);
const element = document.querySelector(item.href);
if (element) {
element.scrollIntoView({ behavior: "smooth", block: "start" });
}
}}
className="text-3xl font-black text-white uppercase tracking-tighter hover:text-blue-500 transition-colors cursor-pointer"
>
{item.label}
</motion.a>
v1.0 / 2026
</span>
<a
href="/Profile.pdf"
download="Evandro_Fonseca_Junior_CV.pdf"
className="hidden rounded-sm px-4 py-2 text-[10px] font-semibold uppercase tracking-[0.1em] no-underline transition-all duration-200 hover:shadow-[0_0_0_1px_rgba(232,255,0,.8),0_8px_24px_rgba(232,255,0,.35)] lg:inline-flex"
style={{ background: "#E8FF00", color: "#0A0A0A" }}
>
Download CV
</a>
<button
type="button"
className="flex h-8 w-8 flex-col items-center justify-center gap-1.5 border-none bg-transparent lg:hidden"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
aria-label="Menu"
>
<span
className={`h-0.5 w-5 bg-white transition-transform duration-200 ${mobileMenuOpen ? "translate-y-2 rotate-45" : ""}`}
/>
<span
className={`h-0.5 w-5 bg-white transition-opacity duration-200 ${mobileMenuOpen ? "opacity-0" : ""}`}
/>
<span
className={`h-0.5 w-5 bg-white transition-transform duration-200 ${mobileMenuOpen ? "-translate-y-2 -rotate-45" : ""}`}
/>
</button>
</div>
</div>
<div
className={`absolute left-0 right-0 top-[52px] overflow-hidden border-b backdrop-blur-xl transition-all duration-300 lg:hidden ${mobileMenuOpen ? "max-h-[420px] opacity-100" : "max-h-0 opacity-0"}`}
style={{
borderColor: "rgba(255,255,255,.1)",
background: "rgba(10,10,10,.98)",
}}
>
<nav className="px-6 py-4">
<ul className="flex list-none flex-col gap-2">
{navItems.map((item) => (
<li key={item.href}>
<a
href={item.href}
className="block rounded-sm px-3 py-3 text-[11px] uppercase tracking-[0.14em] no-underline transition-all duration-200 hover:bg-white/10"
style={{ color: "rgba(255,255,255,.9)" }}
onClick={() => setMobileMenuOpen(false)}
>
{item.label}
</a>
</li>
))}
<motion.a
href="/Profile.pdf"
download="Evandro_Fonseca_Junior_CV.pdf"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: navItems.length * 0.1 }}
className="mt-8 px-8 py-4 bg-white text-black text-xl font-black uppercase tracking-tighter rounded-full hover:bg-blue-500 hover:text-white transition-all duration-300"
>
Download CV
</motion.a>
</motion.div>
)}
</AnimatePresence>
</>
<li>
<a
href="/Profile.pdf"
download="Evandro_Fonseca_Junior_CV.pdf"
className="mt-2 inline-flex rounded-sm px-4 py-3 text-[10px] font-semibold uppercase tracking-[0.1em] no-underline"
style={{ background: "#E8FF00", color: "#0A0A0A" }}
>
Download CV
</a>
</li>
</ul>
</nav>
</div>
</header>
);
}
}
+199 -124
View File
@@ -1,145 +1,220 @@
"use client";
"use client";
import { motion } from "motion/react";
import Image from "next/image";
import { AuroraText } from "@/components/magicui/aurora-text";
import { AnimatedGradientText } from "@/components/magicui/animated-gradient-text";
import { Github, Linkedin, MessageCircle, Mail, MapPin, Terminal } from "lucide-react";
import TextType from "@/components/TextType";
import { Logo } from "./Logo";
const SOCIAL_LINKS = [
{
href: "https://github.com/evandrodevbr",
label: "GitHub",
icon: (
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
),
},
{
href: "https://www.linkedin.com/in/evandro-fonseca-junior-0969aa1b0/",
label: "LinkedIn",
icon: (
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" />
),
},
{
href: "mailto:[email protected]",
label: "Email",
icon: (
<>
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" />
<polyline points="22,6 12,13 2,6" />
</>
),
stroke: true,
},
];
const STATS = [
{ value: "4", accent: "px", label: "Base unit" },
{ value: "9", accent: "x", label: "Type scale" },
{ value: "4", accent: "+", label: "Core tokens" },
];
export default function Hero() {
const getGreetingMessage = () => {
const hour = new Date().getHours();
if (hour >= 8 && hour < 12) return "Olá! Vi seu portfólio. Podemos conversar?";
if (hour >= 13 && hour < 18) return "Boa tarde! Vi seu portfólio. Podemos conversar?";
return "Olá! Vi seu portfólio. Podemos conversar quando estiver disponível?";
};
const generateWhatsAppUrl = () => {
const message = encodeURIComponent(getGreetingMessage());
return `https://wa.me/5547992742365?text=${message}`;
};
const hour = new Date().getHours();
const greeting =
hour >= 8 && hour < 12
? "Ola! Vi seu portfolio. Podemos conversar?"
: hour >= 13 && hour < 18
? "Boa tarde! Vi seu portfolio. Podemos conversar?"
: "Ola! Vi seu portfolio. Podemos conversar quando estiver disponivel?";
return (
<section id="home" className="relative min-h-screen flex items-center justify-center overflow-hidden pt-28 md:pt-20">
{/* 1. Background Engineering: Grid Pattern & Radial Shading */}
<div className="absolute inset-0 z-0 pointer-events-none">
<div className="absolute inset-0 bg-radial-at-t from-blue-500/10 via-transparent to-transparent" />
</div>
<section
id="home"
className="relative overflow-hidden px-6 py-24 lg:px-16"
style={{ background: "#0A0A0A", color: "#FFFFFF" }}
>
<div className="pointer-events-none absolute inset-0 bg-grid-light opacity-70" />
<div className="pointer-events-none absolute inset-x-0 top-0 h-40 bg-[radial-gradient(circle_at_top,rgba(232,255,0,0.18),transparent_60%)]" />
<div className="relative z-10 w-full max-w-6xl mx-auto px-6 grid grid-cols-1 lg:grid-cols-12 gap-12 items-center">
<div className="relative z-10 mx-auto grid min-h-[calc(100vh-52px)] w-full max-w-[1240px] grid-cols-1 items-center gap-16 lg:grid-cols-2">
<div>
<div className="mb-8 inline-flex items-center gap-2">
<span
className="h-1.5 w-1.5 animate-pulse rounded-full"
style={{ background: "#E8FF00" }}
/>
<span
className="text-[9px] uppercase tracking-[0.2em]"
style={{ color: "rgba(255,255,255,.38)" }}
>
Systems engineer available
</span>
</div>
{/* Lado Esquerdo: Conteúdo Textual (80/20 do Impacto) */}
<div className="lg:col-span-7 text-left">
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.6 }}
<h1
className="mb-6 font-display text-5xl font-extrabold leading-[0.92] tracking-[-0.04em] md:text-6xl lg:text-7xl xl:text-[5.4rem]"
style={{ color: "#FFFFFF" }}
>
<AnimatedGradientText className="mb-6 inline-flex items-center gap-2 px-4 py-2 rounded-full border border-blue-500/20 bg-blue-500/5 backdrop-blur-md">
<span className="relative flex h-2 w-2">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-cyan-400 opacity-75"></span>
<span className="relative inline-flex rounded-full h-2 w-2 bg-cyan-500"></span>
</span>
<span className="text-sm font-medium text-cyan-300 tracking-wide uppercase">
Systems Engineer available
</span>
</AnimatedGradientText>
Evandro
<br />
<span style={{ color: "#E8FF00" }}>.dev</span>
<br />
.br
</h1>
<h1 className="text-5xl md:text-7xl font-black text-white mb-4 tracking-tight leading-none">
Evandro <br />
<AuroraText className="italic">Fonseca Junior</AuroraText>
</h1>
<p
className="mb-10 max-w-[460px] text-[13px] leading-7"
style={{ color: "rgba(255,255,255,.72)" }}
>
Engenheiro de Software especializado em aplicacoes de alta
performance, interfaces de produto e sistemas criticos com Next.js,
React, Tauri e arquitetura escalavel.
</p>
<div className="h-10 flex items-center gap-2 mb-6">
<Terminal className="w-5 h-5 text-blue-400" />
<TextType
text={["Engenheiro de Software", "Next.js & Tauri Specialist", "High Performance Systems Architect"]}
className="text-2xl font-mono text-slate-400"
typingSpeed={60}
/>
</div>
<div className="mb-8 flex flex-wrap items-center gap-4">
<a
href={`https://wa.me/5547992742365?text=${encodeURIComponent(greeting)}`}
className="inline-flex h-11 items-center rounded-sm px-6 text-[11px] font-semibold uppercase tracking-[0.08em] no-underline transition-all duration-200 hover:-translate-y-0.5 hover:shadow-[0_0_0_1px_rgba(232,255,0,.8),0_8px_24px_rgba(232,255,0,.35)]"
style={{ background: "#E8FF00", color: "#0A0A0A" }}
>
Iniciar projeto
</a>
<p className="text-lg text-slate-400 max-w-xl mb-10 leading-relaxed">
Engenheiro de Software especializado em aplicações de alta performance e sistemas críticos. Desenvolvimento com <span className="text-white font-medium">Next.js 15, React 19, Tauri</span> e arquitetura escalável. Transformo requisitos complexos em software robusto.
</p>
<div className="flex flex-wrap gap-4 items-center">
<motion.a
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
href={generateWhatsAppUrl()}
className="px-8 py-4 bg-blue-600 hover:bg-blue-500 text-white rounded-xl font-bold transition-all shadow-lg shadow-blue-600/20 flex items-center gap-2"
>
<MessageCircle className="w-5 h-5" />
Iniciar Projeto
</motion.a>
<div className="flex gap-4 px-4">
{[
{ icon: Github, link: "https://github.com/evandrodevbr", rel: "me" },
{ icon: Linkedin, link: "https://www.linkedin.com/in/evandro-fonseca-junior-0969aa1b0/", rel: "me" },
{ icon: Mail, link: "mailto:[email protected]" }
].map((social, i) => (
<a
key={i}
href={social.link}
rel={social.rel ? `${social.rel} noopener noreferrer` : "noopener noreferrer"}
target={social.link.startsWith("mailto:") ? undefined : "_blank"}
className="p-3 rounded-lg border border-slate-800 text-slate-400 hover:text-white hover:border-slate-600 transition-all"
<div className="flex items-center gap-2">
{SOCIAL_LINKS.map((link) => (
<a
key={link.label}
href={link.href}
target={link.href.startsWith("http") ? "_blank" : undefined}
rel={
link.href.startsWith("http")
? "noopener noreferrer"
: undefined
}
className="inline-flex h-11 w-11 items-center justify-center rounded-sm border transition-all duration-200 hover:bg-white/10"
style={{
borderColor: "rgba(255,255,255,.18)",
color: "rgba(255,255,255,.84)",
}}
aria-label={link.label}
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill={link.stroke ? "none" : "currentColor"}
stroke={link.stroke ? "currentColor" : "none"}
strokeWidth={link.stroke ? "2" : undefined}
role="img"
aria-label={link.label}
>
<social.icon className="w-5 h-5" />
</a>
))}
</div>
{link.icon}
</svg>
</a>
))}
</div>
</div>
<div className="mt-8 flex items-center gap-2 text-slate-500 text-sm">
<MapPin className="w-4 h-4" />
<span>Garuva, SC | Remote Worldwide</span>
</div>
</motion.div>
</div>
{/* Lado Direito: Visual (A "Âncora" do Design) */}
<div className="lg:col-span-5 relative flex justify-center">
<motion.div
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.8, ease: "easeOut" }}
className="relative w-72 h-72 md:w-96 md:h-96"
<div
className="text-[10px] uppercase tracking-[0.14em]"
style={{ color: "rgba(255,255,255,.38)" }}
>
{/* Elementos Orbitais (Vibe Engenharia) */}
<div className="absolute inset-0 rounded-full border border-dashed border-blue-500/30 animate-[spin_20s_linear_infinite]" />
<div className="absolute inset-10 rounded-full border border-blue-500/10 animate-[spin_15s_linear_infinite_reverse]" />
Garuva, SC | Remote worldwide
</div>
{/* Foto de Perfil com Masking de Engenharia */}
<div className="relative z-10 w-full h-full rounded-[2.5rem] overflow-hidden border-2 border-slate-800 shadow-2xl">
<div className="absolute inset-0 bg-blue-500/10 z-10 hover:opacity-0 transition-opacity duration-500" />
<Image
src="/edev.jpeg"
alt="Evandro Fonseca Junior"
fill
className="object-cover grayscale hover:grayscale-0 transition-all duration-700 scale-105 hover:scale-100"
priority
fetchPriority="high"
/>
</div>
{/* Floating Stats Badge */}
<div className="absolute -bottom-6 -right-6 bg-slate-900 border border-slate-800 p-4 rounded-2xl shadow-2xl z-20 backdrop-blur-xl">
<p className="text-xs text-slate-500 uppercase font-bold tracking-tighter">Exp. Level</p>
<p className="text-xl font-mono text-blue-400">Mid-Level Engineer</p>
</div>
</motion.div>
<div
className="mt-10 grid max-w-[440px] grid-cols-3 gap-px border max-sm:grid-cols-1"
style={{
borderColor: "rgba(255,255,255,.12)",
background: "rgba(255,255,255,.08)",
}}
>
{STATS.map((item) => (
<div
key={item.label}
className="p-5"
style={{ background: "rgba(255,255,255,.03)" }}
>
<div
className="font-display text-2xl font-extrabold tracking-[-0.03em]"
style={{ color: "#FFFFFF" }}
>
{item.value}
<span style={{ color: "#E8FF00" }}>{item.accent}</span>
</div>
<div
className="mt-1 text-[9px] uppercase tracking-[0.12em]"
style={{ color: "rgba(255,255,255,.35)" }}
>
{item.label}
</div>
</div>
))}
</div>
</div>
</div>
{/* Scroll Indicator (Refinado) */}
<div className="absolute bottom-8 left-1/2 -translate-x-1/2 flex flex-col items-center gap-2">
<span className="text-[10px] text-slate-600 uppercase tracking-[0.2em]">Scroll</span>
<div className="w-[1px] h-12 bg-gradient-to-b from-blue-500 to-transparent" />
<div className="flex flex-col items-end gap-6 max-lg:items-start">
<div
className="relative flex aspect-square w-full max-w-[440px] items-center justify-center border max-lg:max-w-[320px]"
style={{
borderColor: "rgba(255,255,255,.12)",
background: "rgba(255,255,255,.03)",
}}
>
<span
className="absolute left-6 top-5 text-[9px] tracking-[0.14em]"
style={{ color: "rgba(255,255,255,.32)" }}
>
D / SLASH MARK
</span>
<Logo variant="mark" size="xl" theme="dark" />
<span
className="absolute bottom-5 right-6 text-[9px] tracking-[0.14em]"
style={{ color: "#E8FF00", opacity: 0.78 }}
>
CONCEITO ESCOLHIDO
</span>
</div>
<div
className="flex items-center gap-3 text-[9px] tracking-[0.12em]"
style={{ color: "rgba(255,255,255,.32)" }}
>
<span
className="h-px w-6"
style={{ background: "rgba(255,255,255,.18)" }}
/>
D / Slash mark · conceito final
</div>
<div
className="flex items-center gap-3 text-[9px] tracking-[0.12em]"
style={{ color: "rgba(255,255,255,.32)" }}
>
<span
className="h-px w-6"
style={{ background: "rgba(255,255,255,.18)" }}
/>
Escala: 280x200 · viewbox
</div>
</div>
</div>
</section>
);
}
}
+73 -194
View File
@@ -1,207 +1,86 @@
"use client";
"use client";
import React, { useMemo } from "react";
import { motion } from "motion/react";
import { BentoGrid, BentoGridItem } from "@/components/magicui/bento-grid";
import {
ExternalLink,
Github,
Star,
Zap,
Globe,
Cpu,
Code2,
Terminal,
} from "lucide-react";
import { useMemo } from "react";
import { usePortfolioData } from "@/lib/usePortfolioData";
// --- 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 },
};
// Funções auxiliares para Schema
function getApplicationCategory(category: string): string {
const map: Record<string, string> = {
"full-stack": "WebApplication",
"iot": "DesktopApplication",
"api": "WebApplication",
"web": "WebApplication",
"desktop": "DesktopApplication"
};
return map[category.toLowerCase()] || "WebApplication";
}
function getOperatingSystem(category: string): string {
if (category.toLowerCase() === "desktop") return "Windows, Linux";
return "Web Browser";
}
import { Section } from "./Section";
export default function Projects() {
const { data, loading, error } = usePortfolioData();
const projects = useMemo(() =>
data?.projects.filter((p) => p.featured) || [],
[data]);
const projectsJsonLd = useMemo(() => {
if (!projects || projects.length === 0) return null;
return {
"@context": "https://schema.org",
"@type": "ItemList",
"itemListElement": projects.map((project, index) => ({
"@type": "ListItem",
"position": index + 1,
"item": {
"@type": "SoftwareApplication",
"name": project.title,
"description": project.description,
"applicationCategory": getApplicationCategory(project.category),
"operatingSystem": getOperatingSystem(project.category),
"url": project.demoUrl || project.githubUrl || `https://evandro.dev.br/#projects`,
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "BRL"
},
"softwareVersion": "1.0.0",
"author": {
"@type": "Person",
"name": "Evandro Fonseca Junior",
"url": "https://evandro.dev.br"
}
}
}))
};
}, [projects]);
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-32 px-6 relative">
{/* 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: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="text-left mb-20"
>
<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>
</motion.div>
<BentoGrid className="grid-cols-1 md:grid-cols-3 gap-4">
{projects.map((project, index) => {
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}
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]`}
>
{/* 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-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.githubUrl && <ProjectLink href={project.githubUrl} icon={Github} />}
{project.demoUrl && <ProjectLink href={project.demoUrl} icon={ExternalLink} highlight />}
</div>
</div>
{/* 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>
<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>
{/* 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>
{/* 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>
{/* JSON-LD Schema for Projects */}
{projectsJsonLd && (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(projectsJsonLd) }}
/>
)}
</div>
</section>
const projects = useMemo(
() => data?.projects.filter((p) => p.featured) || [],
[data],
);
}
// --- Subcomponentes Auxiliares ---
if (loading)
return <div className="ds-loading">Loading featured projects</div>;
if (error)
return <div className="ds-error">Error loading featured projects</div>;
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'
}`}
<Section
id="projects"
eyebrow="PRODUCAO_INTELECTUAL"
title="Projetos com recorte técnico claro e impacto observável"
description="Seleção de produtos e ferramentas onde performance, confiabilidade e clareza de arquitetura foram requisitos reais."
dark
>
<Icon className="w-4 h-4" />
</a>
<div className="ds-grid-2">
{projects.map((project) => (
<article key={project.id} className="ds-card">
<div className="flex items-start justify-between gap-sp-6">
<div>
<div className="ds-kicker">{project.category}</div>
<h3 className="ds-card-title">{project.title}</h3>
<p className="mt-sp-2 text-[11px] uppercase tracking-[0.1em] text-[rgba(255,255,255,.36)]">
{project.subtitle}
</p>
</div>
<span className="ds-tag">{project.status}</span>
</div>
<p className="ds-copy">{project.description}</p>
<div className="mt-sp-6 flex flex-wrap gap-sp-3">
{project.githubUrl && (
<a
href={project.githubUrl}
target="_blank"
rel="noreferrer"
className="ds-link ds-link-muted"
>
GitHub
</a>
)}
{project.demoUrl && (
<a
href={project.demoUrl}
target="_blank"
rel="noreferrer"
className="ds-link ds-link-accent"
>
Live demo
</a>
)}
</div>
<ul className="ds-tags mt-sp-6">
{project.technologies.slice(0, 6).map((tech: string) => (
<li key={tech} className="ds-tag">
{tech}
</li>
))}
</ul>
<div className="mt-sp-6 flex flex-wrap gap-sp-4">
<span className="ds-metric">
Complexity {project.metrics?.complexity || 3}/5
</span>
{project.metrics?.teamSize && (
<span className="ds-metric">
Team {project.metrics.teamSize}
</span>
)}
</div>
</article>
))}
</div>
</Section>
);
}
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>
);
}
+29 -121
View File
@@ -1,134 +1,42 @@
"use client";
import React from "react";
import { motion } from "framer-motion";
import {
Code, Database, Cloud, Cpu, Smartphone,
Zap, GitBranch, TestTube, Terminal, Layers
} from "lucide-react";
import { usePortfolioData } from "@/lib/usePortfolioData";
const ICON_MAP: Record<string, React.ElementType> = {
Code, Database, Cloud, Cpu, Smartphone, Zap, GitBranch, TestTube, Terminal, Layers
};
const getCategoryStyles = (color: string) => {
const base = color.replace("text-", "");
return {
icon: color,
bg: `bg-${base}/10`,
border: `border-slate-800/50`,
};
};
import { Section } from "./Section";
export default function Skills() {
const { data, loading, error } = usePortfolioData();
if (loading) return <LoadingState />;
if (error || !data) return <ErrorState />;
if (loading) return <div className="ds-loading">Loading skill registry</div>;
if (error || !data) {
return <div className="ds-error">Error loading skill registry</div>;
}
return (
<section id="skills" className="relative py-32 px-6">
{/* Background Decoration */}
<div className="absolute top-0 left-0 w-full h-full pointer-events-none">
<div className="absolute top-[40%] left-[50%] -translate-x-1/2 w-[800px] h-[800px] bg-blue-900/5 rounded-full blur-[100px]" />
</div>
<div className="max-w-7xl mx-auto relative z-10">
{/* Header Seção */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-100px" }}
className="flex flex-col md:flex-row justify-between items-end mb-20 gap-8"
>
<div className="text-left">
<div 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-4">
<Layers className="w-3 h-3" />
<span>MODULE_REGISTRY.lib</span>
<Section
id="skills"
eyebrow="MODULE_REGISTRY.lib"
title="Stack modular para produto, operação e escala"
description="Ferramentas escolhidas por previsibilidade operacional, velocidade de entrega e facilidade de manutenção."
dark
>
<div className="ds-grid-2">
{data.skills.technical.map((category, index) => (
<article key={category.category} className="ds-card">
<div className="flex items-center justify-between gap-sp-4">
<div className="ds-kicker">ID_0{index + 1}X</div>
<div className="ds-metric">{category.skills.length} items</div>
</div>
<h3 className="text-4xl md:text-6xl font-black text-white tracking-tighter">
Arsenal <span className="text-transparent bg-clip-text bg-gradient-to-r from-blue-500 to-indigo-500">Tecnológico</span>
</h3>
</div>
<p className="text-slate-400 max-w-md text-sm md:text-base font-light leading-relaxed border-l-2 border-slate-800 pl-6">
Sistemas resilientes construídos com precisão matemática e rigor arquitetural.
</p>
</motion.div>
{/* Grid de Habilidades */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
{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: 50 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.2 }}
transition={{
duration: 0.5,
delay: index * 0.05,
ease: "easeOut"
}}
className="h-full"
>
<div
className="group relative h-full p-8 flex flex-col bg-zinc-900/40 border border-white/5 rounded-2xl backdrop-blur-sm hover:border-blue-500/20 hover:bg-zinc-900/60 transition-all duration-300 overflow-hidden"
>
{/* Scanning Line Effect */}
<div className="absolute top-0 left-0 w-full h-[2px] bg-gradient-to-r from-transparent via-blue-500/50 to-transparent -translate-x-full group-hover:animate-scan" />
<div className="flex items-start justify-between mb-8">
<div className={`p-4 rounded-xl bg-slate-800/50 border border-white/5 group-hover:bg-blue-500/10 group-hover:border-blue-500/20 transition-all duration-300`}>
<Icon className={`w-8 h-8 text-slate-400 group-hover:text-blue-400 transition-colors`} />
</div>
<span className="text-[10px] font-mono text-slate-600 uppercase tracking-widest font-bold group-hover:text-blue-500/50 transition-colors">
ID_0{index}X
</span>
</div>
<h3 className="text-xl font-bold text-white mb-6 group-hover:text-blue-200 transition-colors">
{category.category}
</h3>
<div className="flex flex-wrap gap-2 mt-auto">
{category.skills.map((skill, sIdx) => (
<span
key={sIdx}
className="px-2.5 py-1 text-[10px] font-mono bg-black/40 text-slate-400 rounded border border-white/5 group-hover:border-blue-500/20 group-hover:text-blue-300 transition-all duration-300 cursor-default"
>
{skill}
</span>
))}
</div>
</div>
</motion.div>
);
})}
</div>
<h3 className="ds-card-title">{category.category}</h3>
<ul className="ds-tags mt-sp-6">
{category.skills.map((skill) => (
<li key={skill} className="ds-tag">
{skill}
</li>
))}
</ul>
</article>
))}
</div>
</section>
</Section>
);
}
// --- Componentes de Suporte (Manter no mesmo arquivo para contexto ou mover para /components) ---
function LoadingState() {
return (
<div className="py-32 flex flex-col items-center justify-center space-y-4 bg-[#030712]">
<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 uppercase tracking-tighter">Initializing Systems...</span>
</div>
);
}
function ErrorState() {
return (
<div className="py-32 text-center text-red-400 font-mono bg-[#030712]">
[FATAL_ERROR]: Failed to synchronize portfolio data.
</div>
);
}
+67 -229
View File
@@ -1,272 +1,110 @@
"use client";
import React from "react";
import { motion } from "framer-motion";
import {
Briefcase,
Clock,
HardHat,
Lightbulb,
Building2,
Users,
User,
Star,
CheckCircle2,
Terminal,
ShieldCheck,
Zap,
} from "lucide-react";
import { Section } from "./Section";
// --- 1. DATA ---
const WorkModalitiesData = [
{
title: "Intervenção Ad-hoc",
value: "R$ 35 - 70/hora",
badge: "AGILIDADE",
badge: "Agilidade",
idealFor:
"Resolução de gargalos técnicos específicos e consultoria de arquitetura imediata.",
description:
"Engenharia sob demanda com foco em troubleshooting e otimização de performance. Rastreabilidade total via timesheet detalhado.",
"Engenharia sob demanda com foco em troubleshooting, performance e decisões rápidas com rastreabilidade.",
benefits: [
"Diagnóstico Sênior Imediato",
"Sem Compromisso de Longo Prazo",
"Documentação de Alterações",
"Diagnóstico sênior imediato",
"Sem compromisso de longo prazo",
"Documentação de alterações",
],
icon: Zap,
color: "blue",
featured: false,
},
{
title: "Alocação de Sprint",
value: "Eficiência Financeira",
badge: "MAIS ESCOLHIDO",
title: "Alocação de sprint",
value: "Eficiência financeira",
badge: "Mais escolhido",
idealFor:
"Desenvolvimento contínuo de features com previsibilidade de budget e prioridade técnica.",
"Times que precisam de entrega contínua com previsibilidade de orçamento e prioridade técnica.",
description:
"Pacotes de horas pré-pagas que garantem disponibilidade na agenda e redução do custo unitário por hora. Ideal para scaling.",
"Pacotes de horas pré-pagas com redução de custo unitário e espaço reservado na agenda para evolução constante.",
benefits: [
"Taxa horária reduzida (Lock-in)",
"Garantia de Disponibilidade",
"Prioridade em Critical Bugs",
"Taxa horária reduzida",
"Garantia de disponibilidade",
"Prioridade em bugs críticos",
],
icon: Briefcase,
color: "green",
featured: true,
},
{
title: "Engenheiro Dedicado",
title: "Engenheiro dedicado",
value: "A partir de R$ 2.800/mês",
badge: "VIP & ESTRATÉGICO",
badge: "Estratégico",
idealFor:
"Empresas que necessitam de um 'Fractional CTO' ou Lead Engineer para governança técnica.",
"Empresas que precisam de liderança técnica contínua, coordenação de backlog e direção arquitetural.",
description:
"Parceria estratégica com foco em sustentabilidade do sistema, manutenção preventiva e evolução arquitetural contínua.",
"Parceria recorrente para sustentação, evolução de sistema, revisão de dívida técnica e tomada de decisão.",
benefits: [
"SLA de Resposta Prioritária",
"Gestão de Débito Técnico",
"Consultoria Estratégica",
"SLA prioritário",
"Gestão de débito técnico",
"Consultoria estratégica",
],
icon: ShieldCheck,
color: "purple",
featured: false,
},
{
title: "Squad por Projeto",
value: "Escopo Fechado",
badge: "CONTROLE TOTAL",
title: "Squad por projeto",
value: "Escopo fechado",
badge: "Controle total",
idealFor:
"Produtos novos ou migrações de sistemas onde o orçamento é rigorosamente definido pelo escopo.",
"Produtos novos, replatforming ou migrações com orçamento definido por entregáveis e marcos claros.",
description:
"Desenvolvimento orientado a entregáveis (milestones). Mitigação de riscos através de definições claras de 'Done'.",
"Execução orientada a milestones com redução de risco através de definição objetiva de escopo e critérios de done.",
benefits: [
"Estimativa de Riscos Inclusa",
"Cronograma de Entregas",
"Garantia Pós-Entrega",
"Estimativa de riscos inclusa",
"Cronograma de entregas",
"Garantia pós-entrega",
],
icon: HardHat,
color: "orange",
featured: false,
},
];
// --- 2. COMPONENTS ---
export function WorkModalities() {
return (
<section
<Section
id="modalities"
className="py-32 px-6 relative"
eyebrow="CONTRACT_PROTOCOLS.sys"
title="Formatos de contratação para contextos técnicos distintos"
description="Modelos pensados para reduzir overhead, preservar velocidade de decisão e manter previsibilidade."
>
{/* Background Decor handled globally now, avoiding overflow-hidden here allows bleed */}
<div className="max-w-7xl mx-auto relative z-10">
{/* Header Profissional */}
<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>CONTRACT_PROTOCOLS.sys</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"
>
Flexibilidade <br />
<span className="text-transparent bg-clip-text bg-gradient-to-r from-blue-500 to-cyan-400">Contratual</span>
</motion.h2>
<p className="mt-6 text-lg text-slate-400 max-w-2xl leading-relaxed font-light border-l-2 border-slate-800 pl-6">
Modelos de engajamento adaptáveis para diferentes estágios de maturidade tecnológica e orçamentária.
</p>
</div>
{/* Comparação de Mercado (Anchoring Effect) */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-24">
<MarketCard
title="Agências Tradicionais"
price="Alto Overhead"
icon={Building2}
color="text-red-500"
delay={0}
best={false}
/>
<MarketCard
title="Consultorias Big Tech"
price="Custo Inflacionado"
icon={Users}
color="text-orange-500"
delay={0.1}
best={false}
/>
<MarketCard
title="Freelancers Generalistas"
price="Risco Técnico"
icon={User}
color="text-yellow-500"
delay={0.2}
best={false}
/>
<MarketCard
title="Engenheiro Especialista"
price="High ROI"
icon={Star}
color="text-emerald-400"
best={true}
delay={0.3}
/>
</div>
{/* Grid de Modalidades */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{WorkModalitiesData.map((modality, index) => (
<ModalityCard key={modality.title} modality={modality} index={index} />
))}
</div>
<div className="ds-grid-4 mb-sp-6">
{[
["Agências tradicionais", "Alto overhead"],
["Consultorias big tech", "Custo inflacionado"],
["Freelancers generalistas", "Risco técnico"],
["Engenheiro especialista", "High ROI"],
].map(([label, value]) => (
<div key={label} className="ds-panel">
<div className="ds-kicker">{label}</div>
<div className="mt-sp-3 font-display text-2xl font-extrabold tracking-[-0.02em]">
{value}
</div>
</div>
))}
</div>
</section>
);
}
function MarketCard({ title, price, icon: Icon, color, best, delay }: any) {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay }}
className={`p-6 rounded-2xl border backdrop-blur-sm transition-all duration-300 ${best
? "bg-emerald-500/5 border-emerald-500/20 shadow-[0_0_20px_rgba(16,185,129,0.1)]"
: "bg-zinc-900/40 border-white/5 opacity-70 hover:opacity-100"
}`}
>
<div className="flex items-center gap-3 mb-3">
<Icon className={`w-4 h-4 ${color}`} />
<h4 className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">
{title}
</h4>
<div className="ds-grid-2">
{WorkModalitiesData.map((modality) => (
<article key={modality.title} className="ds-card">
<div className="flex items-center justify-between gap-sp-4">
<span className="ds-kicker">{modality.badge}</span>
<span className="ds-tag">{modality.value}</span>
</div>
<h3 className="ds-card-title">{modality.title}</h3>
<p className="ds-copy">{modality.description}</p>
<div className="mt-sp-5 rounded-[var(--r-md)] bg-[rgba(10,10,10,.03)] p-sp-4 text-[11px] leading-7 text-ink-80">
Ideal para: {modality.idealFor}
</div>
<ul className="ds-list">
{modality.benefits.map((benefit) => (
<li key={benefit}>{benefit}</li>
))}
</ul>
</article>
))}
</div>
<p className={`text-sm font-bold ${best ? "text-emerald-400" : "text-slate-500"}`}>
{price}
</p>
</motion.div>
);
}
function ModalityCard({ modality, index }: { modality: any; index: number }) {
const Icon = modality.icon;
const isFeatured = modality.featured;
return (
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-50px" }}
transition={{ duration: 0.5, delay: index * 0.1 }}
className={`group relative flex flex-col p-8 rounded-3xl border transition-all duration-500 overflow-hidden ${isFeatured
? "bg-zinc-900/60 border-blue-500/30 shadow-2xl shadow-blue-900/10"
: "bg-zinc-900/40 border-white/5 hover:border-white/10"
}`}
>
{/* Hover Gradient */}
<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 */}
{modality.badge && (
<div className="absolute top-6 right-6">
<span className={`px-3 py-1 rounded-full text-[10px] font-bold tracking-widest uppercase border ${isFeatured
? "bg-blue-500/10 border-blue-500/20 text-blue-300"
: "bg-slate-800/50 border-slate-700 text-slate-400"
}`}>
{modality.badge}
</span>
</div>
)}
{/* Header */}
<div className="relative z-10 mb-8">
<div className={`w-12 h-12 rounded-xl flex items-center justify-center mb-6 transition-transform group-hover:scale-110 ${isFeatured ? "bg-blue-500/10 text-blue-400" : "bg-slate-800/50 text-slate-400"
}`}>
<Icon className="w-6 h-6" />
</div>
<h3 className="text-2xl font-bold text-white mb-2 group-hover:text-blue-200 transition-colors">
{modality.title}
</h3>
<p className="font-mono text-sm text-slate-500">
{modality.value}
</p>
</div>
{/* Content */}
<div className="relative z-10 flex-1">
<p className="text-slate-400 text-sm leading-relaxed mb-8">
{modality.description}
</p>
<ul className="space-y-3 mb-8">
{modality.benefits.map((benefit: string, i: number) => (
<li key={i} className="flex items-start gap-3 text-xs text-slate-300">
<CheckCircle2 className={`w-4 h-4 flex-shrink-0 mt-0.5 ${isFeatured ? "text-blue-400" : "text-slate-500"}`} />
<span>{benefit}</span>
</li>
))}
</ul>
</div>
{/* Footer */}
<div className="relative z-10 pt-6 border-t border-white/5 mt-auto">
<p className="text-[10px] uppercase tracking-wider text-slate-500">
<span className="text-slate-600 font-bold mb-1 block">Ideal Para:</span>
{modality.idealFor}
</p>
</div>
</motion.div>
</Section>
);
}