feat: initialize portfolio site with core components, pages, and content structure, and update Next.js configuration.

This commit is contained in:
Evandro Fonseca Junior committed 2026-03-17 20:08:00 -03:00
1 parent 223746b898
commit d00258dcd1
22 files changed
+1512 -8038

No files matched your search

-7516
View File
File diff suppressed because it is too large. Load diff
+114
View File
@@ -0,0 +1,114 @@
import type { Metadata } from "next";
import Link from "next/link";
import { caseStudies, siteName, siteUrl } from "@/lib/site-content";
export const metadata: Metadata = {
title: `Cases de Engenharia de Software, Next.js e IoT | ${siteName}`,
description:
"Cases selecionados em engenharia de software, Next.js, IoT, integracoes e software para contextos criticos. Portfolio tecnico com foco em clareza, performance e execucao.",
alternates: {
canonical: `${siteUrl}/cases`,
},
openGraph: {
title: `Cases de Engenharia de Software, Next.js e IoT | ${siteName}`,
description:
"Cases selecionados em engenharia de software, Next.js, IoT, integracoes e software para contextos criticos.",
url: `${siteUrl}/cases`,
siteName,
type: "website",
},
};
export default function CasesPage() {
const jsonLd = {
"@context": "https://schema.org",
"@graph": [
{
"@type": "CollectionPage",
"@id": `${siteUrl}/cases#page`,
name: "Cases de engenharia de software",
url: `${siteUrl}/cases`,
description:
"Colecao de cases em Next.js, IoT, integracoes e software para operacoes criticas.",
},
{
"@type": "ItemList",
itemListElement: caseStudies.map((caseStudy, index) => ({
"@type": "ListItem",
position: index + 1,
name: caseStudy.title,
url: caseStudy.demoUrl ?? caseStudy.githubUrl ?? `${siteUrl}/cases`,
})),
},
],
};
return (
<main>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<section className="ds-section ds-section-dark">
<div className="ds-section-shell">
<div className="max-w-[860px]">
<p className="ds-eyebrow">Selected work</p>
<h1 className="ds-section-title max-w-[14ch] text-balance text-white">
Cases em software, Next.js, integracoes e produtos conectados.
</h1>
<p className="ds-copy max-w-[64ch] !text-[rgba(255,255,255,0.78)]">
Esta pagina organiza projetos com leitura mais indexavel e comercial, conectando stack,
contexto tecnico e tipo de problema resolvido.
</p>
</div>
</div>
</section>
<section className="ds-section">
<div className="ds-section-shell">
<div className="ds-grid-2">
{caseStudies.map((caseStudy) => (
<article key={caseStudy.id} className="ds-card">
<div className="ds-kicker">{caseStudy.title}</div>
<h2 className="ds-card-title">{caseStudy.subtitle}</h2>
<p className="ds-copy">{caseStudy.summary}</p>
<p className="mt-sp-4 text-[13px] leading-7 text-[var(--meta)]">
{caseStudy.impact}
</p>
<ul className="ds-tags mt-sp-6">
{caseStudy.stack.map((item) => (
<li key={item} className="ds-tag">
{item}
</li>
))}
</ul>
<div className="mt-sp-6 flex flex-wrap gap-sp-3">
{caseStudy.demoUrl ? (
<a href={caseStudy.demoUrl} className="ds-link ds-link-accent" target="_blank" rel="noreferrer">
Abrir projeto
</a>
) : null}
{caseStudy.githubUrl ? (
<a href={caseStudy.githubUrl} className="ds-link ds-link-muted" target="_blank" rel="noreferrer">
Ver codigo
</a>
) : null}
</div>
</article>
))}
</div>
<div className="mt-sp-10 flex flex-wrap gap-sp-3">
<Link href="/servicos" className="ds-link ds-link-accent">
Ver servicos
</Link>
<Link href="/#contact" className="ds-link ds-link-muted">
Ir para contato
</Link>
</div>
</div>
</section>
</main>
);
}
+31 -34
View File
@@ -1,4 +1,4 @@
"use client";
"use client";
import { motion, type Variants, useReducedMotion } from "framer-motion";
import profilePhoto from "@/public/edev.jpeg";
@@ -8,39 +8,34 @@ import { LazyRevealImage } from "./animations/LazyRevealImage";
const STORY_DATA = [
{
id: "HARDWARE_BRIDGE",
title: "Do físico ao digital",
label: "Hardware + software",
title: "Do sinal fisico ao produto digital",
content:
"Construo a ponte entre hardware e software. Projeto sistemas capazes de orquestrar dispositivos físicos, protocolos de baixo nível e interfaces web modernas com baixa latência e alta resiliência.",
"Construo a ponte entre dispositivos, protocolos de baixo nivel, backend e interface para que o sistema inteiro pareca uma unica experiencia coerente.",
},
{
id: "RUNTIME_EXEC",
title: "Entrega sob restrições",
label: "Execution",
title: "Entrega clara mesmo sob restricao",
content:
"Atuo com naturalidade em cenários críticos envolvendo integrações de hardware, APIs complexas e infraestrutura legada, sempre priorizando a estabilidade e a legibilidade técnica.",
"Trabalho bem em contextos com legado, integracoes complexas e pouca margem para erro, priorizando estabilidade, leitura tecnica e evolucao sustentavel.",
},
];
const VALUES_DATA = [
const PROFILE_POINTS = [
{
title: "Pragmatismo arquitetural",
title: "Arquitetura pragmatica",
description:
"Decisões guiadas por contexto real e operabilidade. A melhor tecnologia é aquela que resolve o problema sem adicionar complexidade inútil ao sistema.",
"Escolhas guiadas por contexto, operabilidade e impacto real no produto.",
},
{
title: "Fundação & Integração",
title: "Integracao profunda",
description:
"Domínio de protocolos de rede e arquiteturas distribuídas para conectar desde microcontroladores até sistemas robustos na nuvem.",
"Capacidade de atravessar camada fisica, servicos e interface sem perder consistencia.",
},
{
title: "Ownership ponta a ponta",
description:
"Responsabilidade que atravessa camadas: do pulso elétrico no hardware até a renderização do último componente na tela do usuário.",
},
{
title: "Security & Safety",
description:
"Segurança e resiliência tratadas como base. Sistemas desenhados para falhar graciosamente e proteger dados do dispositivo à nuvem.",
"Responsabilidade que cobre discovery, implementacao, qualidade e operacao.",
},
];
@@ -84,9 +79,9 @@ export default function About() {
return (
<Section
id="about"
eyebrow="WHOAMI.sys"
title="Engenharia orientada por clareza, nao por ruido"
description="Transformo complexidade tecnica em sistemas estaveis, operaveis e alinhados ao negocio."
eyebrow="Operating context"
title="Engenharia de software, arquitetura e integracoes complexas"
description="Atuo na intersecao entre arquitetura de software, integracoes de sistemas, hardware + software e entrega full-stack para produtos que precisam funcionar com confiabilidade."
disableChildrenAnimation
aside={
<div className="mt-sp-4 ds-metric">
@@ -108,14 +103,14 @@ export default function About() {
>
{STORY_DATA.map((step) => (
<motion.article
key={step.id}
key={step.title}
className="ds-card"
variants={cardVariants}
whileHover={cardHover}
whileTap={shouldReduceMotion ? undefined : { scale: 0.99 }}
transition={{ type: "spring", stiffness: 180, damping: 18 }}
>
<div className="ds-kicker">{step.id}</div>
<div className="ds-kicker">{step.label}</div>
<DrawLine delay={0.03} />
<h3 className="ds-card-title">{step.title}</h3>
<p className="ds-copy">{step.content}</p>
@@ -136,25 +131,25 @@ export default function About() {
className="mb-sp-6 h-[220px] w-full overflow-hidden rounded-[6px] border border-[var(--border)]"
imageClassName="object-cover"
/>
<div className="ds-kicker">System Signature</div>
<div className="ds-kicker">Technical profile</div>
<DrawLine delay={0.05} />
<h3 className="ds-card-title">Base tecnica e postura de entrega</h3>
<p className="ds-copy">
Evandro Fonseca Junior. Engenheiro de Software Full-Stack com foco
na convergência entre hardware e aplicações web modernas.
Especialista em TypeScript, Node.js, ecossistema React e automação
de dispositivos físicos.
Sou engenheiro de software full-stack com foco em integrar camadas
que normalmente ficam separadas: hardware, servicos, operacao e
interface. Meu trabalho e dar forma clara ao que costuma nascer
fragmentado.
</p>
<div className="ds-grid-2 mt-sp-6">
{VALUES_DATA.map((value) => (
<div className="mt-sp-6 space-y-sp-4">
{PROFILE_POINTS.map((point) => (
<div
key={value.title}
key={point.title}
className="border-t border-[var(--border)] pt-sp-4"
>
<div className="text-[11px] font-medium uppercase tracking-[0.08em]">
{value.title}
<div className="text-[12px] font-medium tracking-[0.02em]">
{point.title}
</div>
<p className="ds-copy !mt-sp-2">{value.description}</p>
<p className="ds-copy !mt-sp-2">{point.description}</p>
</div>
))}
</div>
@@ -163,3 +158,5 @@ export default function About() {
</Section>
);
}
+44 -31
View File
@@ -10,6 +10,8 @@ const WHATSAPP_NUMBER = "5547992742365";
export default function Contact() {
const shouldReduceMotion = useReducedMotion();
const fieldClassName =
"mt-sp-2 w-full border border-[rgba(255,255,255,.12)] bg-[rgba(255,255,255,.06)] px-sp-4 py-[14px] text-[13px] leading-6 text-white outline-none transition-[border-color,box-shadow] focus-visible:border-[var(--slash)] focus-visible:ring-2 focus-visible:ring-[rgba(204,255,0,0.34)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)]";
const [formData, setFormData] = useState({
name: "",
email: "",
@@ -55,9 +57,9 @@ export default function Contact() {
return (
<Section
id="contact"
eyebrow="CONTACT_PROTOCOL.init"
title="Vamos discutir o proximo sistema"
description="Descreva o contexto, o gargalo ou o produto. O contato abre direto no WhatsApp com briefing estruturado."
eyebrow="Contact"
title="Consultoria tecnica para software, arquitetura e integracoes criticas"
description="Fale comigo se voce precisa de engenharia de software, consultoria tecnica, arquitetura de software ou apoio em projetos com Next.js, Tauri, Rust e IoT."
dark
disableChildrenAnimation
>
@@ -82,18 +84,22 @@ export default function Contact() {
whileTap={shouldReduceMotion ? undefined : { scale: 0.995 }}
transition={{ type: "spring", stiffness: 180, damping: 18 }}
>
<div className="ds-kicker">Response SLA</div>
<div className="ds-kicker">Availability</div>
<DrawLine delay={0.04} />
<h3 className="ds-card-title">Retorno inicial em ate 24h</h3>
<h3 className="ds-card-title">Resposta inicial em ate 24h</h3>
<p className="ds-copy">
Ideal para discovery tecnico, revisao de arquitetura, rescue de
projeto, otimizacao de performance ou construcao de produto do zero.
projeto, performance ou construcao de produto com ownership real.
</p>
<div className="mt-sp-6 space-y-sp-4 text-[12px] leading-7 text-[rgba(255,255,255,.7)]">
<div className="mt-sp-6 space-y-sp-4 text-[13px] leading-7 text-[rgba(255,255,255,.85)]">
<div>WhatsApp: +55 47 99274-2365</div>
<div>E-mail: evandrofonsecajunior@gmail.com</div>
<div>Base: Garuva, Santa Catarina | atuacao remota</div>
</div>
<div className="mt-sp-6 border-t border-[rgba(255,255,255,.08)] pt-sp-4 text-[12px] leading-6 text-[rgba(255,255,255,.75)]">
Envie contexto, prazo e objetivo. Eu cuido de transformar isso em
conversa tecnica objetiva.
</div>
</motion.div>
<motion.form
@@ -111,54 +117,60 @@ export default function Contact() {
transition={{ type: "spring", stiffness: 180, damping: 18 }}
>
<div>
<label htmlFor="contact-name" className="ds-kicker block">
<label
htmlFor="contact-name"
className="block text-[12px] font-medium text-[rgba(255,255,255,.85)]"
>
Nome completo
</label>
<input
id="contact-name"
name="name"
required
type="text"
autoComplete="name"
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,.06)] px-sp-4 py-[14px] text-[12px] outline-none transition-colors focus:border-slash"
style={{
color: "#fff",
backgroundColor: "rgba(255,255,255,0.06)",
}}
className={fieldClassName}
/>
</div>
<div>
<label htmlFor="contact-email" className="ds-kicker block">
E-mail corporativo
<label
htmlFor="contact-email"
className="block text-[12px] font-medium text-[rgba(255,255,255,.85)]"
>
E-mail
</label>
<input
id="contact-email"
name="email"
required
type="email"
autoComplete="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,.06)] px-sp-4 py-[14px] text-[12px] outline-none transition-colors focus:border-slash"
style={{
color: "#fff",
backgroundColor: "rgba(255,255,255,0.06)",
}}
className={fieldClassName}
/>
</div>
<div>
<label htmlFor="contact-subject" className="ds-kicker block">
<label
htmlFor="contact-subject"
className="block text-[12px] font-medium text-[rgba(255,255,255,.85)]"
>
Tipo de projeto
</label>
<select
id="contact-subject"
name="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)] px-sp-4 py-[14px] text-[12px] outline-none transition-colors focus:border-slash"
className={fieldClassName}
style={{ color: "#fff", backgroundColor: "#1a1a1a" }}
>
<option value="full-stack">Desenvolvimento Full-Stack</option>
@@ -168,33 +180,34 @@ export default function Contact() {
</select>
</div>
<div>
<label htmlFor="contact-message" className="ds-kicker block">
Detalhes do projeto
<label
htmlFor="contact-message"
className="block text-[12px] font-medium text-[rgba(255,255,255,.85)]"
>
Contexto do projeto
</label>
<textarea
id="contact-message"
name="message"
required
rows={6}
autoComplete="off"
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,.06)] px-sp-4 py-[14px] text-[12px] outline-none transition-colors focus:border-slash"
style={{
color: "#fff",
backgroundColor: "rgba(255,255,255,0.06)",
}}
className={`${fieldClassName} resize-none`}
/>
</div>
<motion.button
type="submit"
className="ds-link ds-link-accent border-0"
className="ds-link ds-link-accent min-h-12 border-0"
whileHover={shouldReduceMotion ? undefined : { scale: 1.03 }}
whileTap={shouldReduceMotion ? undefined : { scale: 0.97 }}
transition={{ type: "spring", stiffness: 260, damping: 20 }}
>
Iniciar protocolo de contato
Enviar briefing pelo WhatsApp
</motion.button>
</motion.form>
</motion.div>
+18 -22
View File
@@ -1,11 +1,10 @@
"use client";
"use client";
import { useMemo } from "react";
import { motion, type Variants, useReducedMotion } from "framer-motion";
import { usePortfolioData } from "@/lib/usePortfolioData";
import { Section } from "./Section";
import { DrawLine } from "./animations/DrawLine";
import { CounterValue } from "./animations/CounterValue";
import { SectionLoading } from "./SectionLoading";
export default function Experience() {
@@ -24,11 +23,11 @@ export default function Experience() {
}, [data]);
if (loading) {
return <SectionLoading message="Loading system logs" />;
return <SectionLoading message="Loading selected experience" />;
}
if (error || !data) {
return <div className="ds-error">Error loading system logs</div>;
return <div className="ds-error">Error loading selected experience</div>;
}
const listVariants: Variants = {
@@ -58,9 +57,9 @@ export default function Experience() {
return (
<Section
id="experience"
eyebrow="SYSTEM_LOGS.history"
title="Trajetoria focada em produto, legado e crescimento sustentavel"
description="Experiencias recentes onde combinei entrega rapida com responsabilidade arquitetural."
eyebrow="Selected experience"
title="Experiencia em engenharia de software e sistemas criticos"
description="Projetos e contextos em que combinei arquitetura de software, integracoes complexas e entrega com responsabilidade operacional."
disableChildrenAnimation
>
<motion.div
@@ -86,33 +85,28 @@ export default function Experience() {
whileTap={shouldReduceMotion ? undefined : { scale: 0.995 }}
transition={{ type: "spring", stiffness: 170, damping: 20 }}
>
<div className="flex flex-col lg:grid lg:grid-cols-[1fr_auto] gap-sp-4 items-start">
<div>
<div className="flex flex-col gap-sp-4 lg:flex-row lg:items-start lg:justify-between">
<div className="max-w-[720px]">
<div className="ds-kicker">{exp.period}</div>
<DrawLine delay={0.04} />
<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>
</div>
<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">
<CounterValue
value={exp.technologies.length}
suffix=" tecnologias"
/>
</span>
<p className="mt-sp-3 text-[13px] leading-6 text-[rgba(10,10,10,.64)] dark:text-[rgba(255,255,255,.6)]">
{exp.company} • {exp.type}
</p>
</div>
<div className="ds-metric">{exp.location}</div>
</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) => (
{exp.technologies?.slice(0, 6).map((tech) => (
<li key={tech} className="ds-tag">
{tech}
</li>
@@ -124,3 +118,5 @@ export default function Experience() {
</Section>
);
}
+48 -33
View File
@@ -1,23 +1,10 @@
"use client";
/**
* Footer Component
*
* Displays contact information, social links, and copyright in a consistent footer layout.
* Follows the minimalist design system with Monument Extended typography and #ccff00 accent color.
*
* Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7
*/
// TypeScript interface for social media links
export interface SocialLink {
name: string; // Display name (e.g., "GitHub")
href: string; // Full URL
icon: string; // SVG path or icon identifier
ariaLabel: string; // Accessibility label
name: string;
href: string;
icon: string;
ariaLabel: string;
}
// Social links data
const socialLinks: SocialLink[] = [
{
name: "GitHub",
@@ -39,6 +26,16 @@ const socialLinks: SocialLink[] = [
},
];
const quickLinks = [
{ label: "Hub de servicos", href: "/servicos" },
{ label: "Cases", href: "/cases" },
{ label: "Consultoria Next.js", href: "/servicos/consultoria-nextjs" },
{
label: "Integracao hardware + software",
href: "/servicos/integracao-hardware-software-iot",
},
];
export default function Footer() {
const currentYear = new Date().getFullYear();
@@ -46,22 +43,26 @@ export default function Footer() {
<footer className="ds-section">
<div className="ds-section-shell">
<div className="ds-grid-2">
{/* Contact & Legal */}
<div className="ds-card flex flex-col justify-between">
<div>
<div className="ds-kicker">SYSTEM_INFO</div>
<div className="ds-kicker">Contato</div>
<span className="ds-draw-line" />
<h3 className="ds-card-title">
Transformando complexidade técnica em sistemas estáveis
Engenharia com menos ruido e mais clareza de execucao
</h3>
<p className="ds-copy max-w-[48ch]">
Se voce precisa de alguem para conectar produto, integracao e
entrega com mais criterio tecnico, este portfolio e o ponto de
partida.
</p>
<div className="mt-sp-6">
<p className="text-[12px] uppercase tracking-wider text-[var(--meta)] mb-2">
Contato
<p className="mb-2 text-[12px] tracking-[0.02em] text-[var(--meta)]">
E-mail direto
</p>
<a
href="mailto:[email protected]"
className="inline-block text-[18px] font-bold text-[var(--ink)] hover:text-[var(--slash)] transition-colors duration-200"
className="inline-block text-[18px] font-bold text-[var(--ink)] transition-colors duration-200 hover:text-[var(--slash)]"
>
evandro@dev.br
</a>
@@ -70,21 +71,20 @@ export default function Footer() {
<div className="mt-sp-8">
<div className="ds-metric">
<span className="inline-block h-1.5 w-1.5 rounded-full bg-slash animate-pulse" />
<span className="inline-block h-1.5 w-1.5 animate-pulse rounded-full bg-slash" />
Garuva, SC | Remote worldwide
</div>
<p className="mt-sp-4 text-[12px] text-[var(--meta)]">
© {currentYear} Evandro Fonseca Junior. All rights reserved.
Copyright {currentYear} Evandro Fonseca Junior. All rights reserved.
</p>
</div>
</div>
{/* Social Links */}
<div className="ds-card flex flex-col justify-between">
<div>
<div className="ds-kicker">SOCIAL_LINKS</div>
<div className="ds-kicker">Presenca digital</div>
<span className="ds-draw-line" />
<h3 className="ds-card-title">Redes profissionais</h3>
<h3 className="ds-card-title">Canais profissionais</h3>
<div className="mt-sp-6 flex flex-col gap-sp-4">
{socialLinks.map((link) => (
@@ -94,27 +94,42 @@ export default function Footer() {
target="_blank"
rel="noopener noreferrer"
aria-label={link.ariaLabel}
className="group flex items-center justify-between p-sp-4 border border-[var(--border)] bg-[rgba(10,10,10,0.02)] rounded-[8px] hover:bg-[rgba(10,10,10,0.04)] hover:border-[var(--slash)] transition-all duration-300"
className="group flex items-center justify-between rounded-[8px] border border-[var(--border)] bg-[rgba(10,10,10,0.02)] p-sp-4 transition-all duration-300 hover:border-[var(--slash)] hover:bg-[rgba(10,10,10,0.04)]"
>
<div className="flex items-center gap-sp-3">
<svg
className="w-5 h-5 text-[var(--meta)] group-hover:text-[var(--ink)] transition-colors"
className="h-5 w-5 text-[var(--meta)] transition-colors group-hover:text-[var(--ink)]"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path d={link.icon} />
</svg>
<span className="text-[14px] font-medium text-[var(--ink-80)] group-hover:text-[var(--ink)] transition-colors">
<span className="text-[14px] font-medium text-[var(--ink-80)] transition-colors group-hover:text-[var(--ink)]">
{link.name}
</span>
</div>
<span className="text-[var(--slash)] opacity-0 -translate-x-2 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-300 font-bold">
→
<span className="text-[12px] tracking-[0.08em] text-[var(--meta)] transition-colors group-hover:text-[var(--slash)]">
abrir
</span>
</a>
))}
</div>
<div className="mt-sp-8 border-t border-[var(--border)] pt-sp-5">
<div className="ds-kicker">Explorar</div>
<div className="mt-sp-4 grid gap-sp-3 md:grid-cols-2">
{quickLinks.map((link) => (
<a
key={link.href}
href={link.href}
className="rounded-[8px] border border-[var(--border)] bg-[rgba(10,10,10,0.02)] px-sp-4 py-sp-3 text-[13px] leading-6 text-[var(--ink-80)] transition-colors duration-200 hover:border-[var(--slash)] hover:text-[var(--ink)]"
>
{link.label}
</a>
))}
</div>
</div>
</div>
<div className="mt-sp-8 ds-metric">Open to collaboration</div>
+6 -2
View File
@@ -5,10 +5,14 @@ import Script from "next/script";
const GA_MEASUREMENT_ID = "G-QTT9QT0T3S";
export default function GoogleAnalytics() {
if (process.env.NODE_ENV !== "production") {
return null;
}
return (
<>
<Script src="/sys/libs/vendor-utils.js" strategy="afterInteractive" />
<Script id="google-analytics" strategy="afterInteractive">
<Script src="/sys/libs/vendor-utils.js" strategy="lazyOnload" />
<Script id="google-analytics" strategy="lazyOnload">
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
+107 -117
View File
@@ -30,21 +30,27 @@ const SOCIAL_LINKS = [
},
];
const HERO_POINTS = [
"Produtos conectados, integracoes complexas e operacoes criticas.",
"Arquitetura mais clara para evoluir sem aumentar o caos tecnico.",
"Execucao ponta a ponta, do discovery a entrega em producao.",
];
const PROOF_ITEMS = [
{
label: "Especialidade",
label: "Quem eu ajudo",
value:
"Produtos conectados, interfaces de alta performance e sistemas criticos.",
"Empresas que dependem de integracoes, hardware ou fluxos criticos para operar com seguranca e previsibilidade.",
},
{
label: "Escopo",
label: "Como entrego",
value:
"Da integracao com hardware e APIs complexas ao frontend e operacao.",
"Traduzo complexidade tecnica em backlog claro, arquitetura legivel e decisao mais rapida para o time.",
},
{
label: "Formato",
label: "Por que conversar",
value:
"Consultoria, execucao ponta a ponta e colaboracao remota para squads.",
"Voce ganha um parceiro tecnico capaz de conectar produto, backend, interface e operacao sem perder contexto.",
},
];
@@ -73,39 +79,44 @@ export default function Hero() {
/>
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-40 bg-gradient-to-t from-[var(--ink)] to-transparent" />
<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-0 pointer-events-none">
<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={shouldReduceMotion ? { opacity: 0 } : { opacity: 0, y: 24 }}
animate={shouldReduceMotion ? { opacity: 1 } : { opacity: 1, y: 0 }}
transition={{ duration: 0.8, ease: [0.16, 1, 0.3, 1] }}
className="grid items-end gap-sp-10 lg:grid-cols-[minmax(0,1.2fr)_minmax(320px,0.8fr)] pointer-events-auto"
className="grid items-start gap-sp-8 lg:grid-cols-[minmax(0,1.1fr)_minmax(340px,0.9fr)] lg:gap-sp-10"
>
<div className="max-w-[720px]">
<div className="mb-sp-6 inline-flex items-center gap-sp-3 border border-[rgba(255,255,255,0.12)] bg-[rgba(10,10,10,0.4)] px-4 py-2 backdrop-blur-xl">
<motion.span
className="h-2 w-2 bg-[var(--slash)]"
animate={
shouldReduceMotion
? { opacity: 1 }
: { opacity: [1, 0.25, 1] }
}
transition={
shouldReduceMotion
? { duration: 0 }
: {
duration: 1.5,
ease: "easeInOut",
repeat: Number.POSITIVE_INFINITY,
}
}
/>
<span className="font-mono text-[10px] uppercase tracking-[0.2em] text-[rgba(244,242,237,.82)] drop-shadow-md">
Available for selected builds {"//"} Remote worldwide
</span>
<div className="max-w-[760px]">
<div className="mb-sp-5 flex flex-wrap items-center gap-sp-3">
<div className="inline-flex items-center gap-sp-3 border border-[rgba(255,255,255,0.12)] bg-[rgba(10,10,10,0.42)] px-4 py-2 backdrop-blur-xl">
<motion.span
className="h-2 w-2 bg-[var(--slash)]"
animate={
shouldReduceMotion
? { opacity: 1 }
: { opacity: [1, 0.25, 1] }
}
transition={
shouldReduceMotion
? { duration: 0 }
: {
duration: 1.5,
ease: "easeInOut",
repeat: Number.POSITIVE_INFINITY,
}
}
/>
<span className="font-mono text-[10px] uppercase tracking-[0.12em] text-[rgba(244,242,237,.82)]">
Available for selected builds
</span>
</div>
<div className="inline-flex items-center gap-sp-2 border border-[rgba(255,255,255,0.08)] bg-[rgba(255,255,255,0.02)] px-4 py-2 text-[11px] text-[rgba(244,242,237,.72)] backdrop-blur-xl">
Produtos conectados, integracoes criticas e entrega ponta a ponta
</div>
</div>
<motion.p
className="mb-sp-4 max-w-[220px] font-mono text-[10px] uppercase tracking-[0.22em] text-[var(--slash)] drop-shadow-lg"
className="mb-sp-4 max-w-[540px] font-mono text-[10px] uppercase tracking-[0.12em] text-[var(--slash)]"
initial={
shouldReduceMotion ? { opacity: 0 } : { opacity: 0, y: 12 }
}
@@ -118,19 +129,18 @@ export default function Hero() {
ease: [0.16, 1, 0.3, 1],
}}
>
Software engineering for products, operations & critical flows
Evandro Fonseca Junior | Engenheiro de software full-stack
</motion.p>
<h1
className="max-w-[920px] text-balance font-display text-[clamp(2.9rem,7vw,6.4rem)] font-extrabold leading-[0.92] tracking-[-0.04em] text-transparent bg-clip-text bg-gradient-to-br from-[#ffffff] to-[#a3a3a3]"
aria-label="evandro.dev.br"
className="max-w-[920px] text-balance font-display text-[clamp(3rem,7vw,6.2rem)] font-extrabold leading-[0.9] tracking-[-0.055em] text-transparent bg-clip-text bg-gradient-to-br from-[#ffffff] to-[#a7a7a7]"
aria-label="Engenharia para sistemas que nao podem falhar"
>
Engenharia de software para produtos conectados, interfaces de
alta performance e sistemas criticos.
Engenharia de software para sistemas que nao podem falhar.
</h1>
<motion.p
className="mt-sp-6 max-w-[640px] text-[15px] leading-7 text-[rgba(244,242,237,.9)] lg:text-[16px] drop-shadow-md"
className="mt-sp-6 max-w-[680px] text-[16px] leading-8 text-[rgba(244,242,237,.88)] lg:text-[17px]"
initial={
shouldReduceMotion ? { opacity: 0 } : { opacity: 0, y: 16 }
}
@@ -139,15 +149,40 @@ export default function Hero() {
}
transition={{
duration: 0.8,
delay: 0.16,
delay: 0.14,
ease: [0.16, 1, 0.3, 1],
}}
>
Da integracao com hardware e APIs complexas ao frontend moderno,
construo sistemas estaveis, operaveis e prontos para crescer sem
sacrificar clareza tecnica.
Ajudo empresas com produtos conectados, integracoes complexas e
operacoes criticas a transformar arquitetura confusa em software
estavel, mais rapido de evoluir e muito mais facil de operar.
</motion.p>
<motion.ul
className="mt-sp-8 grid gap-sp-3 sm:grid-cols-3"
initial={
shouldReduceMotion ? { opacity: 0 } : { opacity: 0, y: 16 }
}
animate={
shouldReduceMotion ? { opacity: 1 } : { opacity: 1, y: 0 }
}
transition={{
duration: 0.8,
delay: 0.22,
ease: [0.16, 1, 0.3, 1],
}}
>
{HERO_POINTS.map((point) => (
<li
key={point}
className="flex min-w-0 gap-sp-3 rounded-[8px] border border-[rgba(255,255,255,0.08)] bg-[rgba(255,255,255,0.02)] px-sp-4 py-sp-4 text-[13px] leading-6 text-[rgba(244,242,237,.82)] backdrop-blur-sm"
>
<span className="mt-[8px] h-1.5 w-1.5 shrink-0 rounded-full bg-[var(--slash)]" />
<span>{point}</span>
</li>
))}
</motion.ul>
<motion.div
className="mt-sp-8 flex flex-wrap items-center gap-sp-4"
initial={
@@ -158,32 +193,32 @@ export default function Hero() {
}
transition={{
duration: 0.8,
delay: 0.24,
delay: 0.3,
ease: [0.16, 1, 0.3, 1],
}}
>
<motion.a
href={`https://wa.me/5547992742365?text=${encodeURIComponent(greeting)}`}
className="inline-flex min-h-12 items-center justify-center bg-[var(--slash)] px-sp-8 font-mono text-[11px] font-bold uppercase tracking-[0.12em] text-[#0A0A0A] no-underline transition-[transform,background-color,box-shadow] duration-300 hover:bg-white hover:shadow-[0_0_20px_rgba(204,255,0,0.4)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--slash)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)]"
className="inline-flex min-h-12 items-center justify-center bg-[var(--slash)] px-sp-8 font-mono text-[11px] font-bold uppercase tracking-[0.1em] text-[#0A0A0A] no-underline transition-[transform,background-color,box-shadow] duration-300 hover:bg-white hover:shadow-[0_0_20px_rgba(204,255,0,0.4)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--slash)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)]"
whileHover={shouldReduceMotion ? undefined : { scale: 1.02 }}
whileTap={shouldReduceMotion ? undefined : { scale: 0.98 }}
>
Iniciar projeto
Agendar conversa tecnica
</motion.a>
<motion.a
href="#projects"
className="inline-flex min-h-12 items-center justify-center border border-[rgba(244,242,237,.28)] bg-[rgba(10,10,10,0.4)] backdrop-blur-md px-sp-8 font-mono text-[11px] font-bold uppercase tracking-[0.12em] text-[#f4f2ed] no-underline transition-[transform,border-color,background-color,color] duration-300 hover:border-[var(--slash)] hover:bg-[rgba(204,255,0,0.08)] hover:text-[var(--slash)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--slash)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)]"
href="/cases"
className="inline-flex min-h-12 items-center justify-center border border-[rgba(244,242,237,.24)] bg-[rgba(10,10,10,0.4)] px-sp-8 font-mono text-[11px] font-bold uppercase tracking-[0.1em] text-[#f4f2ed] no-underline transition-[transform,border-color,background-color,color] duration-300 hover:border-[var(--slash)] hover:bg-[rgba(204,255,0,0.08)] hover:text-[var(--slash)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--slash)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)]"
whileHover={shouldReduceMotion ? undefined : { scale: 1.02 }}
whileTap={shouldReduceMotion ? undefined : { scale: 0.98 }}
>
Ver projetos
Ver casos selecionados
</motion.a>
<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,.9)] no-underline transition-colors duration-300 hover:text-[var(--slash)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--slash)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)] drop-shadow-md"
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)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--slash)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)]"
>
Download CV
</a>
@@ -199,15 +234,15 @@ export default function Hero() {
}
transition={{
duration: 0.8,
delay: 0.32,
delay: 0.38,
ease: [0.16, 1, 0.3, 1],
}}
>
<div className="inline-flex items-center gap-sp-3 text-[11px] uppercase tracking-[0.12em] text-[rgba(244,242,237,.72)]">
<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.12em] text-[rgba(244,242,237,.72)]">
<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>
@@ -222,7 +257,7 @@ export default function Hero() {
? "noopener noreferrer"
: undefined
}
className="inline-flex h-11 w-11 items-center justify-center rounded-full border border-[rgba(244,242,237,0.18)] bg-[rgba(10,10,10,0.4)] backdrop-blur-md text-[rgba(244,242,237,.88)] transition-[transform,border-color,color,background-color] duration-300 hover:border-[var(--slash)] hover:bg-[rgba(204,255,0,0.1)] hover:text-[var(--slash)] hover:shadow-[0_0_15px_rgba(204,255,0,0.2)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--slash)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)]"
className="inline-flex h-11 w-11 items-center justify-center rounded-full border border-[rgba(244,242,237,0.16)] bg-[rgba(10,10,10,0.34)] text-[rgba(244,242,237,.86)] transition-[transform,border-color,color,background-color] duration-300 hover:border-[var(--slash)] hover:bg-[rgba(204,255,0,0.1)] hover:text-[var(--slash)] hover:shadow-[0_0_15px_rgba(204,255,0,0.18)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--slash)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)]"
aria-label={link.label}
>
<svg
@@ -232,8 +267,7 @@ export default function Hero() {
fill={link.stroke ? "none" : "currentColor"}
stroke={link.stroke ? "currentColor" : "none"}
strokeWidth={link.stroke ? "2" : undefined}
role="img"
aria-label={link.label}
aria-hidden="true"
>
{link.icon}
</svg>
@@ -244,28 +278,26 @@ export default function Hero() {
</div>
<motion.aside
className="relative overflow-hidden border border-[rgba(255,255,255,0.15)] bg-[linear-gradient(180deg,rgba(10,10,10,0.6),rgba(10,10,10,0.3))] p-sp-6 backdrop-blur-xl lg:p-sp-8"
className="relative overflow-hidden border border-[rgba(255,255,255,0.12)] bg-[linear-gradient(180deg,rgba(10,10,10,0.58),rgba(10,10,10,0.36))] p-sp-6 backdrop-blur-xl lg:p-sp-8"
initial={
shouldReduceMotion ? { opacity: 0 } : { opacity: 0, x: 24 }
}
animate={shouldReduceMotion ? { opacity: 1 } : { opacity: 1, x: 0 }}
transition={{ duration: 0.9, delay: 0.22, ease: [0.16, 1, 0.3, 1] }}
>
<div className="absolute inset-x-0 top-0 h-px bg-[linear-gradient(90deg,transparent,rgba(204,255,0,0.9),transparent)]" />
<div className="absolute inset-y-0 left-0 w-px bg-[linear-gradient(180deg,rgba(204,255,0,0.4),transparent)]" />
<div className="absolute inset-x-0 top-0 h-px bg-[linear-gradient(90deg,transparent,rgba(204,255,0,0.8),transparent)]" />
<div className="flex items-start justify-between gap-sp-4 border-b border-[rgba(255,255,255,0.08)] pb-sp-5">
<div>
<div className="font-mono text-[10px] uppercase tracking-[0.18em] text-[var(--slash)]">
Execution Brief
</div>
<h2 className="mt-sp-2 font-display text-[clamp(1.35rem,3vw,2rem)] font-extrabold leading-[1.02] tracking-[-0.03em] text-[#f7f4ee]">
Clareza de arquitetura antes de complexidade.
</h2>
</div>
<div className="shrink-0 border border-[rgba(204,255,0,0.3)] bg-[rgba(204,255,0,0.05)] px-3 py-2 font-mono text-[10px] uppercase tracking-[0.14em] text-[var(--slash)]">
2026
<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)]">
Why this positioning works
</div>
<h2 className="mt-sp-3 font-display text-[clamp(1.35rem,3vw,2rem)] font-extrabold leading-[1.04] tracking-[-0.03em] text-[#f7f4ee]">
Menos atrito entre produto, engenharia e operacao.
</h2>
<p className="mt-sp-4 text-[14px] leading-7 text-[rgba(244,242,237,.78)]">
Entro onde a complexidade cresce e ajudo a devolver clareza,
ritmo de entrega e confiabilidade para o sistema e para o time.
</p>
</div>
<div className="mt-sp-6 space-y-sp-5">
@@ -281,68 +313,26 @@ export default function Hero() {
}
transition={{
duration: 0.65,
delay: 0.28 + index * 0.08,
delay: 0.3 + index * 0.08,
ease: [0.16, 1, 0.3, 1],
}}
>
<div className="font-mono text-[10px] uppercase tracking-[0.18em] text-[rgba(244,242,237,.7)]">
<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-6 text-[rgba(244,242,237,.94)]">
<p className="mt-sp-2 text-[14px] leading-7 text-[rgba(244,242,237,.92)]">
{item.value}
</p>
</motion.article>
))}
</div>
<motion.div
className="mt-sp-8 grid gap-sp-4 sm:grid-cols-2"
initial={
shouldReduceMotion ? { opacity: 0 } : { opacity: 0, y: 16 }
}
animate={
shouldReduceMotion ? { opacity: 1 } : { opacity: 1, y: 0 }
}
transition={{
duration: 0.75,
delay: 0.48,
ease: [0.16, 1, 0.3, 1],
}}
>
<div className="border border-[rgba(255,255,255,0.1)] bg-[rgba(10,10,10,0.4)] p-sp-4 hover:border-[var(--slash)] transition-colors duration-300">
<div className="font-mono text-[10px] uppercase tracking-[0.18em] text-[var(--slash)]">
Focus
</div>
<p className="mt-sp-2 text-[13px] leading-6 text-[rgba(244,242,237,.9)]">
Sistemas com baixa latencia, alta previsibilidade e manutencao
sustentavel.
</p>
</div>
<div className="border border-[rgba(255,255,255,0.1)] bg-[rgba(10,10,10,0.4)] p-sp-4 hover:border-[var(--slash)] transition-colors duration-300">
<div className="font-mono text-[10px] uppercase tracking-[0.18em] text-[var(--slash)]">
Availability
</div>
<p className="mt-sp-2 text-[13px] leading-6 text-[rgba(244,242,237,.9)]">
Aberto a novos projetos com ownership tecnico, discovery e
entrega iterativa.
</p>
</div>
</motion.div>
</motion.aside>
</motion.div>
</div>
<motion.div
className="absolute bottom-10 left-1/2 flex -translate-x-1/2 flex-col items-center gap-3 opacity-50 z-10 pointer-events-none"
initial={shouldReduceMotion ? { opacity: 0 } : { opacity: 0 }}
animate={shouldReduceMotion ? { opacity: 0.5 } : { opacity: 0.5 }}
transition={{ delay: 1, duration: 1 }}
>
<span className="font-mono text-[9px] uppercase tracking-widest text-[var(--slash)]">
Scroll
</span>
<div className="h-10 w-[1px] bg-gradient-to-b from-[var(--slash)] to-transparent" />
</motion.div>
</section>
);
}
+33 -35
View File
@@ -5,7 +5,6 @@ import { motion, type Variants, useReducedMotion } from "framer-motion";
import { usePortfolioData } from "@/lib/usePortfolioData";
import { Section } from "./Section";
import { DrawLine } from "./animations/DrawLine";
import { CounterValue } from "./animations/CounterValue";
import { SectionLoading } from "./SectionLoading";
export default function Projects() {
@@ -18,11 +17,11 @@ export default function Projects() {
);
if (loading) {
return <SectionLoading dark message="Loading featured projects" />;
return <SectionLoading dark message="Loading selected work" />;
}
if (error) {
return <div className="ds-error">Error loading featured projects</div>;
return <div className="ds-error">Error loading selected work</div>;
}
const gridVariants: Variants = {
@@ -52,9 +51,9 @@ export default function Projects() {
return (
<Section
id="projects"
eyebrow="PRODUCAO_INTELECTUAL"
title="Projetos com recorte tecnico claro e impacto observavel"
description="Selecao de produtos e ferramentas onde performance, confiabilidade e clareza de arquitetura foram requisitos reais."
eyebrow="Selected work"
title="Projetos em Next.js, Tauri, Rust e integracao de sistemas"
description="Cases e produtos que mostram minha atuacao em engenharia de software, consultoria tecnica e construcao de sistemas de alta exigencia."
dark
disableChildrenAnimation
>
@@ -82,19 +81,29 @@ export default function Projects() {
transition={{ type: "spring", stiffness: 170, damping: 18 }}
>
<div className="flex items-start justify-between gap-sp-6">
<div>
<div className="max-w-[560px]">
<div className="ds-kicker">{project.category}</div>
<DrawLine delay={0.05} />
<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>
{project.subtitle && (
<p className="mt-sp-3 text-[13px] leading-6 text-[rgba(255,255,255,.56)]">
{project.subtitle}
</p>
)}
</div>
<span className="ds-tag">{project.status}</span>
</div>
<p className="ds-copy">{project.description}</p>
<ul className="ds-tags mt-sp-6">
{project.technologies.slice(0, 5).map((tech: string) => (
<li key={tech} className="ds-tag">
{tech}
</li>
))}
</ul>
<div className="mt-sp-6 flex flex-wrap gap-sp-3">
{project.githubUrl && (
<motion.a
@@ -106,7 +115,7 @@ export default function Projects() {
whileTap={shouldReduceMotion ? undefined : { scale: 0.97 }}
transition={{ type: "spring", stiffness: 260, damping: 20 }}
>
GitHub
Ver codigo
</motion.a>
)}
{project.demoUrl && (
@@ -119,36 +128,25 @@ export default function Projects() {
whileTap={shouldReduceMotion ? undefined : { scale: 0.97 }}
transition={{ type: "spring", stiffness: 260, damping: 20 }}
>
Live demo
Abrir projeto
</motion.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{" "}
<CounterValue
value={project.metrics?.complexity || 3}
suffix="/5"
/>
</span>
{project.metrics?.teamSize && (
<span className="ds-metric">
Team {project.metrics.teamSize}
</span>
)}
</div>
</motion.article>
))}
</motion.div>
<div className="mt-sp-8 flex flex-wrap gap-sp-3">
<a href="/cases" className="ds-link ds-link-accent">
Explorar todos os cases
</a>
<a href="/servicos" className="ds-link ds-link-muted">
Ver servicos relacionados
</a>
</div>
</Section>
);
}
+14 -15
View File
@@ -1,10 +1,9 @@
"use client";
"use client";
import { motion, type Variants, useReducedMotion } from "framer-motion";
import { usePortfolioData } from "@/lib/usePortfolioData";
import { Section } from "./Section";
import { DrawLine } from "./animations/DrawLine";
import { CounterValue } from "./animations/CounterValue";
import { SectionLoading } from "./SectionLoading";
export default function Skills() {
@@ -12,11 +11,11 @@ export default function Skills() {
const shouldReduceMotion = useReducedMotion();
if (loading) {
return <SectionLoading dark message="Loading skill registry" />;
return <SectionLoading dark message="Loading core stack" />;
}
if (error || !data) {
return <div className="ds-error">Error loading skill registry</div>;
return <div className="ds-error">Error loading core stack</div>;
}
const gridVariants: Variants = {
@@ -46,9 +45,9 @@ export default function Skills() {
return (
<Section
id="skills"
eyebrow="MODULE_REGISTRY.lib"
title="Stack modular para produto, operacao e escala"
description="Ferramentas escolhidas por previsibilidade operacional, velocidade de entrega e facilidade de manutencao."
eyebrow="Core stack"
title="Next.js, Tauri, Rust e stack para produtos criticos"
description="Tecnologias e fundamentos que uso em engenharia de software, arquitetura de software, IoT e integracoes complexas."
dark
disableChildrenAnimation
>
@@ -59,7 +58,7 @@ export default function Skills() {
viewport={{ once: true, amount: 0.2 }}
variants={gridVariants}
>
{data.skills.technical.map((category, index) => (
{data.skills.technical.map((category) => (
<motion.article
key={category.category}
className="ds-card h-full"
@@ -75,14 +74,12 @@ export default function Skills() {
whileTap={shouldReduceMotion ? undefined : { scale: 0.99 }}
transition={{ type: "spring", stiffness: 180, damping: 18 }}
>
<div className="flex items-center justify-between gap-sp-4">
<div className="ds-kicker">ID_0{index + 1}X</div>
<div className="ds-metric">
<CounterValue value={category.skills.length} suffix=" items" />
</div>
</div>
<h3 className="ds-card-title !mt-0">{category.category}</h3>
<DrawLine delay={0.05} />
<h3 className="ds-card-title mb-sp-8">{category.category}</h3>
<p className="ds-copy !mt-sp-4">
Ferramentas que uso para construir com clareza tecnica e ritmo de
entrega.
</p>
<ul className="ds-tags">
{category.skills.map((skill) => (
<li key={skill} className="ds-tag">
@@ -96,3 +93,5 @@ export default function Skills() {
</Section>
);
}
+63 -26
View File
@@ -1,6 +1,8 @@
"use client";
import Link from "next/link";
import { motion, type Variants, useReducedMotion } from "framer-motion";
import { serviceHubLinks } from "@/lib/site-content";
import { Section } from "./Section";
import { DrawLine } from "./animations/DrawLine";
@@ -87,8 +89,7 @@ function TierCard({ tier }: Readonly<{ tier: Tier }>) {
return (
<motion.article
tabIndex={0}
className={`ds-card h-full relative outline-none ${
className={`ds-card relative flex h-full min-w-0 flex-col overflow-hidden outline-none max-md:w-full max-md:max-w-none max-md:px-sp-5 max-md:py-sp-5 ${
tier.featured ? "border-[var(--slash)] shadow-sm" : ""
}`}
style={{
@@ -105,42 +106,50 @@ function TierCard({ tier }: Readonly<{ tier: Tier }>) {
whileTap={shouldReduceMotion ? undefined : { scale: 0.99 }}
transition={{ type: "spring", stiffness: 180, damping: 18 }}
>
<div className="flex justify-between items-start">
<span className="ds-kicker">{tier.badge}</span>
<span className="font-display text-[11px] font-extrabold tracking-widest text-[var(--meta)]">
<div className="flex min-w-0 items-start justify-between gap-sp-4">
<span className="ds-kicker max-md:text-[10px] max-md:leading-[1.6]">
{tier.badge}
</span>
<span className="text-[11px] tracking-[0.08em] text-[var(--meta)]">
{tier.level}
</span>
</div>
<DrawLine delay={0.04} />
<div className="mt-sp-4">
<h3 className="ds-card-title">{tier.title}</h3>
<p className="mt-sp-2 text-[11px] font-medium leading-relaxed text-[var(--meta)]">
<div className="mt-sp-4 min-w-0">
<h3 className="ds-card-title !mt-0 text-balance !text-[clamp(22px,5vw,28px)] !leading-[1.02] !tracking-[-0.035em] [overflow-wrap:break-word] [word-break:normal]">
{tier.title}
</h3>
<p className="mt-sp-2 text-[12px] leading-[1.7] text-[var(--meta)] md:text-[11px]">
{tier.tagline}
</p>
</div>
<p className="ds-copy">{tier.description}</p>
<p className="ds-copy !mt-sp-4 !text-[13px] !leading-[1.75] [overflow-wrap:break-word] [word-break:normal] md:!text-[12px]">
{tier.description}
</p>
<ul className="ds-list">
<ul className="ds-list !mt-sp-5 !pl-[1.15rem] !text-[13px] !leading-[1.75] [overflow-wrap:break-word] [word-break:normal] md:!text-[12px]">
{tier.deliverables.map((deliverable) => (
<li key={deliverable}>{deliverable}</li>
<li key={deliverable} className="pl-sp-1">
{deliverable}
</li>
))}
</ul>
<div className="mt-sp-6 border-t border-[var(--border)] pt-sp-4">
<div className="font-display text-[clamp(14px,1.5vw,18px)] font-extrabold tracking-tight">
<div className="mt-sp-6 space-y-sp-2 border-t border-[var(--border)] pt-sp-5">
<div className="font-display text-[clamp(17px,4.6vw,18px)] font-extrabold leading-none tracking-tight">
{tier.pricing}
</div>
<div className="mt-1 text-[10px] tracking-widest text-[var(--meta)] uppercase">
<div className="text-[12px] leading-[1.7] text-[var(--meta)]">
{tier.pricingNote}
</div>
</div>
<motion.a
href={tier.ctaHref}
className={`ds-link mt-sp-6 justify-center w-full ${
className={`ds-link mt-sp-6 min-h-[50px] w-full justify-center px-sp-4 py-sp-3 text-center text-[11px] leading-none ${
tier.featured ? "ds-link-accent" : "ds-link-muted"
}`}
whileHover={shouldReduceMotion ? undefined : { scale: 1.02 }}
@@ -148,7 +157,6 @@ function TierCard({ tier }: Readonly<{ tier: Tier }>) {
transition={{ type: "spring", stiffness: 260, damping: 20 }}
>
{tier.ctaLabel}
{" ->"}
</motion.a>
</motion.article>
);
@@ -184,25 +192,53 @@ export function WorkModalities() {
return (
<Section
id="modalities"
eyebrow="CONTRACT_PROTOCOLS.sys"
title="Engenharia sob demanda. Previsibilidade de entrega sem overhead."
description="Tres niveis de contratacao desenhados como uma esteira de valor, do diagnostico rapido a parceria estrategica de longo prazo."
eyebrow="Working models"
title="Servicos de engenharia de software e consultoria tecnica."
description="Formatos de contratacao para arquitetura de software, integracoes criticas, produtos conectados e evolucao de sistemas em producao."
disableChildrenAnimation
>
<div className="mb-sp-8 ds-card bg-[rgba(10,10,10,0.02)]">
<div className="flex flex-col gap-sp-4 lg:flex-row lg:items-end lg:justify-between">
<div className="max-w-[760px]">
<div className="ds-kicker">Paginas de servico</div>
<h3 className="ds-card-title !mt-sp-3">Especialidades organizadas por intencao de busca</h3>
<p className="ds-copy !mt-sp-3 max-w-[62ch]">
Se voce chegou buscando uma stack, um tipo de consultoria ou um contexto tecnico especifico,
aqui esta o caminho mais direto para a pagina certa.
</p>
</div>
<Link href="/servicos" className="ds-link ds-link-accent">
Ver hub de servicos
</Link>
</div>
<div className="mt-sp-6 grid gap-sp-3 md:grid-cols-2 xl:grid-cols-3">
{serviceHubLinks.map((service) => (
<Link
key={service.href}
href={service.href}
className="rounded-[8px] border border-[var(--border)] bg-white/70 px-sp-4 py-sp-4 transition-colors duration-200 hover:border-[var(--slash)] hover:bg-white"
>
<div className="text-[12px] font-semibold tracking-[0.02em] text-[var(--ink)]">
{service.label}
</div>
<div className="mt-sp-2 text-[12px] leading-6 text-[var(--meta)]">
{service.description}
</div>
</Link>
))}
</div>
</div>
<motion.div
style={{
display: "grid",
gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
gap: "var(--sp-6)",
}}
className="max-md:grid-cols-1 max-md:!flex max-md:!flex-col"
className="grid grid-cols-1 gap-sp-4 md:grid-cols-2 md:gap-sp-5 lg:grid-cols-3 lg:gap-sp-6"
initial="hidden"
whileInView="visible"
viewport={{ once: true, amount: 0.2 }}
variants={containerVariants}
>
{TIERS.map((tier) => (
<motion.div key={tier.id} variants={itemVariants}>
<motion.div key={tier.id} variants={itemVariants} className="min-w-0">
<TierCard tier={tier} />
</motion.div>
))}
@@ -210,3 +246,4 @@ export function WorkModalities() {
</Section>
);
}
+5 -13
View File
@@ -21,7 +21,6 @@ export function LazyRevealImage({
}: Readonly<LazyRevealImageProps>) {
const shouldReduceMotion = useReducedMotion();
const [loaded, setLoaded] = useState(false);
const [animating, setAnimating] = useState(!shouldReduceMotion);
return (
<motion.div
@@ -29,28 +28,24 @@ export function LazyRevealImage({
initial={
shouldReduceMotion
? { opacity: 0 }
: { opacity: 0, filter: "blur(20px)", scale: 1.05 }
: { opacity: 0, y: 20, scale: 1.02 }
}
animate={
loaded
? shouldReduceMotion
? { opacity: 1 }
: { opacity: 1, filter: "blur(0px)", scale: 1 }
: { opacity: 1, y: 0, scale: 1 }
: shouldReduceMotion
? { opacity: 0 }
: { opacity: 0, filter: "blur(20px)", scale: 1.05 }
: { opacity: 0, y: 20, scale: 1.02 }
}
transition={{
duration: shouldReduceMotion ? 0.25 : 0.75,
ease: [0.16, 1, 0.3, 1],
}}
style={{
willChange:
animating && !shouldReduceMotion
? "filter, transform, opacity"
: undefined,
willChange: !shouldReduceMotion ? "transform, opacity" : undefined,
}}
onAnimationComplete={() => setAnimating(false)}
>
<Image
src={src}
@@ -59,10 +54,7 @@ export function LazyRevealImage({
loading="lazy"
sizes={sizes}
placeholder={typeof src === "string" ? "empty" : "blur"}
onLoad={() => {
setAnimating(true);
setLoaded(true);
}}
onLoad={() => setLoaded(true)}
className={imageClassName}
/>
</motion.div>
+89 -72
View File
@@ -1,21 +1,3 @@
@import url("https://fonts.googleapis.com/css2?family=Syne:wght@400;500;600;700;800&family=Geist+Mono:wght@300;400;500;600&display=swap");
@font-face {
font-family: "Monument Extended";
src: url("/fonts/MonumentExtended-Regular.otf") format("opentype");
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Monument Extended";
src: url("/fonts/MonumentExtended-Ultrabold.otf") format("opentype");
font-weight: 800;
font-style: normal;
font-display: swap;
}
@tailwind base;
@tailwind components;
@tailwind utilities;
@@ -46,7 +28,7 @@
--white: #ffffff;
--slash: #ccff00;
--slash-dim: rgba(204, 255, 0, 0.16);
--meta: #777;
--meta: #595959;
--border: rgba(10, 10, 10, 0.1);
--border-light: rgba(10, 10, 10, 0.06);
--sp-1: 4px;
@@ -65,9 +47,9 @@
--r-md: 8px;
--r-lg: 16px;
--r-full: 999px;
--font-display: "Syne", sans-serif;
--font-hero: "Monument Extended", sans-serif;
--font-mono: "Geist Mono", monospace;
--font-display: var(--font-syne), sans-serif;
--font-hero: var(--font-monument-extended), sans-serif;
--font-mono: var(--font-geist-mono), monospace;
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
--ease-in: cubic-bezier(0.4, 0, 1, 1);
}
@@ -88,8 +70,8 @@ body {
background: var(--paper);
color: var(--ink);
font-family: var(--font-mono);
font-size: 13px;
line-height: 1.6;
font-size: 14px;
line-height: 1.68;
overflow-x: hidden;
}
@@ -124,11 +106,11 @@ textarea {
}
.ds-main {
/* padding-top: 52px; removed since there is no header now */
/* no top padding needed */
}
.ds-section {
padding: var(--sp-20) var(--sp-16);
padding: clamp(72px, 9vw, 112px) clamp(24px, 6vw, 64px);
border-bottom: 1px solid var(--border);
}
@@ -153,18 +135,18 @@ textarea {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: var(--sp-8);
margin-bottom: var(--sp-12);
gap: var(--sp-6);
margin-bottom: var(--sp-10);
}
.ds-section-meta {
max-width: 700px;
max-width: 760px;
}
.ds-eyebrow,
.ds-kicker {
font-size: 9px;
letter-spacing: 0.16em;
font-size: 10px;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--meta);
}
@@ -175,16 +157,17 @@ textarea {
.ds-section-title {
font-family: var(--font-display);
font-size: clamp(24px, 4vw, 40px);
line-height: 1.05;
font-size: clamp(28px, 4vw, 44px);
line-height: 1.02;
font-weight: 800;
letter-spacing: -0.03em;
letter-spacing: -0.035em;
}
.ds-section-description {
color: var(--meta);
font-size: 11px;
line-height: 1.8;
max-width: 58ch;
color: rgba(10, 10, 10, 0.62);
font-size: 14px;
line-height: 1.82;
}
.ds-grid-2,
@@ -209,14 +192,15 @@ textarea {
}
.ds-card {
background: rgba(255, 255, 255, 0.55);
padding: var(--sp-6);
background: rgba(255, 255, 255, 0.74);
padding: clamp(20px, 3vw, 28px);
display: flex;
flex-direction: column;
backdrop-filter: blur(10px);
}
.ds-panel {
background: rgba(10, 10, 10, 0.02);
background: rgba(10, 10, 10, 0.03);
padding: var(--sp-5);
display: flex;
flex-direction: column;
@@ -224,16 +208,16 @@ textarea {
.ds-card-title {
font-family: var(--font-display);
font-size: clamp(18px, 2.4vw, 28px);
line-height: 1.1;
letter-spacing: -0.02em;
margin-top: var(--sp-3);
font-size: clamp(20px, 2.4vw, 30px);
line-height: 1.08;
letter-spacing: -0.025em;
margin-top: var(--sp-4);
}
.ds-copy {
margin-top: var(--sp-4);
color: var(--ink-80);
font-size: 12px;
font-size: 14px;
line-height: 1.8;
flex-grow: 1;
}
@@ -241,27 +225,30 @@ textarea {
.ds-tags {
display: flex;
flex-wrap: wrap;
gap: var(--sp-3);
gap: 10px;
list-style: none;
margin-top: auto;
padding-top: var(--sp-8);
padding-top: var(--sp-6);
}
.ds-tag {
padding: 8px 12px;
border: 1px solid var(--border);
border: 1px solid rgba(10, 10, 10, 0.12);
background: rgba(10, 10, 10, 0.03);
font-size: 10px;
letter-spacing: 0.12em;
text-transform: uppercase;
font-size: 11px;
letter-spacing: 0.04em;
text-transform: none;
font-weight: 500;
white-space: normal;
word-break: normal;
overflow-wrap: break-word;
}
.ds-metric {
display: inline-flex;
align-items: center;
gap: var(--sp-2);
color: var(--meta);
color: rgba(10, 10, 10, 0.65);
font-size: 10px;
letter-spacing: 0.1em;
text-transform: uppercase;
@@ -272,8 +259,8 @@ textarea {
align-items: center;
gap: var(--sp-2);
text-decoration: none;
font-size: 10px;
letter-spacing: 0.12em;
font-size: 11px;
letter-spacing: 0.08em;
text-transform: uppercase;
transition:
transform 0.2s ease,
@@ -297,10 +284,10 @@ textarea {
}
.ds-list {
margin-top: var(--sp-6);
padding-left: var(--sp-5);
font-size: 12px;
line-height: 1.8;
margin-top: var(--sp-5);
padding-left: 18px;
font-size: 14px;
line-height: 1.85;
flex-grow: 1;
}
@@ -310,31 +297,31 @@ textarea {
.ds-loading,
.ds-error {
padding: var(--sp-10) var(--sp-16);
padding: var(--sp-10) clamp(24px, 6vw, 64px);
font-size: 11px;
letter-spacing: 0.14em;
letter-spacing: 0.1em;
text-transform: uppercase;
}
.ds-section-dark .ds-eyebrow,
.ds-section-dark .ds-kicker,
.ds-section-dark .ds-metric {
color: rgba(255, 255, 255, 0.3);
color: rgba(255, 255, 255, 0.65);
}
.ds-section-dark .ds-section-description {
color: rgba(255, 255, 255, 0.38);
color: rgba(255, 255, 255, 0.75);
}
.ds-section-dark .ds-card,
.ds-section-dark .ds-panel {
background: rgba(255, 255, 255, 0.02);
background: rgba(255, 255, 255, 0.03);
border-color: rgba(255, 255, 255, 0.08);
}
.ds-section-dark .ds-copy,
.ds-section-dark .ds-list {
color: rgba(255, 255, 255, 0.68);
color: rgba(255, 255, 255, 0.74);
}
.ds-section-dark .ds-tag {
@@ -350,19 +337,19 @@ textarea {
display: block;
width: 100%;
height: 1px;
margin-top: var(--sp-5);
margin-top: var(--sp-4);
background: linear-gradient(
90deg,
rgba(10, 10, 10, 0.55) 0%,
rgba(10, 10, 10, 0.08) 100%
rgba(10, 10, 10, 0.38) 0%,
rgba(10, 10, 10, 0.06) 100%
);
}
.ds-section-dark .ds-draw-line {
background: linear-gradient(
90deg,
rgba(204, 255, 0, 0.75) 0%,
rgba(204, 255, 0, 0.1) 100%
rgba(204, 255, 0, 0.5) 0%,
rgba(204, 255, 0, 0.08) 100%
);
}
@@ -453,14 +440,44 @@ textarea {
scroll-behavior: auto !important;
}
}
@media (max-width: 1024px) {
.ds-section {
padding: var(--sp-16) var(--sp-6);
.ds-section-header {
flex-direction: column;
gap: var(--sp-4);
}
.ds-section-header,
.ds-grid-2,
.ds-grid-4 {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
.ds-section {
padding: 72px 20px;
}
.ds-section-title {
font-size: clamp(26px, 8vw, 34px);
}
.ds-section-description,
.ds-copy,
.ds-list {
font-size: 13px;
line-height: 1.75;
}
.ds-tags {
gap: 8px;
padding-top: var(--sp-5);
}
.ds-tag {
font-size: 10px;
padding: 7px 10px;
}
}
+175 -64
View File
@@ -1,53 +1,88 @@
import type { Metadata, Viewport } from "next";
import { Geist_Mono, Syne } from "next/font/google";
import localFont from "next/font/local";
import type { ReactNode } from "react";
import GoogleAnalytics from "./components/GoogleAnalytics";
import { RouteTransition } from "./components/RouteTransition";
import { SmoothScrollProvider } from "./providers/SmoothScrollProvider";
import { AnimationProvider } from "./providers/AnimationProvider";
import { SmoothScrollProvider } from "./providers/SmoothScrollProvider";
import "./globals.css";
const siteUrl = "https://evandro.dev.br";
const seoTitle =
"Engenharia de Software, Next.js, Tauri, Rust e Consultoria Tecnica | Evandro Fonseca Junior";
const seoDescription =
"Consultoria e engenharia de software para Next.js, Tauri, Rust, IoT e sistemas criticos. Arquitetura de software e integracoes hardware + software para empresas que precisam de estabilidade e escala.";
const syne = Syne({
subsets: ["latin"],
weight: ["400", "500", "600", "700", "800"],
display: "swap",
variable: "--font-syne",
});
const geistMono = Geist_Mono({
subsets: ["latin"],
weight: ["400", "500", "600"],
display: "swap",
variable: "--font-geist-mono",
});
const monumentExtended = localFont({
src: [
{
path: "../public/fonts/MonumentExtended-Regular.otf",
weight: "400",
style: "normal",
},
{
path: "../public/fonts/MonumentExtended-Ultrabold.otf",
weight: "800",
style: "normal",
},
],
display: "swap",
variable: "--font-monument-extended",
});
export const metadata: Metadata = {
title:
"Evandro Fonseca Junior | Engenheiro de Software (Next.js, Tauri & Rust)",
description:
"Engenheiro Full-Stack especializado em aplicacoes de alta performance e sistemas criticos. Desenvolvimento com Next.js, React, Tauri e arquitetura escalavel.",
title: seoTitle,
description: seoDescription,
keywords: [
"engenheiro full-stack",
"engenharia de software",
"engenheiro de software",
"Next.js",
"React",
"Tauri",
"Rust",
"Python",
"TypeScript",
"PostgreSQL",
"MySQL",
"cloud computing",
"IoT",
"automacao",
"consultoria tecnica",
"consultoria next.js",
"next.js",
"tauri",
"rust",
"arquitetura de software",
"sistemas escalaveis",
"high performance",
"sistemas criticos",
"integracao hardware software",
"iot",
"software para iot",
"engenharia full-stack",
"aplicacoes de alta performance",
],
authors: [{ name: "Evandro Fonseca Junior" }],
creator: "Evandro Fonseca Junior",
publisher: "Evandro Fonseca Junior",
applicationName: "Evandro.dev.br",
category: "Technology",
metadataBase: new URL(siteUrl),
alternates: {
canonical: "/",
},
formatDetection: {
email: false,
address: false,
telephone: false,
},
metadataBase: new URL("https://evandro.dev.br"),
alternates: {
canonical: "/",
},
openGraph: {
title:
"Evandro Fonseca Junior | Engenheiro de Software (Next.js, Tauri & Rust)",
description:
"Engenheiro Full-Stack especializado em aplicacoes de alta performance e sistemas criticos. Desenvolvimento com Next.js, React, Tauri e arquitetura escalavel.",
url: "https://evandro.dev.br",
siteName: "Evandro Fonseca Junior",
title: seoTitle,
description: seoDescription,
url: siteUrl,
siteName: "Evandro.dev.br",
locale: "pt_BR",
type: "website",
images: [
@@ -55,16 +90,14 @@ export const metadata: Metadata = {
url: "/opengraph-image",
width: 1200,
height: 630,
alt: "Evandro Fonseca Junior - Engenheiro de Software",
alt: "Evandro Fonseca Junior | Engenharia de Software, Next.js, Tauri e Rust",
},
],
},
twitter: {
card: "summary_large_image",
title:
"Evandro Fonseca Junior | Engenheiro de Software (Next.js, Tauri & Rust)",
description:
"Engenheiro Full-Stack especializado em aplicacoes de alta performance e sistemas criticos.",
title: seoTitle,
description: seoDescription,
creator: "@evandrodevbr",
images: ["/opengraph-image"],
},
@@ -95,41 +128,119 @@ interface RootLayoutProps {
export default function RootLayout({ children }: Readonly<RootLayoutProps>) {
const jsonLd = {
"@context": "https://schema.org",
"@type": "ProfilePage",
mainEntity: {
"@type": "Person",
name: "Evandro Fonseca Junior",
alternateName: "Evandro.dev",
jobTitle: "Engenheiro de Software",
url: "https://evandro.dev.br",
sameAs: [
"https://github.com/evandrodevbr",
"https://www.linkedin.com/in/evandro-fonseca-junior-0969aa1b0/",
],
knowsAbout: [
"Software Engineering",
"Next.js",
"React",
"Tauri",
"Rust",
"TypeScript",
"System Architecture",
"Cloud Computing",
"IoT",
],
worksFor: {
"@type": "Organization",
name: "Freelancer / Consultor Independente",
"@graph": [
{
"@type": "WebSite",
"@id": `${siteUrl}/#website`,
url: siteUrl,
name: "Evandro.dev.br",
inLanguage: "pt-BR",
description: seoDescription,
},
description:
"Engenheiro de software especializado em aplicacoes de alta performance e sistemas criticos. Desenvolvimento com Next.js 15, React 19, Tauri e arquitetura escalavel.",
},
{
"@type": "Person",
"@id": `${siteUrl}/#person`,
name: "Evandro Fonseca Junior",
alternateName: ["Evandro.dev", "evandrodevbr"],
jobTitle: "Engenheiro de Software Full-Stack",
description: seoDescription,
url: siteUrl,
image: `${siteUrl}/opengraph-image`,
sameAs: [
"https://github.com/evandrodevbr",
"https://www.linkedin.com/in/evandro-fonseca-junior-0969aa1b0/",
"https://www.instagram.com/evandrodevbr/",
],
worksFor: {
"@type": "Organization",
name: "Consultoria Independente",
},
homeLocation: {
"@type": "Place",
name: "Garuva, Santa Catarina, Brasil",
},
knowsAbout: [
"Engenharia de Software",
"Next.js",
"Tauri",
"Rust",
"Arquitetura de Software",
"Sistemas Criticos",
"Integracao hardware e software",
"IoT",
"TypeScript",
"Cloud Computing",
],
},
{
"@type": "ProfessionalService",
"@id": `${siteUrl}/#services`,
name: "Evandro Fonseca Junior | Engenharia de Software e Consultoria Tecnica",
url: siteUrl,
description:
"Servico de engenharia de software, arquitetura de software, integracoes criticas e consultoria tecnica para Next.js, Tauri, Rust, IoT e produtos conectados.",
areaServed: [
{
"@type": "Country",
name: "Brasil",
},
{
"@type": "Place",
name: "Remote worldwide",
},
],
provider: {
"@id": `${siteUrl}/#person`,
},
serviceType: [
"Engenharia de Software",
"Consultoria Tecnica",
"Arquitetura de Software",
"Integracao hardware + software",
"Desenvolvimento Next.js",
"Desenvolvimento Tauri",
"Desenvolvimento Rust",
"IoT",
"Sistemas Criticos",
],
hasOfferCatalog: {
"@type": "OfferCatalog",
name: "Modelos de contratacao",
itemListElement: [
{
"@type": "Offer",
itemOffered: {
"@type": "Service",
name: "Auditoria e diagnostico tecnico",
},
},
{
"@type": "Offer",
itemOffered: {
"@type": "Service",
name: "Ciclos de engenharia",
},
},
{
"@type": "Offer",
itemOffered: {
"@type": "Service",
name: "Fractional Tech Lead",
},
},
],
},
},
],
};
return (
<html lang="pt-BR">
<html
lang="pt-BR"
className={`${syne.variable} ${geistMono.variable} ${monumentExtended.variable}`}
>
<head>
<link rel="canonical" href="https://evandro.dev.br" />
<link rel="canonical" href={siteUrl} />
</head>
<body>
<SmoothScrollProvider>
+6 -7
View File
@@ -1,16 +1,14 @@
"use client";
import dynamic from "next/dynamic";
import { Suspense } from "react";
import About from "./components/About";
import Contact from "./components/Contact";
import Experience from "./components/Experience";
import Footer from "./components/Footer";
import Hero from "./components/Hero";
import { SectionLoading } from "./components/SectionLoading";
import Skills from "./components/Skills";
import { WorkModalities } from "./components/WorkModalities";
const About = dynamic(() => import("./components/About"));
const Skills = dynamic(() => import("./components/Skills"));
const Experience = dynamic(() => import("./components/Experience"));
const WorkModalities = dynamic(() => import("./components/WorkModalities").then(mod => mod.WorkModalities));
const Contact = dynamic(() => import("./components/Contact"));
const Projects = dynamic(() => import("./components/Projects"));
export default function Home() {
@@ -33,3 +31,4 @@ export default function Home() {
</div>
);
}
+50 -21
View File
@@ -1,40 +1,69 @@
"use client";
import type { ReactNode } from "react";
import { useEffect, useRef } from "react";
import Lenis from "lenis";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
export function SmoothScrollProvider({
children,
}: {
children: React.ReactNode;
children: ReactNode;
}) {
const lenisRef = useRef<Lenis | null>(null);
const teardownRef = useRef<() => void>(() => {});
useEffect(() => {
const lenis = new Lenis({
duration: 1.2,
easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)), // Expo ease
smoothWheel: true,
});
const prefersReducedMotion = window.matchMedia(
"(prefers-reduced-motion: reduce)",
).matches;
const hasCoarsePointer = window.matchMedia("(pointer: coarse)").matches;
lenisRef.current = lenis;
if (prefersReducedMotion || hasCoarsePointer) {
return undefined;
}
// Sincroniza Lenis com GSAP ScrollTrigger — critico para evitar dessync
lenis.on("scroll", ScrollTrigger.update);
let isMounted = true;
gsap.ticker.add((time) => {
lenis.raf(time * 1000);
});
const setupSmoothScroll = async () => {
const [{ default: Lenis }, { gsap }, { ScrollTrigger }] =
await Promise.all([
import("lenis"),
import("gsap"),
import("gsap/ScrollTrigger"),
]);
gsap.ticker.lagSmoothing(0);
if (!isMounted) {
return;
}
gsap.registerPlugin(ScrollTrigger);
const lenis = new Lenis({
duration: 1.2,
easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
smoothWheel: true,
});
const handleScroll = () => ScrollTrigger.update();
const tick = (time: number) => {
lenis.raf(time * 1000);
};
lenis.on("scroll", handleScroll);
gsap.ticker.add(tick);
gsap.ticker.lagSmoothing(0);
teardownRef.current = () => {
lenis.off?.("scroll", handleScroll);
gsap.ticker.remove(tick);
lenis.destroy();
};
};
void setupSmoothScroll();
return () => {
lenis.destroy();
gsap.ticker.remove((time) => lenis.raf(time * 1000));
isMounted = false;
teardownRef.current();
teardownRef.current = () => {};
};
}, []);
+4 -3
View File
@@ -1,14 +1,15 @@
import { MetadataRoute } from "next";
import { MetadataRoute } from "next";
export default function robots(): MetadataRoute.Robots {
return {
rules: [
{
userAgent: "*",
allow: "/",
disallow: ["/api/", "/_next/", "/admin/"],
allow: ["/", "/_next/static/", "/_next/image/"],
disallow: ["/api/", "/admin/"],
},
],
sitemap: "https://evandro.dev.br/sitemap.xml",
host: "https://evandro.dev.br",
};
}
+282
View File
@@ -0,0 +1,282 @@
import Link from "next/link";
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import {
getRelatedCases,
getServicePage,
servicePages,
siteName,
siteUrl,
} from "@/lib/site-content";
interface ServicePageProps {
params: Promise<{
slug: string;
}>;
}
export function generateStaticParams() {
return servicePages.map((service) => ({ slug: service.slug }));
}
export async function generateMetadata({
params,
}: ServicePageProps): Promise<Metadata> {
const { slug } = await params;
const service = getServicePage(slug);
if (!service) {
return {
title: `Servico nao encontrado | ${siteName}`,
};
}
const url = `${siteUrl}/servicos/${service.slug}`;
return {
title: service.seoTitle,
description: service.metaDescription,
alternates: {
canonical: url,
},
openGraph: {
title: service.seoTitle,
description: service.metaDescription,
url,
siteName,
type: "article",
},
twitter: {
card: "summary_large_image",
title: service.seoTitle,
description: service.metaDescription,
},
};
}
export default async function ServicePage({ params }: ServicePageProps) {
const { slug } = await params;
const service = getServicePage(slug);
if (!service) {
notFound();
}
const relatedCases = getRelatedCases(service.relatedCaseIds);
const pageUrl = `${siteUrl}/servicos/${service.slug}`;
const jsonLd = {
"@context": "https://schema.org",
"@graph": [
{
"@type": "Service",
"@id": `${pageUrl}#service`,
name: service.navLabel,
description: service.metaDescription,
serviceType: service.navLabel,
provider: {
"@type": "Person",
name: "Evandro Fonseca Junior",
url: siteUrl,
},
areaServed: [
{
"@type": "Country",
name: "Brasil",
},
{
"@type": "Place",
name: "Remote worldwide",
},
],
url: pageUrl,
},
{
"@type": "BreadcrumbList",
itemListElement: [
{
"@type": "ListItem",
position: 1,
name: "Inicio",
item: siteUrl,
},
{
"@type": "ListItem",
position: 2,
name: "Servicos",
item: `${siteUrl}/servicos`,
},
{
"@type": "ListItem",
position: 3,
name: service.navLabel,
item: pageUrl,
},
],
},
],
};
return (
<main>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<section className="ds-section ds-section-dark">
<div className="ds-section-shell">
<div className="max-w-[860px]">
<p className="ds-eyebrow">{service.eyebrow}</p>
<h1 className="ds-section-title max-w-[14ch] text-balance text-white">
{service.headline}
</h1>
<p className="ds-copy max-w-[64ch] !text-[rgba(255,255,255,0.78)]">
{service.intro}
</p>
<div className="mt-sp-8 flex flex-wrap gap-sp-3">
<a href={service.ctaHref} className="ds-link ds-link-accent">
{service.ctaLabel}
</a>
<Link href="/cases" className="ds-link ds-link-muted">
Ver casos relacionados
</Link>
</div>
</div>
</div>
</section>
<section className="ds-section">
<div className="ds-section-shell">
<div className="ds-grid-2">
<article className="ds-card">
<div className="ds-kicker">Para quem faz sentido</div>
<h2 className="ds-card-title">Contextos em que esse servico encaixa melhor</h2>
<ul className="ds-list mt-sp-5 pl-[1.15rem]">
{service.audience.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</article>
<article className="ds-card">
<div className="ds-kicker">O que muda</div>
<h2 className="ds-card-title">Resultados que eu busco destravar</h2>
<ul className="ds-list mt-sp-5 pl-[1.15rem]">
{service.outcomes.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</article>
</div>
</div>
</section>
<section className="ds-section">
<div className="ds-section-shell">
<div className="ds-grid-2">
<article className="ds-card">
<div className="ds-kicker">Como eu estruturo</div>
<h2 className="ds-card-title">Processo de trabalho</h2>
<ol className="mt-sp-5 space-y-sp-4 text-[14px] leading-8 text-[var(--ink-80)]">
{service.process.map((step, index) => (
<li key={step} className="border-t border-[var(--border)] pt-sp-4 first:border-t-0 first:pt-0">
<span className="mr-sp-3 inline-flex h-7 min-w-7 items-center justify-center rounded-full border border-[var(--border)] text-[11px] font-semibold text-[var(--meta)]">
{index + 1}
</span>
{step}
</li>
))}
</ol>
</article>
<article className="ds-card">
<div className="ds-kicker">Stack e repertorio</div>
<h2 className="ds-card-title">Tecnologias e capacidades envolvidas</h2>
<p className="ds-copy">
A stack entra como meio para resolver o problema certo, com criterio de manutencao,
observabilidade e evolucao do produto.
</p>
<ul className="ds-tags">
{service.stack.map((item) => (
<li key={item} className="ds-tag">
{item}
</li>
))}
</ul>
</article>
</div>
</div>
</section>
<section className="ds-section">
<div className="ds-section-shell">
<div className="ds-section-header">
<div className="ds-section-meta">
<p className="ds-eyebrow">Casos relacionados</p>
<h2 className="ds-section-title">Projetos que reforcam esse tipo de entrega</h2>
<p className="ds-section-description">
Alguns exemplos de contexto tecnico em que esse servico se conecta a entregas reais.
</p>
</div>
</div>
<div className="ds-grid-2">
{relatedCases.map((caseStudy) => (
<article key={caseStudy.id} className="ds-card">
<div className="ds-kicker">{caseStudy.title}</div>
<h3 className="ds-card-title">{caseStudy.subtitle}</h3>
<p className="ds-copy">{caseStudy.summary}</p>
<p className="mt-sp-4 text-[13px] leading-7 text-[var(--meta)]">
{caseStudy.impact}
</p>
<ul className="ds-tags mt-sp-6">
{caseStudy.stack.map((item) => (
<li key={item} className="ds-tag">
{item}
</li>
))}
</ul>
<div className="mt-sp-6 flex flex-wrap gap-sp-3">
{caseStudy.demoUrl ? (
<a href={caseStudy.demoUrl} className="ds-link ds-link-accent" target="_blank" rel="noreferrer">
Abrir projeto
</a>
) : null}
{caseStudy.githubUrl ? (
<a href={caseStudy.githubUrl} className="ds-link ds-link-muted" target="_blank" rel="noreferrer">
Ver codigo
</a>
) : null}
</div>
</article>
))}
</div>
</div>
</section>
<section className="ds-section ds-section-dark">
<div className="ds-section-shell">
<div className="ds-card border-white/10 bg-[rgba(255,255,255,0.03)]">
<div className="ds-kicker">Proximo passo</div>
<h2 className="ds-card-title text-white">
Se esse servico parece aderente ao seu contexto, a conversa inicial ja pode ser objetiva.
</h2>
<p className="ds-copy !text-[rgba(255,255,255,0.76)]">
Traga o sistema, o gargalo e o objetivo de negocio. Eu estruturo a leitura tecnica e
indico o caminho mais seguro para avancar.
</p>
<div className="mt-sp-6 flex flex-wrap gap-sp-3">
<a href={service.ctaHref} className="ds-link ds-link-accent">
{service.ctaLabel}
</a>
<Link href="/servicos" className="ds-link ds-link-muted">
Ver todos os servicos
</Link>
</div>
</div>
</div>
</section>
</main>
);
}
+88
View File
@@ -0,0 +1,88 @@
import Link from "next/link";
import type { Metadata } from "next";
import { serviceHubLinks, servicePages, siteName, siteUrl } from "@/lib/site-content";
export const metadata: Metadata = {
title: `Servicos de Engenharia de Software e Consultoria Tecnica | ${siteName}`,
description:
"Servicos de engenharia de software, consultoria Next.js, Tauri, Rust, arquitetura de software e integracao hardware + software para produtos e sistemas criticos.",
alternates: {
canonical: `${siteUrl}/servicos`,
},
openGraph: {
title: `Servicos de Engenharia de Software e Consultoria Tecnica | ${siteName}`,
description:
"Servicos de engenharia de software, consultoria Next.js, Tauri, Rust, arquitetura de software e integracao hardware + software para produtos e sistemas criticos.",
url: `${siteUrl}/servicos`,
siteName,
type: "website",
},
};
export default function ServicesIndexPage() {
const jsonLd = {
"@context": "https://schema.org",
"@graph": [
{
"@type": "CollectionPage",
"@id": `${siteUrl}/servicos#page`,
name: "Servicos de Engenharia de Software e Consultoria Tecnica",
url: `${siteUrl}/servicos`,
description:
"Hub de servicos para engenharia de software, consultoria tecnica, Next.js, Tauri, Rust, IoT e arquitetura de software.",
},
{
"@type": "ItemList",
itemListElement: servicePages.map((service, index) => ({
"@type": "ListItem",
position: index + 1,
url: `${siteUrl}/servicos/${service.slug}`,
name: service.navLabel,
})),
},
],
};
return (
<main>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<section className="ds-section ds-section-dark">
<div className="ds-section-shell">
<div className="max-w-[860px]">
<p className="ds-eyebrow">Hub de servicos</p>
<h1 className="ds-section-title max-w-[14ch] text-balance text-white">
Servicos de engenharia de software para contextos tecnicos de alta exigencia.
</h1>
<p className="ds-copy max-w-[64ch] !text-[rgba(255,255,255,0.78)]">
Organizei as paginas de servico por intencao real de compra: engenharia de software,
consultoria Next.js, Tauri e Rust, arquitetura de software e integracoes hardware + software.
</p>
</div>
</div>
</section>
<section className="ds-section">
<div className="ds-section-shell">
<div className="ds-grid-2">
{serviceHubLinks.map((service) => (
<article key={service.href} className="ds-card">
<div className="ds-kicker">Servico especializado</div>
<h2 className="ds-card-title">{service.label}</h2>
<p className="ds-copy">{service.description}</p>
<div className="mt-sp-6">
<Link href={service.href} className="ds-link ds-link-accent">
Abrir pagina
</Link>
</div>
</article>
))}
</div>
</div>
</section>
</main>
);
}
+15 -27
View File
@@ -1,45 +1,33 @@
import { MetadataRoute } from "next";
import { servicePages, siteUrl } from "@/lib/site-content";
export default function sitemap(): MetadataRoute.Sitemap {
const baseUrl = "https://evandro.dev.br";
const currentDate = new Date().toISOString();
const now = new Date();
return [
{
url: baseUrl,
lastModified: currentDate,
url: siteUrl,
lastModified: now,
changeFrequency: "monthly",
priority: 1.0,
},
{
url: `${baseUrl}/#about`,
lastModified: currentDate,
url: `${siteUrl}/servicos`,
lastModified: now,
changeFrequency: "monthly",
priority: 0.8,
priority: 0.9,
},
{
url: `${baseUrl}/#skills`,
lastModified: currentDate,
url: `${siteUrl}/cases`,
lastModified: now,
changeFrequency: "monthly",
priority: 0.8,
priority: 0.9,
},
{
url: `${baseUrl}/#experience`,
lastModified: currentDate,
changeFrequency: "monthly",
...servicePages.map((service) => ({
url: `${siteUrl}/servicos/${service.slug}`,
lastModified: now,
changeFrequency: "monthly" as const,
priority: 0.8,
},
{
url: `${baseUrl}/#projects`,
lastModified: currentDate,
changeFrequency: "monthly",
priority: 0.8,
},
{
url: `${baseUrl}/#contact`,
lastModified: currentDate,
changeFrequency: "monthly",
priority: 0.8,
},
})),
];
}
+293
View File
@@ -0,0 +1,293 @@
export const siteUrl = "https://evandro.dev.br";
export const siteName = "Evandro.dev.br";
export interface ServicePageContent {
slug: string;
navLabel: string;
eyebrow: string;
seoTitle: string;
metaDescription: string;
headline: string;
intro: string;
audience: string[];
outcomes: string[];
process: string[];
stack: string[];
ctaLabel: string;
ctaHref: string;
relatedCaseIds: string[];
}
export interface CaseStudyContent {
id: string;
title: string;
subtitle: string;
summary: string;
impact: string;
stack: string[];
demoUrl?: string | null;
githubUrl?: string | null;
}
export const servicePages: ServicePageContent[] = [
{
slug: "engenharia-de-software",
navLabel: "Engenharia de Software",
eyebrow: "Servico principal",
seoTitle:
"Engenharia de Software para Produtos e Sistemas Criticos | Evandro Fonseca Junior",
metaDescription:
"Engenharia de software para evolucao de produtos, integracoes complexas e sistemas criticos. Entrega ponta a ponta com foco em arquitetura clara, estabilidade e velocidade de execucao.",
headline:
"Engenharia de software para evoluir produto sem aumentar o caos tecnico.",
intro:
"Atuo em discovery, arquitetura, implementacao e sustentacao de software para empresas que precisam entregar com previsibilidade, sem sacrificar clareza tecnica nem operacao.",
audience: [
"Times que ja tem produto em operacao e precisam voltar a ganhar velocidade.",
"Empresas que sofrem com integracoes, retrabalho e backlog tecnico acumulado.",
"Operacoes em que confiabilidade e continuidade pesam mais do que velocidade teatral.",
],
outcomes: [
"Arquitetura mais legivel para manter e escalar.",
"Entrega incremental com menos retrabalho entre produto e engenharia.",
"Integracoes mais robustas entre sistemas, APIs e fluxos internos.",
"Base tecnica preparada para crescer sem depender de improviso constante.",
],
process: [
"Diagnostico do contexto tecnico, do risco e do gargalo principal.",
"Definicao do recorte de entrega com prioridades e trade-offs claros.",
"Implementacao com rastreabilidade, code review e criterio de producao.",
"Handoff ou continuidade com documentacao e proximo ciclo recomendado.",
],
stack: ["Next.js", "TypeScript", "Node.js", "PostgreSQL", "APIs REST"],
ctaLabel: "Agendar conversa sobre engenharia",
ctaHref:
"https://wa.me/5547992742365?text=Quero%20conversar%20sobre%20engenharia%20de%20software.",
relatedCaseIds: ["nutriscan", "reddit-explorer", "sc-plus"],
},
{
slug: "consultoria-nextjs",
navLabel: "Consultoria Next.js",
eyebrow: "Stack especializada",
seoTitle:
"Consultoria Next.js para Produtos, SEO e Performance | Evandro Fonseca Junior",
metaDescription:
"Consultoria Next.js para App Router, SEO tecnico, performance, arquitetura e evolucao de produto. Ideal para projetos que precisam escalar sem perder clareza e Core Web Vitals.",
headline:
"Consultoria Next.js para produto, performance e SEO tecnico sem gambiarra.",
intro:
"Ajudo equipes a usar Next.js com mais criterio arquitetural, melhor separacao entre server e client, e foco real em performance, indexacao e experiencia de produto.",
audience: [
"Produtos em Next.js que perderam clareza com o crescimento da base.",
"Sites e plataformas que dependem de SEO tecnico e performance para converter.",
"Times que precisam migrar, estabilizar ou revisar arquitetura App Router.",
],
outcomes: [
"Estrutura mais consistente para App Router, Server Components e dados.",
"Melhoras praticas em title, metadata, schema, semantica e links internos.",
"Reducao de gargalos em renderizacao, bundle e fetch.",
"Base mais pronta para crescer sem regressao de SEO ou UX.",
],
process: [
"Auditoria de arquitetura, rotas, metadata e performance.",
"Priorizacao das correcoes de maior impacto comercial e tecnico.",
"Implementacao ou acompanhamento do time interno nas partes criticas.",
"Validacao final com build, semantica e consistencia entre paginas.",
],
stack: ["Next.js", "React", "TypeScript", "Tailwind CSS", "SEO tecnico"],
ctaLabel: "Solicitar consultoria Next.js",
ctaHref: "https://wa.me/5547992742365?text=Quero%20consultoria%20em%20Next.js.",
relatedCaseIds: ["nutriscan", "twins-lab", "sc-plus"],
},
{
slug: "tauri-rust",
navLabel: "Tauri + Rust",
eyebrow: "Desktop e performance",
seoTitle:
"Desenvolvimento Tauri e Rust para Apps Desktop | Evandro Fonseca Junior",
metaDescription:
"Desenvolvimento com Tauri e Rust para apps desktop mais leves, seguros e eficientes. Consultoria tecnica para produtos que precisam de performance, integracao local e distribuicao confiavel.",
headline:
"Tauri e Rust para apps desktop mais leves, seguros e prontos para operar.",
intro:
"Uso Tauri e Rust quando o produto precisa de executavel desktop com footprint menor, melhor controle do ambiente local e mais previsibilidade do que stacks mais pesadas.",
audience: [
"Produtos desktop que precisam integrar recursos do sistema operacional.",
"Ferramentas internas com demanda por distribuicao leve e manutencao simples.",
"Times avaliando migracao de stack desktop ou construindo um MVP tecnico robusto.",
],
outcomes: [
"Aplicacoes desktop com melhor eficiencia de recursos.",
"Fluxos locais e integracoes nativas com criterio tecnico mais alto.",
"Arquitetura preparada para distribuicao, atualizacao e manutencao.",
"Menos atrito entre interface moderna e motor de execucao confiavel.",
],
process: [
"Definicao da necessidade real de desktop e do papel de Rust no produto.",
"Desenho da arquitetura entre frontend, camada nativa e distribuicao.",
"Implementacao dos fluxos criticos com foco em confiabilidade e ergonomia.",
"Empacotamento, validacao e estrategia de evolucao do aplicativo.",
],
stack: [
"Tauri",
"Rust",
"TypeScript",
"Desktop apps",
"System integration",
],
ctaLabel: "Conversar sobre Tauri e Rust",
ctaHref:
"https://wa.me/5547992742365?text=Quero%20conversar%20sobre%20Tauri%20e%20Rust.",
relatedCaseIds: ["gcc-installer"],
},
{
slug: "integracao-hardware-software-iot",
navLabel: "Hardware + Software + IoT",
eyebrow: "Produtos conectados",
seoTitle: "Integracao Hardware + Software e IoT | Evandro Fonseca Junior",
metaDescription:
"Integracao hardware + software, IoT e fluxos conectados para produtos e operacoes criticas. Da coleta de dados ao painel, com APIs, telemetria e arquitetura de software confiavel.",
headline:
"Integracao hardware + software para produtos conectados que precisam operar de verdade.",
intro:
"Atuo na camada que costuma quebrar primeiro: a fronteira entre dispositivo, backend, painel e operacao. Estruturo esses fluxos para que dados, comando e observabilidade funcionem como produto, nao como remendo.",
audience: [
"Empresas com dispositivos, sensores ou equipamentos conectados ao software.",
"Operacoes que dependem de telemetria, dashboards e troca de dados em tempo real.",
"Times que precisam ligar produto digital a processos fisicos com confiabilidade.",
],
outcomes: [
"Fluxos mais robustos entre dispositivo, API, banco e interface.",
"Menos perda de contexto entre operacao fisica e produto digital.",
"Arquitetura com observabilidade e manutencao mais previsivel.",
"Base melhor para evoluir IA, automacao ou analytics em cima dos dados.",
],
process: [
"Mapeamento de sinal, eventos, integracoes e pontos de falha.",
"Definicao de contratos entre hardware, backend e interfaces.",
"Implementacao do fluxo com testes e tratamento de excecoes relevantes.",
"Entrega com documentacao de operacao, limites e proximo passo.",
],
stack: ["IoT", "WebSockets", "APIs", "Dashboards", "Integracao de sistemas"],
ctaLabel: "Falar sobre integracao e IoT",
ctaHref:
"https://wa.me/5547992742365?text=Quero%20falar%20sobre%20integracao%20hardware%20%2B%20software%20e%20IoT.",
relatedCaseIds: ["twins-lab"],
},
{
slug: "arquitetura-de-software",
navLabel: "Arquitetura de Software",
eyebrow: "Diagnostico e decisao",
seoTitle:
"Arquitetura de Software e Consultoria Tecnica | Evandro Fonseca Junior",
metaDescription:
"Arquitetura de software para reduzir complexidade, destravar evolucao e organizar integracoes criticas. Consultoria tecnica para produtos que precisam de direcao antes de mais codigo.",
headline:
"Arquitetura de software para times que precisam de clareza antes de acelerar.",
intro:
"Quando o problema nao e falta de codigo, mas excesso de acoplamento, retrabalho e decisao mal distribuida, a arquitetura certa devolve velocidade, leitura e confianca ao time.",
audience: [
"Produtos que cresceram rapido e perderam legibilidade tecnica.",
"Times travados entre refatorar tudo ou seguir acumulando divida.",
"Operacoes com muitas dependencias, integracoes ou regras sensiveis.",
],
outcomes: [
"Mapa claro dos gargalos e do risco tecnico real.",
"Prioridades de arquitetura ligadas a impacto de negocio.",
"Plano executavel de refatoracao, estabilizacao ou reestruturacao.",
"Mais qualidade de decisao entre produto, engenharia e operacao.",
],
process: [
"Leitura da base, fluxos, integracoes e dores do time.",
"Diagnostico escrito com pontos de risco, oportunidades e trade-offs.",
"Proposta de arquitetura alvo e plano de transicao realista.",
"Acompanhamento da execucao ou handoff estruturado para o time interno.",
],
stack: [
"Arquitetura de software",
"APIs",
"Performance",
"Integracoes",
"Sistemas criticos",
],
ctaLabel: "Solicitar diagnostico arquitetural",
ctaHref:
"https://wa.me/5547992742365?text=Quero%20um%20diagnostico%20de%20arquitetura%20de%20software.",
relatedCaseIds: ["nutriscan", "twins-lab", "reddit-explorer"],
},
];
export const caseStudies: CaseStudyContent[] = [
{
id: "nutriscan",
title: "NutriScan",
subtitle: "PWA com Next.js para analise nutricional e operacao offline",
summary:
"Projeto em Next.js 14 com App Router, historico de uso, scanner de codigo de barras e cache estrategico para reduzir dependencia de rede.",
impact:
"Bom exemplo de engenharia de software orientada a produto, performance e experiencia de uso em contexto real.",
stack: ["Next.js 14", "TypeScript", "MongoDB", "PWA", "Tailwind CSS"],
demoUrl: "https://nutriscan.evandro.dev.br",
githubUrl: "https://github.com/evandrodevbr/nutriscan",
},
{
id: "twins-lab",
title: "Twins Lab",
subtitle: "Plataforma IoT industrial com dados em tempo real",
summary:
"Produto com dashboards, autenticacao, WebSockets e camada de IA para monitoramento de dispositivos e operacao industrial conectada.",
impact:
"Case forte para integracao hardware + software, telemetria e sistemas criticos com atualizacao em tempo real.",
stack: ["Next.js", "TypeScript", "NextAuth", "WebSockets", "OpenRouter AI"],
demoUrl: "https://twins.evandro.dev.br",
githubUrl: "https://github.com/evandrodevbr/twins-lab",
},
{
id: "reddit-explorer",
title: "Reddit Posts Explorer",
subtitle: "Aplicacao orientada a dados com API, cache e busca",
summary:
"Projeto em Astro e MongoDB para explorar conteudo com filtros, ordenacao e cache, mantendo boa experiencia em consultas frequentes.",
impact:
"Mostra capacidade de desenhar consumo de API, camada de persistencia e experiencia de busca sem sobrecarregar a aplicacao.",
stack: ["Astro.js", "MongoDB", "Mongoose", "TypeScript", "Reddit API"],
githubUrl: "https://github.com/evandrodevbr/reddit-search",
},
{
id: "sc-plus",
title: "SC Plus Aquicultura",
subtitle: "Site institucional em Next.js com foco em performance e SEO",
summary:
"Landing page em Next.js 15 otimizada para Core Web Vitals, metadata e estrutura tecnica de indexacao.",
impact:
"Exemplo de projeto em que clareza de mensagem, velocidade e SEO tecnico trabalham juntos.",
stack: ["Next.js 15", "React 19", "Tailwind CSS 4", "TypeScript"],
demoUrl: "https://scplus.evandro.dev.br",
githubUrl: "https://github.com/evandrodevbr/scplus",
},
{
id: "gcc-installer",
title: "MinGW GCC Installer",
subtitle: "Ferramenta desktop para automacao de ambiente de desenvolvimento",
summary:
"Aplicacao em Python para simplificar setup local no Windows, com download assincrono, checagem de compatibilidade e integracao com PATH.",
impact:
"Mostra familiaridade com software desktop, integracao com sistema operacional e automacao de setup tecnico.",
stack: ["Python", "Tkinter", "Win32 API", "Threading", "7-Zip"],
githubUrl: "https://github.com/evandrodevbr/gcc-installer",
},
];
export const serviceHubLinks = servicePages.map((service) => ({
href: `/servicos/${service.slug}`,
label: service.navLabel,
description: service.metaDescription,
}));
export function getServicePage(slug: string) {
return servicePages.find((service) => service.slug === slug);
}
export function getRelatedCases(caseIds: string[]) {
return caseStudies.filter((caseStudy) => caseIds.includes(caseStudy.id));
}
+27
View File
@@ -68,6 +68,33 @@ const nextConfig: NextConfig = {
},
],
},
{
source: "/fonts/(.*)",
headers: [
{
key: "Cache-Control",
value: "public, max-age=31536000, immutable",
},
],
},
{
source: "/assets/(.*)",
headers: [
{
key: "Cache-Control",
value: "public, max-age=31536000, immutable",
},
],
},
{
source: "/logos/(.*)",
headers: [
{
key: "Cache-Control",
value: "public, max-age=31536000, immutable",
},
],
},
];
},