Files
portifolio-evandrodevbr/app/components/Hero.tsx
T
Evandro Fonseca JuniorandClaude Sonnet 5 d76bbf5974 feat: revamp portfolio with case studies, curriculo page, and local SEO
Adds dynamic case study and curriculo routes, project health monitoring
for live demo status, and strengthens local SEO (structured data,
service-area targeting for Garuva/Joinville/Curitiba, a dedicated
"criação de sites" service page) so search results better match the
professional title (Engenheiro da Computação) and experience (5 anos).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-07-15 01:31:46 -03:00

797 lines
31 KiB
TypeScript

"use client";
import { motion, type Variants } from "framer-motion";
import dynamic from "next/dynamic";
import { useEffect, useState } from "react";
import { shouldLoadHeroScene } from "@/app/lib/should-load-hero-scene";
import { buildWhatsAppUrl, getWhatsAppDisplayNumber } from "@/lib/whatsapp";
import MagneticButton from "./cinematic/MagneticButton";
const HeroScene = dynamic(() => import("./cinematic/HeroScene"), {
ssr: false,
});
type HeroSceneVariant =
| "command"
| "launch"
| "rescue"
| "signal"
| "diagnostic"
| "portrait";
type HeroVariant = {
id: string;
weight: number;
scene: HeroSceneVariant;
layout: "default" | "visual-first" | "split" | "report" | "portrait";
status: string;
chip: string;
eyebrow: string;
headline: string;
subtitle: string;
cta: string;
whatsappMessage: string;
sideLabel: string;
sideTitle: string;
sideText: string;
points: string[];
proof: Array<{ label: string; value: string }>;
};
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 HERO_VARIANTS: HeroVariant[] = [
{
id: "command-center",
weight: 22,
scene: "command",
layout: "default",
status: "Disponível para novas oportunidades",
chip: "Garuva · Joinville · Curitiba · Remoto",
eyebrow:
"Evandro Fonseca Junior | Engenheiro da Computação e Desenvolvedor de Software",
headline:
"Engenharia de software para sistemas que precisam operar de verdade.",
subtitle:
"Profissional disponível para oportunidades CLT e PJ em Garuva, Joinville, Norte de Santa Catarina e Curitiba; também atuo em consultoria para produtos, integrações e sistemas críticos.",
cta: "Conversar sobre uma oportunidade",
whatsappMessage:
"Olá! Quero conversar sobre uma oportunidade profissional em engenharia de software.",
sideLabel: "Contato profissional direto",
sideTitle: "Procurando um engenheiro da computação para o seu time?",
sideText:
"Vamos conversar sobre o contexto técnico, o desafio da vaga e como posso contribuir da arquitetura à entrega.",
points: [
"Engenharia full-stack com base em sistemas e integrações.",
"Next.js, TypeScript, Rust, IoT e arquitetura de software.",
"Disponível para times do Norte de SC, Curitiba e atuação remota.",
],
proof: [
{
label: "Operacao",
value: "Fluxo pensado para discovery, build, deploy e sustentacao.",
},
{
label: "Clareza",
value: "Menos ruído entre produto, engenharia e decisao tecnica.",
},
{
label: "Resultado",
value: "Base pronta para operar, evoluir e receber proximo ciclo.",
},
],
},
{
id: "launch-sequence",
weight: 18,
scene: "launch",
layout: "visual-first",
status: "Launch sequence ready",
chip: "Discovery → build → deploy",
eyebrow: "MVP, produto e entrega em producao",
headline:
"Tire seu MVP do plano e coloque em producao com base tecnica real.",
subtitle:
"Transformo escopo solto em sequencia de lancamento: o que validar, o que construir primeiro e como publicar sem criar uma divida tecnica logo no inicio.",
cta: "Lancar meu MVP",
whatsappMessage:
"Ola! Quero lancar um MVP com uma base tecnica segura. Podemos conversar?",
sideLabel: "Sequencia de lancamento",
sideTitle: "Do primeiro corte ao deploy.",
sideText:
"A conversa define prioridade, risco e o menor caminho confiavel para colocar a primeira versao no ar.",
points: [
"Recorte de MVP sem inflar escopo.",
"Entrega incremental com criterio de producao.",
"Deploy e continuidade pensados desde o inicio.",
],
proof: [
{ label: "01", value: "Diagnostico do objetivo e gargalo principal." },
{ label: "02", value: "Plano de construcao com risco visivel." },
{ label: "03", value: "Publicacao e proximo ciclo sem perder contexto." },
],
},
{
id: "rescue-split",
weight: 16,
scene: "rescue",
layout: "split",
status: "Rescue mode available",
chip: "Legado, instabilidade e projeto travado",
eyebrow: "Rescue tecnico para sistemas que precisam voltar a andar",
headline:
"Codigo confuso, entrega travada ou sistema instavel? Vamos organizar.",
subtitle:
"Entro no ponto onde o projeto parou: leio o fluxo real, separo sintoma de causa e transformo caos tecnico em plano executavel.",
cta: "Destravar meu projeto",
whatsappMessage:
"Ola! Tenho um projeto travado ou instavel e quero destravar. Podemos conversar?",
sideLabel: "Antes / depois tecnico",
sideTitle: "Do caos operacional para uma rota clara.",
sideText:
"O primeiro objetivo nao e reescrever tudo. E encontrar o menor corte seguro que devolve controle e previsibilidade.",
points: [
"Leitura rapida de fluxo, risco e acoplamento.",
"Priorizacao do que destrava sem reescrever tudo.",
"Correcoes com criterio para nao trocar um bug por outro.",
],
proof: [
{
label: "Antes",
value: "Backlog confuso, bugs recorrentes e pouca previsibilidade.",
},
{
label: "Intervencao",
value: "Mapa de risco, cortes pequenos e decisao tecnica clara.",
},
{ label: "Depois", value: "Entrega volta a andar com menos improviso." },
],
},
{
id: "iot-signal-map",
weight: 15,
scene: "signal",
layout: "default",
status: "Signal map active",
chip: "Hardware + software + operacao",
eyebrow: "Produtos conectados, telemetria e integracoes criticas",
headline:
"Conecte dispositivo, backend e interface em uma operacao confiavel.",
subtitle:
"Atuo na fronteira que costuma quebrar primeiro: sinal fisico, API, painel, dados e rotina operacional funcionando como um unico produto.",
cta: "Conectar meu produto",
whatsappMessage:
"Ola! Quero conectar hardware, software e operacao em um produto confiavel. Podemos conversar?",
sideLabel: "Mapa de sinais",
sideTitle: "Dados, comandos e painel no mesmo fluxo.",
sideText:
"A conversa identifica fontes de dados, contratos, pontos de falha e o caminho para transformar integracao em produto.",
points: [
"Contratos claros entre dispositivo, API e interface.",
"Telemetria e dashboards com contexto operacional.",
"Base pronta para automacao, IA ou analytics.",
],
proof: [
{
label: "Entrada",
value: "Sensores, dispositivos, eventos ou sistemas externos.",
},
{
label: "Meio",
value: "APIs, WebSockets, filas, banco e observabilidade.",
},
{
label: "Saida",
value: "Painel, operacao, alerta e decisao de produto.",
},
],
},
{
id: "diagnostic-report",
weight: 15,
scene: "diagnostic",
layout: "report",
status: "Diagnostic report open",
chip: "Arquitetura antes de mais codigo",
eyebrow: "Diagnostico tecnico para decidir melhor",
headline: "Antes de escrever mais codigo, descubra o proximo passo certo.",
subtitle:
"Quando a base cresce sem clareza, mais features podem piorar o problema. Eu ajudo a mapear risco, arquitetura e prioridades antes da proxima decisao cara.",
cta: "Agendar diagnostico",
whatsappMessage:
"Ola! Quero um diagnostico tecnico para decidir o proximo passo do meu sistema. Podemos conversar?",
sideLabel: "Relatorio tecnico",
sideTitle: "Risco, arquitetura e rota de acao.",
sideText:
"Ideal para produto que precisa escalar, migrar, estabilizar ou parar de acumular retrabalho tecnico.",
points: [
"Mapa claro dos gargalos e dependencias.",
"Trade-offs traduzidos para decisao de negocio.",
"Plano de transicao sem promessa magica.",
],
proof: [
{ label: "Risco", value: "O que pode quebrar, atrasar ou custar caro." },
{
label: "Arquitetura",
value: "Onde simplificar, separar ou estabilizar primeiro.",
},
{ label: "Acao", value: "Proximo corte de implementacao com criterio." },
],
},
{
id: "cinematic-portrait",
weight: 14,
scene: "portrait",
layout: "portrait",
status: "Direct technical partner",
chip: "Conversa direta com quem implementa",
eyebrow: "Engenharia senior, ownership e execucao",
headline: "Um parceiro tecnico para transformar complexidade em entrega.",
subtitle:
"Voce nao precisa apenas de tela bonita ou codigo isolado. Precisa de alguem que entenda contexto, escolha o corte certo e acompanhe ate o sistema operar.",
cta: "Falar com Evandro",
whatsappMessage:
"Ola Evandro! Quero conversar sobre um projeto e entender como podemos colocar esse sistema no ar.",
sideLabel: "Founder-led engineering",
sideTitle: "Menos repasse, mais ownership.",
sideText:
"A conversa e comigo: contexto tecnico, prioridade, risco e caminho de entrega sem perder informacao entre camadas.",
points: [
"Atendimento consultivo com leitura tecnica real.",
"Do produto ao backend sem perder contexto.",
"Execucao com criterio, nao so codigo por demanda.",
],
proof: [
{
label: "Perfil",
value: "Engenharia full-stack com base em sistemas e integracoes.",
},
{
label: "Postura",
value: "Clareza, autonomia e decisao tecnica pragmatica.",
},
{
label: "Acesso",
value: "WhatsApp direto para conversa tecnica inicial.",
},
],
},
];
const LAYOUT_CLASSES: Record<HeroVariant["layout"], string> = {
default:
"grid items-start gap-sp-8 lg:grid-cols-[minmax(0,1.1fr)_minmax(340px,0.9fr)] lg:gap-sp-10",
"visual-first":
"grid items-start gap-sp-8 lg:grid-cols-[minmax(340px,0.9fr)_minmax(0,1.1fr)] lg:gap-sp-10",
split: "grid items-stretch gap-sp-8 lg:grid-cols-2 lg:gap-sp-10",
report:
"grid items-start gap-sp-8 lg:grid-cols-[minmax(360px,0.85fr)_minmax(0,1.15fr)] lg:gap-sp-10",
portrait:
"grid items-center gap-sp-8 lg:grid-cols-[minmax(0,1fr)_minmax(320px,0.72fr)] lg:gap-sp-10",
};
function getGreeting(message: string) {
const hour = new Date().getHours();
if (hour >= 8 && hour < 12) return `Bom dia! ${message}`;
if (hour >= 13 && hour < 18) return `Boa tarde! ${message}`;
return message;
}
function SideVisual({ variant }: { variant: HeroVariant }) {
if (variant.scene === "launch") {
return (
<div className="mb-sp-6 grid gap-sp-3">
{variant.proof.map((item, index) => (
<div
key={item.label}
className="cinematic-glass flex gap-sp-4 p-sp-4"
>
<span className="font-mono text-[11px] text-[var(--slash)]">
0{index + 1}
</span>
<div>
<div className="text-[12px] font-bold uppercase tracking-[0.08em]">
{item.label}
</div>
<p className="mt-sp-1 text-[12px] leading-6 text-[rgba(244,242,237,.7)]">
{item.value}
</p>
</div>
</div>
))}
</div>
);
}
if (variant.scene === "rescue") {
return (
<div className="mb-sp-6 grid gap-sp-4 sm:grid-cols-2">
<div className="border border-[rgba(255,80,80,.28)] bg-[rgba(255,80,80,.05)] p-sp-4">
<div className="font-mono text-[10px] uppercase tracking-[0.12em] text-[rgba(255,160,160,.9)]">
Antes
</div>
<p className="mt-sp-3 text-[13px] leading-6 text-[rgba(244,242,237,.72)]">
Bugs recorrentes, escopo nebuloso e medo de mexer.
</p>
</div>
<div className="border border-[rgba(204,255,0,.35)] bg-[rgba(204,255,0,.06)] p-sp-4">
<div className="font-mono text-[10px] uppercase tracking-[0.12em] text-[var(--slash)]">
Depois
</div>
<p className="mt-sp-3 text-[13px] leading-6 text-[rgba(244,242,237,.86)]">
Rota clara, cortes menores e entrega voltando a andar.
</p>
</div>
</div>
);
}
if (variant.scene === "signal") {
return (
<div className="relative mb-sp-6 h-40 overflow-hidden border border-[rgba(255,255,255,.08)] bg-[rgba(255,255,255,.03)]">
<svg className="absolute inset-0 h-full w-full" viewBox="0 0 420 160">
<path
d="M28 116 C108 18 190 142 284 56 S380 36 400 28"
fill="none"
stroke="rgba(204,255,0,.75)"
strokeWidth="2"
/>
{[28, 150, 284, 400].map((x, index) => (
<circle
key={x}
cx={x}
cy={[116, 78, 56, 28][index]}
r="6"
fill="#ccff00"
/>
))}
</svg>
</div>
);
}
if (variant.scene === "diagnostic") {
return (
<div className="mb-sp-6 border border-[rgba(255,255,255,.1)] bg-[rgba(255,255,255,.04)] p-sp-5">
<div className="mb-sp-4 flex items-center justify-between border-b border-[rgba(255,255,255,.08)] pb-sp-3 font-mono text-[10px] uppercase tracking-[0.12em]">
<span>Risk report</span>
<span className="text-[var(--slash)]">Actionable</span>
</div>
<div className="space-y-sp-3">
{variant.proof.map((item) => (
<div key={item.label}>
<div className="flex justify-between text-[12px] text-[rgba(244,242,237,.78)]">
<span>{item.label}</span>
<span className="text-[var(--slash)]">mapped</span>
</div>
<div className="mt-sp-2 h-px bg-[linear-gradient(90deg,var(--slash),rgba(255,255,255,.08))]" />
</div>
))}
</div>
</div>
);
}
if (variant.scene === "portrait") {
return (
<div className="mb-sp-6 border border-[rgba(204,255,0,.22)] bg-[radial-gradient(circle_at_70%_20%,rgba(204,255,0,.14),transparent_35%),rgba(255,255,255,.035)] p-sp-5">
<div className="font-display text-[clamp(2.5rem,8vw,5rem)] font-extrabold leading-none tracking-[-0.08em] text-[rgba(244,242,237,.95)]">
EFJ
</div>
<div className="mt-sp-4 font-mono text-[10px] uppercase tracking-[0.14em] text-[var(--slash)]">
Engenharia com contato direto
</div>
</div>
);
}
return (
<div className="mb-sp-6 grid gap-sp-3 font-mono text-[11px] uppercase tracking-[0.08em]">
{["deploy pipeline", "api health", "production ready"].map((item) => (
<div
key={item}
className="flex items-center justify-between border border-[rgba(255,255,255,.1)] bg-[rgba(255,255,255,.04)] px-sp-4 py-sp-3"
>
<span>{item}</span>
<span className="text-[var(--slash)]">ok</span>
</div>
))}
</div>
);
}
export default function Hero() {
const variant = HERO_VARIANTS[0];
const [shouldReduceMotion, setShouldReduceMotion] = useState(false);
const [shouldRenderHeroScene, setShouldRenderHeroScene] = useState(false);
const [whatsAppDisplay, setWhatsAppDisplay] = useState("WhatsApp");
useEffect(() => {
const reducedMotion = window.matchMedia(
"(prefers-reduced-motion: reduce)",
).matches;
const coarsePointer = window.matchMedia("(pointer: coarse)").matches;
setShouldReduceMotion(reducedMotion);
setWhatsAppDisplay(getWhatsAppDisplayNumber());
if (
!shouldLoadHeroScene({
reducedMotion,
coarsePointer,
width: window.innerWidth,
})
) {
return;
}
const timer = window.setTimeout(() => setShouldRenderHeroScene(true), 1500);
return () => window.clearTimeout(timer);
}, []);
const handleWhatsAppClick = () => {
window.open(
buildWhatsAppUrl(getGreeting(variant.whatsappMessage)),
"_blank",
"noopener,noreferrer",
);
};
const heroContainer: Variants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: { staggerChildren: 0.1, delayChildren: 0.3 },
},
};
const easeOut: [number, number, number, number] = [0.16, 1, 0.3, 1];
const heroItem: Variants = {
hidden: shouldReduceMotion
? { opacity: 0 }
: { opacity: 0, y: 40, filter: "blur(10px)" },
visible: {
opacity: 1,
y: 0,
filter: "blur(0px)",
transition: { duration: 1, ease: easeOut },
},
};
const heroItemSlow: Variants = {
hidden: shouldReduceMotion
? { opacity: 0 }
: { opacity: 0, y: 60, filter: "blur(12px)" },
visible: {
opacity: 1,
y: 0,
filter: "blur(0px)",
transition: { duration: 1.2, ease: easeOut },
},
};
const contentOrder =
variant.layout === "visual-first" || variant.layout === "report"
? "lg:order-2"
: "";
const panelOrder =
variant.layout === "visual-first" || variant.layout === "report"
? "lg:order-1"
: "";
return (
<section
id="home"
className="relative flex min-h-[100svh] w-full items-center overflow-hidden bg-[var(--ink)] text-[#f4f2ed]"
data-hero-variant={variant.id}
>
{shouldRenderHeroScene && <HeroScene variant={variant.scene} />}
<div className="pointer-events-none absolute inset-0 z-[1] bg-[radial-gradient(ellipse_80%_60%_at_50%_40%,transparent_0%,rgba(10,10,10,0.4)_50%,var(--ink)_100%)]" />
<div className="pointer-events-none absolute inset-0 z-[1] bg-gradient-to-b from-transparent via-transparent to-[var(--ink)]" />
<div
className="pointer-events-none absolute inset-0 z-[2] opacity-[0.015]"
style={{
backgroundImage:
"repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(255,255,255,0.03) 2px, rgba(255,255,255,0.03) 4px)",
}}
/>
<div className="pointer-events-none absolute inset-0 z-[2] bg-[radial-gradient(circle_at_50%_50%,transparent_40%,rgba(10,10,10,0.6)_100%)]" />
<div className="relative z-10 mx-auto flex w-full max-w-[1240px] flex-col px-sp-6 py-sp-16 lg:px-sp-16 lg:py-sp-12">
<motion.div
initial="hidden"
animate="visible"
variants={heroContainer}
className={LAYOUT_CLASSES[variant.layout]}
>
<div className={`max-w-[800px] ${contentOrder}`.trim()}>
<motion.div
className="mb-sp-5 flex flex-wrap items-center gap-sp-3"
variants={heroItem}
>
<div className="cinematic-glass inline-flex items-center gap-sp-3 px-4 py-2">
<motion.span
className="h-2 w-2 rounded-sm bg-[var(--slash)]"
animate={
shouldReduceMotion
? { opacity: 1 }
: {
opacity: [1, 0.25, 1],
boxShadow: [
"0 0 4px rgba(204,255,0,0.6)",
"0 0 12px rgba(204,255,0,0.2)",
"0 0 4px rgba(204,255,0,0.6)",
],
}
}
transition={
shouldReduceMotion
? { duration: 0 }
: {
duration: 2,
ease: "easeInOut",
repeat: Number.POSITIVE_INFINITY,
}
}
/>
<span className="font-mono text-[10px] uppercase tracking-[0.12em] text-[rgba(244,242,237,.82)]">
{variant.status}
</span>
</div>
<div className="cinematic-glass inline-flex items-center gap-sp-2 px-4 py-2 text-[11px] text-[rgba(244,242,237,.72)]">
{variant.chip}
</div>
</motion.div>
<motion.p
className="mb-sp-4 max-w-[560px] font-mono text-[10px] uppercase tracking-[0.12em] text-[var(--slash)]"
variants={heroItem}
>
{variant.eyebrow}
</motion.p>
<motion.h1
className="max-w-[940px] text-balance font-display text-[clamp(3rem,7vw,6.2rem)] font-extrabold leading-[0.9] tracking-[-0.055em]"
variants={heroItemSlow}
>
{variant.headline}
</motion.h1>
<motion.p
className="mt-sp-6 max-w-[700px] text-[16px] leading-8 text-[rgba(244,242,237,.88)] lg:text-[17px]"
variants={heroItem}
>
{variant.subtitle}
</motion.p>
<motion.ul
className="mt-sp-8 grid gap-sp-3 sm:grid-cols-3"
variants={heroItem}
>
{variant.points.map((point, i) => (
<motion.li
key={point}
className="cinematic-glass flex min-w-0 gap-sp-3 px-sp-4 py-sp-4 text-[13px] leading-6 text-[rgba(244,242,237,.82)]"
initial={
shouldReduceMotion
? { opacity: 0 }
: { opacity: 0, y: 30, scale: 0.95 }
}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{
duration: 0.8,
delay: 0.8 + i * 0.1,
ease: [0.16, 1, 0.3, 1],
}}
>
<span className="mt-[8px] h-1.5 w-1.5 shrink-0 rounded-full bg-[var(--slash)] shadow-[0_0_8px_rgba(204,255,0,0.5)]" />
<span>{point}</span>
</motion.li>
))}
</motion.ul>
<motion.div
className="mt-sp-8 flex flex-wrap items-center gap-sp-4"
variants={heroItem}
>
<MagneticButton
as="button"
type="button"
onClick={handleWhatsAppClick}
className="cinematic-cta-primary inline-flex min-h-12 items-center justify-center px-sp-8 font-mono text-[11px] font-bold uppercase tracking-[0.1em] text-[#0A0A0A] no-underline"
data-cursor="View"
strength={0.25}
>
<span className="relative z-10">{variant.cta}</span>
</MagneticButton>
<MagneticButton
as="a"
href="/cases"
className="cinematic-cta-secondary inline-flex min-h-12 items-center justify-center px-sp-8 font-mono text-[11px] font-bold uppercase tracking-[0.1em] text-[#f4f2ed] no-underline"
data-cursor="View"
strength={0.25}
>
Ver casos selecionados
</MagneticButton>
<MagneticButton
as="a"
href="/assets/Evandro_Fonseca_Junior_CV.pdf"
download
className="inline-flex min-h-12 items-center justify-center px-sp-2 text-[12px] font-medium text-[rgba(244,242,237,.76)] no-underline transition-colors duration-300 hover:text-[var(--slash)]"
strength={0.2}
>
Download CV
</MagneticButton>
</motion.div>
<motion.div
className="mt-sp-10 flex flex-wrap items-center gap-x-sp-6 gap-y-sp-3 border-t border-[rgba(255,255,255,.06)] pt-sp-6"
variants={heroItem}
>
<div className="inline-flex items-center gap-sp-3 text-[11px] uppercase tracking-[0.08em] text-[rgba(244,242,237,.68)]">
<span className="h-2 w-2 rounded-full bg-[var(--slash)] shadow-[0_0_8px_var(--slash)]" />
Garuva, SC / Brasil
</div>
<div className="inline-flex items-center gap-sp-3 text-[11px] uppercase tracking-[0.08em] text-[rgba(244,242,237,.68)]">
<span className="h-[1px] w-6 bg-[rgba(255,255,255,.2)]" />
Next.js, Tauri, Rust, Node.js
</div>
<div className="flex items-center gap-sp-2">
{SOCIAL_LINKS.map((link) => (
<MagneticButton
key={link.label}
as="a"
href={link.href}
aria-label={link.label}
target={link.href.startsWith("http") ? "_blank" : undefined}
rel={
link.href.startsWith("http")
? "noopener noreferrer"
: undefined
}
className="cinematic-social-link inline-flex h-11 w-11 items-center justify-center rounded-full"
strength={0.4}
>
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill={link.stroke ? "none" : "currentColor"}
stroke={link.stroke ? "currentColor" : "none"}
strokeWidth={link.stroke ? "2" : undefined}
aria-hidden="true"
>
{link.icon}
</svg>
</MagneticButton>
))}
</div>
</motion.div>
</div>
<motion.aside
className={`cinematic-aside-card relative overflow-hidden p-sp-6 lg:p-sp-8 ${panelOrder}`.trim()}
initial={
shouldReduceMotion
? { opacity: 0 }
: { opacity: 0, x: 60, rotateY: -8, transformPerspective: 1000 }
}
animate={
shouldReduceMotion
? { opacity: 1 }
: { opacity: 1, x: 0, rotateY: 0 }
}
transition={{ duration: 1.2, delay: 0.5, ease: [0.16, 1, 0.3, 1] }}
>
<motion.div
className="absolute inset-x-0 top-0 h-px bg-[linear-gradient(90deg,transparent,rgba(204,255,0,0.8),transparent)]"
initial={{ scaleX: 0 }}
animate={{ scaleX: 1 }}
transition={{ duration: 1.5, delay: 1, ease: [0.16, 1, 0.3, 1] }}
/>
<SideVisual variant={variant} />
<div className="border-b border-[rgba(255,255,255,0.08)] pb-sp-5">
<div className="font-mono text-[10px] uppercase tracking-[0.14em] text-[var(--slash)]">
{variant.sideLabel}
</div>
<h2 className="mt-sp-3 font-display text-[clamp(1.35rem,3vw,2rem)] font-extrabold leading-[1.04] tracking-[-0.03em] text-[#f7f4ee]">
{variant.sideTitle}
</h2>
<p className="mt-sp-4 text-[14px] leading-7 text-[rgba(244,242,237,.78)]">
{variant.sideText}
</p>
<button
type="button"
onClick={handleWhatsAppClick}
className="mt-sp-5 inline-flex w-full items-center justify-center bg-[var(--slash)] px-sp-5 py-sp-4 font-mono text-[11px] font-bold uppercase tracking-[0.1em] text-[var(--ink)] transition hover:bg-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--slash)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)]"
>
Agendar no WhatsApp: {whatsAppDisplay}
</button>
</div>
<div className="mt-sp-6 space-y-sp-5">
{variant.proof.map((item, index) => (
<motion.article
key={`${variant.id}-${item.label}`}
className="border-b border-[rgba(255,255,255,0.08)] pb-sp-5 last:border-b-0 last:pb-0"
initial={
shouldReduceMotion
? { opacity: 0 }
: { opacity: 0, y: 20, filter: "blur(6px)" }
}
animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
transition={{
duration: 0.8,
delay: 0.8 + index * 0.12,
ease: [0.16, 1, 0.3, 1],
}}
>
<div className="font-mono text-[10px] uppercase tracking-[0.12em] text-[rgba(244,242,237,.56)]">
{item.label}
</div>
<p className="mt-sp-2 text-[14px] leading-7 text-[rgba(244,242,237,.92)]">
{item.value}
</p>
</motion.article>
))}
</div>
</motion.aside>
</motion.div>
</div>
<motion.div
className="absolute bottom-8 left-1/2 z-10 -translate-x-1/2"
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 2, duration: 1 }}
>
<motion.div
className="flex flex-col items-center gap-2"
animate={{ y: [0, 8, 0] }}
transition={{
duration: 2,
repeat: Number.POSITIVE_INFINITY,
ease: "easeInOut",
}}
>
<span className="font-mono text-[8px] uppercase tracking-[0.2em] text-[rgba(244,242,237,.4)]">
Scroll
</span>
<div className="h-8 w-px bg-gradient-to-b from-[var(--slash)] to-transparent opacity-50" />
</motion.div>
</motion.div>
</section>
);
}