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 { motion, type Variants, useReducedMotion } from "framer-motion";
import profilePhoto from "@/public/edev.jpeg"; import profilePhoto from "@/public/edev.jpeg";
@@ -8,39 +8,34 @@ import { LazyRevealImage } from "./animations/LazyRevealImage";
const STORY_DATA = [ const STORY_DATA = [
{ {
id: "HARDWARE_BRIDGE", label: "Hardware + software",
title: "Do físico ao digital", title: "Do sinal fisico ao produto digital",
content: 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", label: "Execution",
title: "Entrega sob restrições", title: "Entrega clara mesmo sob restricao",
content: 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: 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: 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", title: "Ownership ponta a ponta",
description: description:
"Responsabilidade que atravessa camadas: do pulso elétrico no hardware até a renderização do último componente na tela do usuário.", "Responsabilidade que cobre discovery, implementacao, qualidade e operacao.",
},
{
title: "Security & Safety",
description:
"Segurança e resiliência tratadas como base. Sistemas desenhados para falhar graciosamente e proteger dados do dispositivo à nuvem.",
}, },
]; ];
@@ -84,9 +79,9 @@ export default function About() {
return ( return (
<Section <Section
id="about" id="about"
eyebrow="WHOAMI.sys" eyebrow="Operating context"
title="Engenharia orientada por clareza, nao por ruido" title="Engenharia de software, arquitetura e integracoes complexas"
description="Transformo complexidade tecnica em sistemas estaveis, operaveis e alinhados ao negocio." 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 disableChildrenAnimation
aside={ aside={
<div className="mt-sp-4 ds-metric"> <div className="mt-sp-4 ds-metric">
@@ -108,14 +103,14 @@ export default function About() {
> >
{STORY_DATA.map((step) => ( {STORY_DATA.map((step) => (
<motion.article <motion.article
key={step.id} key={step.title}
className="ds-card" className="ds-card"
variants={cardVariants} variants={cardVariants}
whileHover={cardHover} whileHover={cardHover}
whileTap={shouldReduceMotion ? undefined : { scale: 0.99 }} whileTap={shouldReduceMotion ? undefined : { scale: 0.99 }}
transition={{ type: "spring", stiffness: 180, damping: 18 }} 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} /> <DrawLine delay={0.03} />
<h3 className="ds-card-title">{step.title}</h3> <h3 className="ds-card-title">{step.title}</h3>
<p className="ds-copy">{step.content}</p> <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)]" className="mb-sp-6 h-[220px] w-full overflow-hidden rounded-[6px] border border-[var(--border)]"
imageClassName="object-cover" imageClassName="object-cover"
/> />
<div className="ds-kicker">System Signature</div> <div className="ds-kicker">Technical profile</div>
<DrawLine delay={0.05} /> <DrawLine delay={0.05} />
<h3 className="ds-card-title">Base tecnica e postura de entrega</h3> <h3 className="ds-card-title">Base tecnica e postura de entrega</h3>
<p className="ds-copy"> <p className="ds-copy">
Evandro Fonseca Junior. Engenheiro de Software Full-Stack com foco Sou engenheiro de software full-stack com foco em integrar camadas
na convergência entre hardware e aplicações web modernas. que normalmente ficam separadas: hardware, servicos, operacao e
Especialista em TypeScript, Node.js, ecossistema React e automação interface. Meu trabalho e dar forma clara ao que costuma nascer
de dispositivos físicos. fragmentado.
</p> </p>
<div className="ds-grid-2 mt-sp-6"> <div className="mt-sp-6 space-y-sp-4">
{VALUES_DATA.map((value) => ( {PROFILE_POINTS.map((point) => (
<div <div
key={value.title} key={point.title}
className="border-t border-[var(--border)] pt-sp-4" className="border-t border-[var(--border)] pt-sp-4"
> >
<div className="text-[11px] font-medium uppercase tracking-[0.08em]"> <div className="text-[12px] font-medium tracking-[0.02em]">
{value.title} {point.title}
</div> </div>
<p className="ds-copy !mt-sp-2">{value.description}</p> <p className="ds-copy !mt-sp-2">{point.description}</p>
</div> </div>
))} ))}
</div> </div>
@@ -163,3 +158,5 @@ export default function About() {
</Section> </Section>
); );
} }
+44 -31
View File
@@ -10,6 +10,8 @@ const WHATSAPP_NUMBER = "5547992742365";
export default function Contact() { export default function Contact() {
const shouldReduceMotion = useReducedMotion(); 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({ const [formData, setFormData] = useState({
name: "", name: "",
email: "", email: "",
@@ -55,9 +57,9 @@ export default function Contact() {
return ( return (
<Section <Section
id="contact" id="contact"
eyebrow="CONTACT_PROTOCOL.init" eyebrow="Contact"
title="Vamos discutir o proximo sistema" title="Consultoria tecnica para software, arquitetura e integracoes criticas"
description="Descreva o contexto, o gargalo ou o produto. O contato abre direto no WhatsApp com briefing estruturado." 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 dark
disableChildrenAnimation disableChildrenAnimation
> >
@@ -82,18 +84,22 @@ export default function Contact() {
whileTap={shouldReduceMotion ? undefined : { scale: 0.995 }} whileTap={shouldReduceMotion ? undefined : { scale: 0.995 }}
transition={{ type: "spring", stiffness: 180, damping: 18 }} transition={{ type: "spring", stiffness: 180, damping: 18 }}
> >
<div className="ds-kicker">Response SLA</div> <div className="ds-kicker">Availability</div>
<DrawLine delay={0.04} /> <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"> <p className="ds-copy">
Ideal para discovery tecnico, revisao de arquitetura, rescue de 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> </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>WhatsApp: +55 47 99274-2365</div>
<div>E-mail: evandrofonsecajunior@gmail.com</div> <div>E-mail: evandrofonsecajunior@gmail.com</div>
<div>Base: Garuva, Santa Catarina | atuacao remota</div> <div>Base: Garuva, Santa Catarina | atuacao remota</div>
</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.div>
<motion.form <motion.form
@@ -111,54 +117,60 @@ export default function Contact() {
transition={{ type: "spring", stiffness: 180, damping: 18 }} transition={{ type: "spring", stiffness: 180, damping: 18 }}
> >
<div> <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 Nome completo
</label> </label>
<input <input
id="contact-name" id="contact-name"
name="name"
required required
type="text" type="text"
autoComplete="name"
value={formData.name} value={formData.name}
onChange={(e) => onChange={(e) =>
setFormData({ ...formData, name: e.target.value }) 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" className={fieldClassName}
style={{
color: "#fff",
backgroundColor: "rgba(255,255,255,0.06)",
}}
/> />
</div> </div>
<div> <div>
<label htmlFor="contact-email" className="ds-kicker block"> <label
E-mail corporativo htmlFor="contact-email"
className="block text-[12px] font-medium text-[rgba(255,255,255,.85)]"
>
E-mail
</label> </label>
<input <input
id="contact-email" id="contact-email"
name="email"
required required
type="email" type="email"
autoComplete="email"
value={formData.email} value={formData.email}
onChange={(e) => onChange={(e) =>
setFormData({ ...formData, email: e.target.value }) 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" className={fieldClassName}
style={{
color: "#fff",
backgroundColor: "rgba(255,255,255,0.06)",
}}
/> />
</div> </div>
<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 Tipo de projeto
</label> </label>
<select <select
id="contact-subject" id="contact-subject"
name="subject"
value={formData.subject} value={formData.subject}
onChange={(e) => onChange={(e) =>
setFormData({ ...formData, subject: e.target.value }) 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" }} style={{ color: "#fff", backgroundColor: "#1a1a1a" }}
> >
<option value="full-stack">Desenvolvimento Full-Stack</option> <option value="full-stack">Desenvolvimento Full-Stack</option>
@@ -168,33 +180,34 @@ export default function Contact() {
</select> </select>
</div> </div>
<div> <div>
<label htmlFor="contact-message" className="ds-kicker block"> <label
Detalhes do projeto htmlFor="contact-message"
className="block text-[12px] font-medium text-[rgba(255,255,255,.85)]"
>
Contexto do projeto
</label> </label>
<textarea <textarea
id="contact-message" id="contact-message"
name="message"
required required
rows={6} rows={6}
autoComplete="off"
value={formData.message} value={formData.message}
onChange={(e) => onChange={(e) =>
setFormData({ ...formData, message: e.target.value }) 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" className={`${fieldClassName} resize-none`}
style={{
color: "#fff",
backgroundColor: "rgba(255,255,255,0.06)",
}}
/> />
</div> </div>
<motion.button <motion.button
type="submit" 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 }} whileHover={shouldReduceMotion ? undefined : { scale: 1.03 }}
whileTap={shouldReduceMotion ? undefined : { scale: 0.97 }} whileTap={shouldReduceMotion ? undefined : { scale: 0.97 }}
transition={{ type: "spring", stiffness: 260, damping: 20 }} transition={{ type: "spring", stiffness: 260, damping: 20 }}
> >
Iniciar protocolo de contato Enviar briefing pelo WhatsApp
</motion.button> </motion.button>
</motion.form> </motion.form>
</motion.div> </motion.div>
+18 -22
View File
@@ -1,11 +1,10 @@
"use client"; "use client";
import { useMemo } from "react"; import { useMemo } from "react";
import { motion, type Variants, useReducedMotion } from "framer-motion"; import { motion, type Variants, useReducedMotion } from "framer-motion";
import { usePortfolioData } from "@/lib/usePortfolioData"; import { usePortfolioData } from "@/lib/usePortfolioData";
import { Section } from "./Section"; import { Section } from "./Section";
import { DrawLine } from "./animations/DrawLine"; import { DrawLine } from "./animations/DrawLine";
import { CounterValue } from "./animations/CounterValue";
import { SectionLoading } from "./SectionLoading"; import { SectionLoading } from "./SectionLoading";
export default function Experience() { export default function Experience() {
@@ -24,11 +23,11 @@ export default function Experience() {
}, [data]); }, [data]);
if (loading) { if (loading) {
return <SectionLoading message="Loading system logs" />; return <SectionLoading message="Loading selected experience" />;
} }
if (error || !data) { 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 = { const listVariants: Variants = {
@@ -58,9 +57,9 @@ export default function Experience() {
return ( return (
<Section <Section
id="experience" id="experience"
eyebrow="SYSTEM_LOGS.history" eyebrow="Selected experience"
title="Trajetoria focada em produto, legado e crescimento sustentavel" title="Experiencia em engenharia de software e sistemas criticos"
description="Experiencias recentes onde combinei entrega rapida com responsabilidade arquitetural." description="Projetos e contextos em que combinei arquitetura de software, integracoes complexas e entrega com responsabilidade operacional."
disableChildrenAnimation disableChildrenAnimation
> >
<motion.div <motion.div
@@ -86,33 +85,28 @@ export default function Experience() {
whileTap={shouldReduceMotion ? undefined : { scale: 0.995 }} whileTap={shouldReduceMotion ? undefined : { scale: 0.995 }}
transition={{ type: "spring", stiffness: 170, damping: 20 }} 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 className="flex flex-col gap-sp-4 lg:flex-row lg:items-start lg:justify-between">
<div> <div className="max-w-[720px]">
<div className="ds-kicker">{exp.period}</div> <div className="ds-kicker">{exp.period}</div>
<DrawLine delay={0.04} /> <DrawLine delay={0.04} />
<h3 className="ds-card-title">{exp.title}</h3> <h3 className="ds-card-title">{exp.title}</h3>
<div className="mt-sp-3 text-[11px] uppercase tracking-[0.1em] text-meta"> <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} {exp.company} • {exp.type}
</div> </p>
</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>
</div> </div>
<div className="ds-metric">{exp.location}</div>
</div> </div>
<p className="ds-copy">{exp.description}</p> <p className="ds-copy">{exp.description}</p>
<ul className="ds-list"> <ul className="ds-list">
{exp.achievements?.map((achievement) => ( {exp.achievements?.map((achievement) => (
<li key={achievement}>{achievement}</li> <li key={achievement}>{achievement}</li>
))} ))}
</ul> </ul>
<ul className="ds-tags mt-sp-6"> <ul className="ds-tags mt-sp-6">
{exp.technologies?.map((tech) => ( {exp.technologies?.slice(0, 6).map((tech) => (
<li key={tech} className="ds-tag"> <li key={tech} className="ds-tag">
{tech} {tech}
</li> </li>
@@ -124,3 +118,5 @@ export default function Experience() {
</Section> </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 { export interface SocialLink {
name: string; // Display name (e.g., "GitHub") name: string;
href: string; // Full URL href: string;
icon: string; // SVG path or icon identifier icon: string;
ariaLabel: string; // Accessibility label ariaLabel: string;
} }
// Social links data
const socialLinks: SocialLink[] = [ const socialLinks: SocialLink[] = [
{ {
name: "GitHub", 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() { export default function Footer() {
const currentYear = new Date().getFullYear(); const currentYear = new Date().getFullYear();
@@ -46,22 +43,26 @@ export default function Footer() {
<footer className="ds-section"> <footer className="ds-section">
<div className="ds-section-shell"> <div className="ds-section-shell">
<div className="ds-grid-2"> <div className="ds-grid-2">
{/* Contact & Legal */}
<div className="ds-card flex flex-col justify-between"> <div className="ds-card flex flex-col justify-between">
<div> <div>
<div className="ds-kicker">SYSTEM_INFO</div> <div className="ds-kicker">Contato</div>
<span className="ds-draw-line" /> <span className="ds-draw-line" />
<h3 className="ds-card-title"> <h3 className="ds-card-title">
Transformando complexidade técnica em sistemas estáveis Engenharia com menos ruido e mais clareza de execucao
</h3> </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"> <div className="mt-sp-6">
<p className="text-[12px] uppercase tracking-wider text-[var(--meta)] mb-2"> <p className="mb-2 text-[12px] tracking-[0.02em] text-[var(--meta)]">
Contato E-mail direto
</p> </p>
<a <a
href="mailto:[email protected]" 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 evandro@dev.br
</a> </a>
@@ -70,21 +71,20 @@ export default function Footer() {
<div className="mt-sp-8"> <div className="mt-sp-8">
<div className="ds-metric"> <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 Garuva, SC | Remote worldwide
</div> </div>
<p className="mt-sp-4 text-[12px] text-[var(--meta)]"> <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> </p>
</div> </div>
</div> </div>
{/* Social Links */}
<div className="ds-card flex flex-col justify-between"> <div className="ds-card flex flex-col justify-between">
<div> <div>
<div className="ds-kicker">SOCIAL_LINKS</div> <div className="ds-kicker">Presenca digital</div>
<span className="ds-draw-line" /> <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"> <div className="mt-sp-6 flex flex-col gap-sp-4">
{socialLinks.map((link) => ( {socialLinks.map((link) => (
@@ -94,27 +94,42 @@ export default function Footer() {
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
aria-label={link.ariaLabel} 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"> <div className="flex items-center gap-sp-3">
<svg <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" fill="currentColor"
viewBox="0 0 24 24" viewBox="0 0 24 24"
aria-hidden="true" aria-hidden="true"
> >
<path d={link.icon} /> <path d={link.icon} />
</svg> </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} {link.name}
</span> </span>
</div> </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> </span>
</a> </a>
))} ))}
</div> </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>
<div className="mt-sp-8 ds-metric">Open to collaboration</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"; const GA_MEASUREMENT_ID = "G-QTT9QT0T3S";
export default function GoogleAnalytics() { export default function GoogleAnalytics() {
if (process.env.NODE_ENV !== "production") {
return null;
}
return ( return (
<> <>
<Script src="/sys/libs/vendor-utils.js" strategy="afterInteractive" /> <Script src="/sys/libs/vendor-utils.js" strategy="lazyOnload" />
<Script id="google-analytics" strategy="afterInteractive"> <Script id="google-analytics" strategy="lazyOnload">
{` {`
window.dataLayer = window.dataLayer || []; window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);} 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 = [ const PROOF_ITEMS = [
{ {
label: "Especialidade", label: "Quem eu ajudo",
value: 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: 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: 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="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 <motion.div
initial={shouldReduceMotion ? { opacity: 0 } : { opacity: 0, y: 24 }} initial={shouldReduceMotion ? { opacity: 0 } : { opacity: 0, y: 24 }}
animate={shouldReduceMotion ? { opacity: 1 } : { opacity: 1, y: 0 }} animate={shouldReduceMotion ? { opacity: 1 } : { opacity: 1, y: 0 }}
transition={{ duration: 0.8, ease: [0.16, 1, 0.3, 1] }} 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="max-w-[760px]">
<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"> <div className="mb-sp-5 flex flex-wrap items-center gap-sp-3">
<motion.span <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">
className="h-2 w-2 bg-[var(--slash)]" <motion.span
animate={ className="h-2 w-2 bg-[var(--slash)]"
shouldReduceMotion animate={
? { opacity: 1 } shouldReduceMotion
: { opacity: [1, 0.25, 1] } ? { opacity: 1 }
} : { opacity: [1, 0.25, 1] }
transition={ }
shouldReduceMotion transition={
? { duration: 0 } shouldReduceMotion
: { ? { duration: 0 }
duration: 1.5, : {
ease: "easeInOut", duration: 1.5,
repeat: Number.POSITIVE_INFINITY, 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 className="font-mono text-[10px] uppercase tracking-[0.12em] text-[rgba(244,242,237,.82)]">
</span> 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> </div>
<motion.p <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={ initial={
shouldReduceMotion ? { opacity: 0 } : { opacity: 0, y: 12 } shouldReduceMotion ? { opacity: 0 } : { opacity: 0, y: 12 }
} }
@@ -118,19 +129,18 @@ export default function Hero() {
ease: [0.16, 1, 0.3, 1], ease: [0.16, 1, 0.3, 1],
}} }}
> >
Software engineering for products, operations & critical flows Evandro Fonseca Junior | Engenheiro de software full-stack
</motion.p> </motion.p>
<h1 <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]" 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="evandro.dev.br" aria-label="Engenharia para sistemas que nao podem falhar"
> >
Engenharia de software para produtos conectados, interfaces de Engenharia de software para sistemas que nao podem falhar.
alta performance e sistemas criticos.
</h1> </h1>
<motion.p <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={ initial={
shouldReduceMotion ? { opacity: 0 } : { opacity: 0, y: 16 } shouldReduceMotion ? { opacity: 0 } : { opacity: 0, y: 16 }
} }
@@ -139,15 +149,40 @@ export default function Hero() {
} }
transition={{ transition={{
duration: 0.8, duration: 0.8,
delay: 0.16, delay: 0.14,
ease: [0.16, 1, 0.3, 1], ease: [0.16, 1, 0.3, 1],
}} }}
> >
Da integracao com hardware e APIs complexas ao frontend moderno, Ajudo empresas com produtos conectados, integracoes complexas e
construo sistemas estaveis, operaveis e prontos para crescer sem operacoes criticas a transformar arquitetura confusa em software
sacrificar clareza tecnica. estavel, mais rapido de evoluir e muito mais facil de operar.
</motion.p> </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 <motion.div
className="mt-sp-8 flex flex-wrap items-center gap-sp-4" className="mt-sp-8 flex flex-wrap items-center gap-sp-4"
initial={ initial={
@@ -158,32 +193,32 @@ export default function Hero() {
} }
transition={{ transition={{
duration: 0.8, duration: 0.8,
delay: 0.24, delay: 0.3,
ease: [0.16, 1, 0.3, 1], ease: [0.16, 1, 0.3, 1],
}} }}
> >
<motion.a <motion.a
href={`https://wa.me/5547992742365?text=${encodeURIComponent(greeting)}`} 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 }} whileHover={shouldReduceMotion ? undefined : { scale: 1.02 }}
whileTap={shouldReduceMotion ? undefined : { scale: 0.98 }} whileTap={shouldReduceMotion ? undefined : { scale: 0.98 }}
> >
Iniciar projeto Agendar conversa tecnica
</motion.a> </motion.a>
<motion.a <motion.a
href="#projects" href="/cases"
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)]" 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 }} whileHover={shouldReduceMotion ? undefined : { scale: 1.02 }}
whileTap={shouldReduceMotion ? undefined : { scale: 0.98 }} whileTap={shouldReduceMotion ? undefined : { scale: 0.98 }}
> >
Ver projetos Ver casos selecionados
</motion.a> </motion.a>
<a <a
href="/assets/Evandro_Fonseca_Junior_CV.pdf" href="/assets/Evandro_Fonseca_Junior_CV.pdf"
download 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 Download CV
</a> </a>
@@ -199,15 +234,15 @@ export default function Hero() {
} }
transition={{ transition={{
duration: 0.8, duration: 0.8,
delay: 0.32, delay: 0.38,
ease: [0.16, 1, 0.3, 1], 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)]" /> <span className="h-2 w-2 rounded-full bg-[var(--slash)] shadow-[0_0_8px_var(--slash)]" />
Garuva, SC / Brasil Garuva, SC / Brasil
</div> </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)]" /> <span className="h-[1px] w-6 bg-[rgba(255,255,255,.2)]" />
Next.js, Tauri, Rust, Node.js Next.js, Tauri, Rust, Node.js
</div> </div>
@@ -222,7 +257,7 @@ export default function Hero() {
? "noopener noreferrer" ? "noopener noreferrer"
: undefined : 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} aria-label={link.label}
> >
<svg <svg
@@ -232,8 +267,7 @@ export default function Hero() {
fill={link.stroke ? "none" : "currentColor"} fill={link.stroke ? "none" : "currentColor"}
stroke={link.stroke ? "currentColor" : "none"} stroke={link.stroke ? "currentColor" : "none"}
strokeWidth={link.stroke ? "2" : undefined} strokeWidth={link.stroke ? "2" : undefined}
role="img" aria-hidden="true"
aria-label={link.label}
> >
{link.icon} {link.icon}
</svg> </svg>
@@ -244,28 +278,26 @@ export default function Hero() {
</div> </div>
<motion.aside <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={ initial={
shouldReduceMotion ? { opacity: 0 } : { opacity: 0, x: 24 } shouldReduceMotion ? { opacity: 0 } : { opacity: 0, x: 24 }
} }
animate={shouldReduceMotion ? { opacity: 1 } : { opacity: 1, x: 0 }} animate={shouldReduceMotion ? { opacity: 1 } : { opacity: 1, x: 0 }}
transition={{ duration: 0.9, delay: 0.22, ease: [0.16, 1, 0.3, 1] }} 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-x-0 top-0 h-px bg-[linear-gradient(90deg,transparent,rgba(204,255,0,0.8),transparent)]" />
<div className="absolute inset-y-0 left-0 w-px bg-[linear-gradient(180deg,rgba(204,255,0,0.4),transparent)]" />
<div className="flex items-start justify-between gap-sp-4 border-b border-[rgba(255,255,255,0.08)] pb-sp-5"> <div className="border-b border-[rgba(255,255,255,0.08)] pb-sp-5">
<div> <div className="font-mono text-[10px] uppercase tracking-[0.14em] text-[var(--slash)]">
<div className="font-mono text-[10px] uppercase tracking-[0.18em] text-[var(--slash)]"> Why this positioning works
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> </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>
<div className="mt-sp-6 space-y-sp-5"> <div className="mt-sp-6 space-y-sp-5">
@@ -281,68 +313,26 @@ export default function Hero() {
} }
transition={{ transition={{
duration: 0.65, duration: 0.65,
delay: 0.28 + index * 0.08, delay: 0.3 + index * 0.08,
ease: [0.16, 1, 0.3, 1], 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} {item.label}
</div> </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} {item.value}
</p> </p>
</motion.article> </motion.article>
))} ))}
</div> </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.aside>
</motion.div> </motion.div>
</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> </section>
); );
} }
+33 -35
View File
@@ -5,7 +5,6 @@ import { motion, type Variants, useReducedMotion } from "framer-motion";
import { usePortfolioData } from "@/lib/usePortfolioData"; import { usePortfolioData } from "@/lib/usePortfolioData";
import { Section } from "./Section"; import { Section } from "./Section";
import { DrawLine } from "./animations/DrawLine"; import { DrawLine } from "./animations/DrawLine";
import { CounterValue } from "./animations/CounterValue";
import { SectionLoading } from "./SectionLoading"; import { SectionLoading } from "./SectionLoading";
export default function Projects() { export default function Projects() {
@@ -18,11 +17,11 @@ export default function Projects() {
); );
if (loading) { if (loading) {
return <SectionLoading dark message="Loading featured projects" />; return <SectionLoading dark message="Loading selected work" />;
} }
if (error) { 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 = { const gridVariants: Variants = {
@@ -52,9 +51,9 @@ export default function Projects() {
return ( return (
<Section <Section
id="projects" id="projects"
eyebrow="PRODUCAO_INTELECTUAL" eyebrow="Selected work"
title="Projetos com recorte tecnico claro e impacto observavel" title="Projetos em Next.js, Tauri, Rust e integracao de sistemas"
description="Selecao de produtos e ferramentas onde performance, confiabilidade e clareza de arquitetura foram requisitos reais." description="Cases e produtos que mostram minha atuacao em engenharia de software, consultoria tecnica e construcao de sistemas de alta exigencia."
dark dark
disableChildrenAnimation disableChildrenAnimation
> >
@@ -82,19 +81,29 @@ export default function Projects() {
transition={{ type: "spring", stiffness: 170, damping: 18 }} transition={{ type: "spring", stiffness: 170, damping: 18 }}
> >
<div className="flex items-start justify-between gap-sp-6"> <div className="flex items-start justify-between gap-sp-6">
<div> <div className="max-w-[560px]">
<div className="ds-kicker">{project.category}</div> <div className="ds-kicker">{project.category}</div>
<DrawLine delay={0.05} /> <DrawLine delay={0.05} />
<h3 className="ds-card-title">{project.title}</h3> <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 && (
{project.subtitle} <p className="mt-sp-3 text-[13px] leading-6 text-[rgba(255,255,255,.56)]">
</p> {project.subtitle}
</p>
)}
</div> </div>
<span className="ds-tag">{project.status}</span> <span className="ds-tag">{project.status}</span>
</div> </div>
<p className="ds-copy">{project.description}</p> <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"> <div className="mt-sp-6 flex flex-wrap gap-sp-3">
{project.githubUrl && ( {project.githubUrl && (
<motion.a <motion.a
@@ -106,7 +115,7 @@ export default function Projects() {
whileTap={shouldReduceMotion ? undefined : { scale: 0.97 }} whileTap={shouldReduceMotion ? undefined : { scale: 0.97 }}
transition={{ type: "spring", stiffness: 260, damping: 20 }} transition={{ type: "spring", stiffness: 260, damping: 20 }}
> >
GitHub Ver codigo
</motion.a> </motion.a>
)} )}
{project.demoUrl && ( {project.demoUrl && (
@@ -119,36 +128,25 @@ export default function Projects() {
whileTap={shouldReduceMotion ? undefined : { scale: 0.97 }} whileTap={shouldReduceMotion ? undefined : { scale: 0.97 }}
transition={{ type: "spring", stiffness: 260, damping: 20 }} transition={{ type: "spring", stiffness: 260, damping: 20 }}
> >
Live demo Abrir projeto
</motion.a> </motion.a>
)} )}
</div> </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.article>
))} ))}
</motion.div> </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> </Section>
); );
} }
+14 -15
View File
@@ -1,10 +1,9 @@
"use client"; "use client";
import { motion, type Variants, useReducedMotion } from "framer-motion"; import { motion, type Variants, useReducedMotion } from "framer-motion";
import { usePortfolioData } from "@/lib/usePortfolioData"; import { usePortfolioData } from "@/lib/usePortfolioData";
import { Section } from "./Section"; import { Section } from "./Section";
import { DrawLine } from "./animations/DrawLine"; import { DrawLine } from "./animations/DrawLine";
import { CounterValue } from "./animations/CounterValue";
import { SectionLoading } from "./SectionLoading"; import { SectionLoading } from "./SectionLoading";
export default function Skills() { export default function Skills() {
@@ -12,11 +11,11 @@ export default function Skills() {
const shouldReduceMotion = useReducedMotion(); const shouldReduceMotion = useReducedMotion();
if (loading) { if (loading) {
return <SectionLoading dark message="Loading skill registry" />; return <SectionLoading dark message="Loading core stack" />;
} }
if (error || !data) { 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 = { const gridVariants: Variants = {
@@ -46,9 +45,9 @@ export default function Skills() {
return ( return (
<Section <Section
id="skills" id="skills"
eyebrow="MODULE_REGISTRY.lib" eyebrow="Core stack"
title="Stack modular para produto, operacao e escala" title="Next.js, Tauri, Rust e stack para produtos criticos"
description="Ferramentas escolhidas por previsibilidade operacional, velocidade de entrega e facilidade de manutencao." description="Tecnologias e fundamentos que uso em engenharia de software, arquitetura de software, IoT e integracoes complexas."
dark dark
disableChildrenAnimation disableChildrenAnimation
> >
@@ -59,7 +58,7 @@ export default function Skills() {
viewport={{ once: true, amount: 0.2 }} viewport={{ once: true, amount: 0.2 }}
variants={gridVariants} variants={gridVariants}
> >
{data.skills.technical.map((category, index) => ( {data.skills.technical.map((category) => (
<motion.article <motion.article
key={category.category} key={category.category}
className="ds-card h-full" className="ds-card h-full"
@@ -75,14 +74,12 @@ export default function Skills() {
whileTap={shouldReduceMotion ? undefined : { scale: 0.99 }} whileTap={shouldReduceMotion ? undefined : { scale: 0.99 }}
transition={{ type: "spring", stiffness: 180, damping: 18 }} transition={{ type: "spring", stiffness: 180, damping: 18 }}
> >
<div className="flex items-center justify-between gap-sp-4"> <h3 className="ds-card-title !mt-0">{category.category}</h3>
<div className="ds-kicker">ID_0{index + 1}X</div>
<div className="ds-metric">
<CounterValue value={category.skills.length} suffix=" items" />
</div>
</div>
<DrawLine delay={0.05} /> <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"> <ul className="ds-tags">
{category.skills.map((skill) => ( {category.skills.map((skill) => (
<li key={skill} className="ds-tag"> <li key={skill} className="ds-tag">
@@ -96,3 +93,5 @@ export default function Skills() {
</Section> </Section>
); );
} }
+63 -26
View File
@@ -1,6 +1,8 @@
"use client"; "use client";
import Link from "next/link";
import { motion, type Variants, useReducedMotion } from "framer-motion"; import { motion, type Variants, useReducedMotion } from "framer-motion";
import { serviceHubLinks } from "@/lib/site-content";
import { Section } from "./Section"; import { Section } from "./Section";
import { DrawLine } from "./animations/DrawLine"; import { DrawLine } from "./animations/DrawLine";
@@ -87,8 +89,7 @@ function TierCard({ tier }: Readonly<{ tier: Tier }>) {
return ( return (
<motion.article <motion.article
tabIndex={0} 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 ${
className={`ds-card h-full relative outline-none ${
tier.featured ? "border-[var(--slash)] shadow-sm" : "" tier.featured ? "border-[var(--slash)] shadow-sm" : ""
}`} }`}
style={{ style={{
@@ -105,42 +106,50 @@ function TierCard({ tier }: Readonly<{ tier: Tier }>) {
whileTap={shouldReduceMotion ? undefined : { scale: 0.99 }} whileTap={shouldReduceMotion ? undefined : { scale: 0.99 }}
transition={{ type: "spring", stiffness: 180, damping: 18 }} transition={{ type: "spring", stiffness: 180, damping: 18 }}
> >
<div className="flex justify-between items-start"> <div className="flex min-w-0 items-start justify-between gap-sp-4">
<span className="ds-kicker">{tier.badge}</span> <span className="ds-kicker max-md:text-[10px] max-md:leading-[1.6]">
<span className="font-display text-[11px] font-extrabold tracking-widest text-[var(--meta)]"> {tier.badge}
</span>
<span className="text-[11px] tracking-[0.08em] text-[var(--meta)]">
{tier.level} {tier.level}
</span> </span>
</div> </div>
<DrawLine delay={0.04} /> <DrawLine delay={0.04} />
<div className="mt-sp-4"> <div className="mt-sp-4 min-w-0">
<h3 className="ds-card-title">{tier.title}</h3> <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]">
<p className="mt-sp-2 text-[11px] font-medium leading-relaxed text-[var(--meta)]"> {tier.title}
</h3>
<p className="mt-sp-2 text-[12px] leading-[1.7] text-[var(--meta)] md:text-[11px]">
{tier.tagline} {tier.tagline}
</p> </p>
</div> </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) => ( {tier.deliverables.map((deliverable) => (
<li key={deliverable}>{deliverable}</li> <li key={deliverable} className="pl-sp-1">
{deliverable}
</li>
))} ))}
</ul> </ul>
<div className="mt-sp-6 border-t border-[var(--border)] pt-sp-4"> <div className="mt-sp-6 space-y-sp-2 border-t border-[var(--border)] pt-sp-5">
<div className="font-display text-[clamp(14px,1.5vw,18px)] font-extrabold tracking-tight"> <div className="font-display text-[clamp(17px,4.6vw,18px)] font-extrabold leading-none tracking-tight">
{tier.pricing} {tier.pricing}
</div> </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} {tier.pricingNote}
</div> </div>
</div> </div>
<motion.a <motion.a
href={tier.ctaHref} 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" tier.featured ? "ds-link-accent" : "ds-link-muted"
}`} }`}
whileHover={shouldReduceMotion ? undefined : { scale: 1.02 }} whileHover={shouldReduceMotion ? undefined : { scale: 1.02 }}
@@ -148,7 +157,6 @@ function TierCard({ tier }: Readonly<{ tier: Tier }>) {
transition={{ type: "spring", stiffness: 260, damping: 20 }} transition={{ type: "spring", stiffness: 260, damping: 20 }}
> >
{tier.ctaLabel} {tier.ctaLabel}
{" ->"}
</motion.a> </motion.a>
</motion.article> </motion.article>
); );
@@ -184,25 +192,53 @@ export function WorkModalities() {
return ( return (
<Section <Section
id="modalities" id="modalities"
eyebrow="CONTRACT_PROTOCOLS.sys" eyebrow="Working models"
title="Engenharia sob demanda. Previsibilidade de entrega sem overhead." title="Servicos de engenharia de software e consultoria tecnica."
description="Tres niveis de contratacao desenhados como uma esteira de valor, do diagnostico rapido a parceria estrategica de longo prazo." description="Formatos de contratacao para arquitetura de software, integracoes criticas, produtos conectados e evolucao de sistemas em producao."
disableChildrenAnimation 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 <motion.div
style={{ className="grid grid-cols-1 gap-sp-4 md:grid-cols-2 md:gap-sp-5 lg:grid-cols-3 lg:gap-sp-6"
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"
initial="hidden" initial="hidden"
whileInView="visible" whileInView="visible"
viewport={{ once: true, amount: 0.2 }} viewport={{ once: true, amount: 0.2 }}
variants={containerVariants} variants={containerVariants}
> >
{TIERS.map((tier) => ( {TIERS.map((tier) => (
<motion.div key={tier.id} variants={itemVariants}> <motion.div key={tier.id} variants={itemVariants} className="min-w-0">
<TierCard tier={tier} /> <TierCard tier={tier} />
</motion.div> </motion.div>
))} ))}
@@ -210,3 +246,4 @@ export function WorkModalities() {
</Section> </Section>
); );
} }
+5 -13
View File
@@ -21,7 +21,6 @@ export function LazyRevealImage({
}: Readonly<LazyRevealImageProps>) { }: Readonly<LazyRevealImageProps>) {
const shouldReduceMotion = useReducedMotion(); const shouldReduceMotion = useReducedMotion();
const [loaded, setLoaded] = useState(false); const [loaded, setLoaded] = useState(false);
const [animating, setAnimating] = useState(!shouldReduceMotion);
return ( return (
<motion.div <motion.div
@@ -29,28 +28,24 @@ export function LazyRevealImage({
initial={ initial={
shouldReduceMotion shouldReduceMotion
? { opacity: 0 } ? { opacity: 0 }
: { opacity: 0, filter: "blur(20px)", scale: 1.05 } : { opacity: 0, y: 20, scale: 1.02 }
} }
animate={ animate={
loaded loaded
? shouldReduceMotion ? shouldReduceMotion
? { opacity: 1 } ? { opacity: 1 }
: { opacity: 1, filter: "blur(0px)", scale: 1 } : { opacity: 1, y: 0, scale: 1 }
: shouldReduceMotion : shouldReduceMotion
? { opacity: 0 } ? { opacity: 0 }
: { opacity: 0, filter: "blur(20px)", scale: 1.05 } : { opacity: 0, y: 20, scale: 1.02 }
} }
transition={{ transition={{
duration: shouldReduceMotion ? 0.25 : 0.75, duration: shouldReduceMotion ? 0.25 : 0.75,
ease: [0.16, 1, 0.3, 1], ease: [0.16, 1, 0.3, 1],
}} }}
style={{ style={{
willChange: willChange: !shouldReduceMotion ? "transform, opacity" : undefined,
animating && !shouldReduceMotion
? "filter, transform, opacity"
: undefined,
}} }}
onAnimationComplete={() => setAnimating(false)}
> >
<Image <Image
src={src} src={src}
@@ -59,10 +54,7 @@ export function LazyRevealImage({
loading="lazy" loading="lazy"
sizes={sizes} sizes={sizes}
placeholder={typeof src === "string" ? "empty" : "blur"} placeholder={typeof src === "string" ? "empty" : "blur"}
onLoad={() => { onLoad={() => setLoaded(true)}
setAnimating(true);
setLoaded(true);
}}
className={imageClassName} className={imageClassName}
/> />
</motion.div> </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 base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
@@ -46,7 +28,7 @@
--white: #ffffff; --white: #ffffff;
--slash: #ccff00; --slash: #ccff00;
--slash-dim: rgba(204, 255, 0, 0.16); --slash-dim: rgba(204, 255, 0, 0.16);
--meta: #777; --meta: #595959;
--border: rgba(10, 10, 10, 0.1); --border: rgba(10, 10, 10, 0.1);
--border-light: rgba(10, 10, 10, 0.06); --border-light: rgba(10, 10, 10, 0.06);
--sp-1: 4px; --sp-1: 4px;
@@ -65,9 +47,9 @@
--r-md: 8px; --r-md: 8px;
--r-lg: 16px; --r-lg: 16px;
--r-full: 999px; --r-full: 999px;
--font-display: "Syne", sans-serif; --font-display: var(--font-syne), sans-serif;
--font-hero: "Monument Extended", sans-serif; --font-hero: var(--font-monument-extended), sans-serif;
--font-mono: "Geist Mono", monospace; --font-mono: var(--font-geist-mono), monospace;
--ease-out: cubic-bezier(0.16, 1, 0.3, 1); --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
--ease-in: cubic-bezier(0.4, 0, 1, 1); --ease-in: cubic-bezier(0.4, 0, 1, 1);
} }
@@ -88,8 +70,8 @@ body {
background: var(--paper); background: var(--paper);
color: var(--ink); color: var(--ink);
font-family: var(--font-mono); font-family: var(--font-mono);
font-size: 13px; font-size: 14px;
line-height: 1.6; line-height: 1.68;
overflow-x: hidden; overflow-x: hidden;
} }
@@ -124,11 +106,11 @@ textarea {
} }
.ds-main { .ds-main {
/* padding-top: 52px; removed since there is no header now */ /* no top padding needed */
} }
.ds-section { .ds-section {
padding: var(--sp-20) var(--sp-16); padding: clamp(72px, 9vw, 112px) clamp(24px, 6vw, 64px);
border-bottom: 1px solid var(--border); border-bottom: 1px solid var(--border);
} }
@@ -153,18 +135,18 @@ textarea {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: flex-start; align-items: flex-start;
gap: var(--sp-8); gap: var(--sp-6);
margin-bottom: var(--sp-12); margin-bottom: var(--sp-10);
} }
.ds-section-meta { .ds-section-meta {
max-width: 700px; max-width: 760px;
} }
.ds-eyebrow, .ds-eyebrow,
.ds-kicker { .ds-kicker {
font-size: 9px; font-size: 10px;
letter-spacing: 0.16em; letter-spacing: 0.12em;
text-transform: uppercase; text-transform: uppercase;
color: var(--meta); color: var(--meta);
} }
@@ -175,16 +157,17 @@ textarea {
.ds-section-title { .ds-section-title {
font-family: var(--font-display); font-family: var(--font-display);
font-size: clamp(24px, 4vw, 40px); font-size: clamp(28px, 4vw, 44px);
line-height: 1.05; line-height: 1.02;
font-weight: 800; font-weight: 800;
letter-spacing: -0.03em; letter-spacing: -0.035em;
} }
.ds-section-description { .ds-section-description {
color: var(--meta); max-width: 58ch;
font-size: 11px; color: rgba(10, 10, 10, 0.62);
line-height: 1.8; font-size: 14px;
line-height: 1.82;
} }
.ds-grid-2, .ds-grid-2,
@@ -209,14 +192,15 @@ textarea {
} }
.ds-card { .ds-card {
background: rgba(255, 255, 255, 0.55); background: rgba(255, 255, 255, 0.74);
padding: var(--sp-6); padding: clamp(20px, 3vw, 28px);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
backdrop-filter: blur(10px);
} }
.ds-panel { .ds-panel {
background: rgba(10, 10, 10, 0.02); background: rgba(10, 10, 10, 0.03);
padding: var(--sp-5); padding: var(--sp-5);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -224,16 +208,16 @@ textarea {
.ds-card-title { .ds-card-title {
font-family: var(--font-display); font-family: var(--font-display);
font-size: clamp(18px, 2.4vw, 28px); font-size: clamp(20px, 2.4vw, 30px);
line-height: 1.1; line-height: 1.08;
letter-spacing: -0.02em; letter-spacing: -0.025em;
margin-top: var(--sp-3); margin-top: var(--sp-4);
} }
.ds-copy { .ds-copy {
margin-top: var(--sp-4); margin-top: var(--sp-4);
color: var(--ink-80); color: var(--ink-80);
font-size: 12px; font-size: 14px;
line-height: 1.8; line-height: 1.8;
flex-grow: 1; flex-grow: 1;
} }
@@ -241,27 +225,30 @@ textarea {
.ds-tags { .ds-tags {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: var(--sp-3); gap: 10px;
list-style: none; list-style: none;
margin-top: auto; margin-top: auto;
padding-top: var(--sp-8); padding-top: var(--sp-6);
} }
.ds-tag { .ds-tag {
padding: 8px 12px; padding: 8px 12px;
border: 1px solid var(--border); border: 1px solid rgba(10, 10, 10, 0.12);
background: rgba(10, 10, 10, 0.03); background: rgba(10, 10, 10, 0.03);
font-size: 10px; font-size: 11px;
letter-spacing: 0.12em; letter-spacing: 0.04em;
text-transform: uppercase; text-transform: none;
font-weight: 500; font-weight: 500;
white-space: normal;
word-break: normal;
overflow-wrap: break-word;
} }
.ds-metric { .ds-metric {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: var(--sp-2); gap: var(--sp-2);
color: var(--meta); color: rgba(10, 10, 10, 0.65);
font-size: 10px; font-size: 10px;
letter-spacing: 0.1em; letter-spacing: 0.1em;
text-transform: uppercase; text-transform: uppercase;
@@ -272,8 +259,8 @@ textarea {
align-items: center; align-items: center;
gap: var(--sp-2); gap: var(--sp-2);
text-decoration: none; text-decoration: none;
font-size: 10px; font-size: 11px;
letter-spacing: 0.12em; letter-spacing: 0.08em;
text-transform: uppercase; text-transform: uppercase;
transition: transition:
transform 0.2s ease, transform 0.2s ease,
@@ -297,10 +284,10 @@ textarea {
} }
.ds-list { .ds-list {
margin-top: var(--sp-6); margin-top: var(--sp-5);
padding-left: var(--sp-5); padding-left: 18px;
font-size: 12px; font-size: 14px;
line-height: 1.8; line-height: 1.85;
flex-grow: 1; flex-grow: 1;
} }
@@ -310,31 +297,31 @@ textarea {
.ds-loading, .ds-loading,
.ds-error { .ds-error {
padding: var(--sp-10) var(--sp-16); padding: var(--sp-10) clamp(24px, 6vw, 64px);
font-size: 11px; font-size: 11px;
letter-spacing: 0.14em; letter-spacing: 0.1em;
text-transform: uppercase; text-transform: uppercase;
} }
.ds-section-dark .ds-eyebrow, .ds-section-dark .ds-eyebrow,
.ds-section-dark .ds-kicker, .ds-section-dark .ds-kicker,
.ds-section-dark .ds-metric { .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 { .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-card,
.ds-section-dark .ds-panel { .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); border-color: rgba(255, 255, 255, 0.08);
} }
.ds-section-dark .ds-copy, .ds-section-dark .ds-copy,
.ds-section-dark .ds-list { .ds-section-dark .ds-list {
color: rgba(255, 255, 255, 0.68); color: rgba(255, 255, 255, 0.74);
} }
.ds-section-dark .ds-tag { .ds-section-dark .ds-tag {
@@ -350,19 +337,19 @@ textarea {
display: block; display: block;
width: 100%; width: 100%;
height: 1px; height: 1px;
margin-top: var(--sp-5); margin-top: var(--sp-4);
background: linear-gradient( background: linear-gradient(
90deg, 90deg,
rgba(10, 10, 10, 0.55) 0%, rgba(10, 10, 10, 0.38) 0%,
rgba(10, 10, 10, 0.08) 100% rgba(10, 10, 10, 0.06) 100%
); );
} }
.ds-section-dark .ds-draw-line { .ds-section-dark .ds-draw-line {
background: linear-gradient( background: linear-gradient(
90deg, 90deg,
rgba(204, 255, 0, 0.75) 0%, rgba(204, 255, 0, 0.5) 0%,
rgba(204, 255, 0, 0.1) 100% rgba(204, 255, 0, 0.08) 100%
); );
} }
@@ -453,14 +440,44 @@ textarea {
scroll-behavior: auto !important; scroll-behavior: auto !important;
} }
} }
@media (max-width: 1024px) { @media (max-width: 1024px) {
.ds-section { .ds-section-header {
padding: var(--sp-16) var(--sp-6); flex-direction: column;
gap: var(--sp-4);
} }
.ds-section-header,
.ds-grid-2, .ds-grid-2,
.ds-grid-4 { .ds-grid-4 {
grid-template-columns: 1fr; 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 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 type { ReactNode } from "react";
import GoogleAnalytics from "./components/GoogleAnalytics"; import GoogleAnalytics from "./components/GoogleAnalytics";
import { RouteTransition } from "./components/RouteTransition"; import { RouteTransition } from "./components/RouteTransition";
import { SmoothScrollProvider } from "./providers/SmoothScrollProvider";
import { AnimationProvider } from "./providers/AnimationProvider"; import { AnimationProvider } from "./providers/AnimationProvider";
import { SmoothScrollProvider } from "./providers/SmoothScrollProvider";
import "./globals.css"; 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 = { export const metadata: Metadata = {
title: title: seoTitle,
"Evandro Fonseca Junior | Engenheiro de Software (Next.js, Tauri & Rust)", description: seoDescription,
description:
"Engenheiro Full-Stack especializado em aplicacoes de alta performance e sistemas criticos. Desenvolvimento com Next.js, React, Tauri e arquitetura escalavel.",
keywords: [ keywords: [
"engenheiro full-stack", "engenharia de software",
"engenheiro de software", "engenheiro de software",
"Next.js", "consultoria tecnica",
"React", "consultoria next.js",
"Tauri", "next.js",
"Rust", "tauri",
"Python", "rust",
"TypeScript",
"PostgreSQL",
"MySQL",
"cloud computing",
"IoT",
"automacao",
"arquitetura de software", "arquitetura de software",
"sistemas escalaveis", "sistemas criticos",
"high performance", "integracao hardware software",
"iot",
"software para iot",
"engenharia full-stack",
"aplicacoes de alta performance",
], ],
authors: [{ name: "Evandro Fonseca Junior" }], authors: [{ name: "Evandro Fonseca Junior" }],
creator: "Evandro Fonseca Junior", creator: "Evandro Fonseca Junior",
publisher: "Evandro Fonseca Junior", publisher: "Evandro Fonseca Junior",
applicationName: "Evandro.dev.br",
category: "Technology",
metadataBase: new URL(siteUrl),
alternates: {
canonical: "/",
},
formatDetection: { formatDetection: {
email: false, email: false,
address: false, address: false,
telephone: false, telephone: false,
}, },
metadataBase: new URL("https://evandro.dev.br"),
alternates: {
canonical: "/",
},
openGraph: { openGraph: {
title: title: seoTitle,
"Evandro Fonseca Junior | Engenheiro de Software (Next.js, Tauri & Rust)", description: seoDescription,
description: url: siteUrl,
"Engenheiro Full-Stack especializado em aplicacoes de alta performance e sistemas criticos. Desenvolvimento com Next.js, React, Tauri e arquitetura escalavel.", siteName: "Evandro.dev.br",
url: "https://evandro.dev.br",
siteName: "Evandro Fonseca Junior",
locale: "pt_BR", locale: "pt_BR",
type: "website", type: "website",
images: [ images: [
@@ -55,16 +90,14 @@ export const metadata: Metadata = {
url: "/opengraph-image", url: "/opengraph-image",
width: 1200, width: 1200,
height: 630, height: 630,
alt: "Evandro Fonseca Junior - Engenheiro de Software", alt: "Evandro Fonseca Junior | Engenharia de Software, Next.js, Tauri e Rust",
}, },
], ],
}, },
twitter: { twitter: {
card: "summary_large_image", card: "summary_large_image",
title: title: seoTitle,
"Evandro Fonseca Junior | Engenheiro de Software (Next.js, Tauri & Rust)", description: seoDescription,
description:
"Engenheiro Full-Stack especializado em aplicacoes de alta performance e sistemas criticos.",
creator: "@evandrodevbr", creator: "@evandrodevbr",
images: ["/opengraph-image"], images: ["/opengraph-image"],
}, },
@@ -95,41 +128,119 @@ interface RootLayoutProps {
export default function RootLayout({ children }: Readonly<RootLayoutProps>) { export default function RootLayout({ children }: Readonly<RootLayoutProps>) {
const jsonLd = { const jsonLd = {
"@context": "https://schema.org", "@context": "https://schema.org",
"@type": "ProfilePage", "@graph": [
mainEntity: { {
"@type": "Person", "@type": "WebSite",
name: "Evandro Fonseca Junior", "@id": `${siteUrl}/#website`,
alternateName: "Evandro.dev", url: siteUrl,
jobTitle: "Engenheiro de Software", name: "Evandro.dev.br",
url: "https://evandro.dev.br", inLanguage: "pt-BR",
sameAs: [ description: seoDescription,
"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",
}, },
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 ( return (
<html lang="pt-BR"> <html
lang="pt-BR"
className={`${syne.variable} ${geistMono.variable} ${monumentExtended.variable}`}
>
<head> <head>
<link rel="canonical" href="https://evandro.dev.br" /> <link rel="canonical" href={siteUrl} />
</head> </head>
<body> <body>
<SmoothScrollProvider> <SmoothScrollProvider>
+6 -7
View File
@@ -1,16 +1,14 @@
"use client";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import { Suspense } from "react"; 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 Footer from "./components/Footer";
import Hero from "./components/Hero"; import Hero from "./components/Hero";
import { SectionLoading } from "./components/SectionLoading"; 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")); const Projects = dynamic(() => import("./components/Projects"));
export default function Home() { export default function Home() {
@@ -33,3 +31,4 @@ export default function Home() {
</div> </div>
); );
} }
+50 -21
View File
@@ -1,40 +1,69 @@
"use client"; "use client";
import type { ReactNode } from "react";
import { useEffect, useRef } 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({ export function SmoothScrollProvider({
children, children,
}: { }: {
children: React.ReactNode; children: ReactNode;
}) { }) {
const lenisRef = useRef<Lenis | null>(null); const teardownRef = useRef<() => void>(() => {});
useEffect(() => { useEffect(() => {
const lenis = new Lenis({ const prefersReducedMotion = window.matchMedia(
duration: 1.2, "(prefers-reduced-motion: reduce)",
easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)), // Expo ease ).matches;
smoothWheel: true, const hasCoarsePointer = window.matchMedia("(pointer: coarse)").matches;
});
lenisRef.current = lenis; if (prefersReducedMotion || hasCoarsePointer) {
return undefined;
}
// Sincroniza Lenis com GSAP ScrollTrigger — critico para evitar dessync let isMounted = true;
lenis.on("scroll", ScrollTrigger.update);
gsap.ticker.add((time) => { const setupSmoothScroll = async () => {
lenis.raf(time * 1000); 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 () => { return () => {
lenis.destroy(); isMounted = false;
gsap.ticker.remove((time) => lenis.raf(time * 1000)); 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 { export default function robots(): MetadataRoute.Robots {
return { return {
rules: [ rules: [
{ {
userAgent: "*", userAgent: "*",
allow: "/", allow: ["/", "/_next/static/", "/_next/image/"],
disallow: ["/api/", "/_next/", "/admin/"], disallow: ["/api/", "/admin/"],
}, },
], ],
sitemap: "https://evandro.dev.br/sitemap.xml", 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 { MetadataRoute } from "next";
import { servicePages, siteUrl } from "@/lib/site-content";
export default function sitemap(): MetadataRoute.Sitemap { export default function sitemap(): MetadataRoute.Sitemap {
const baseUrl = "https://evandro.dev.br"; const now = new Date();
const currentDate = new Date().toISOString();
return [ return [
{ {
url: baseUrl, url: siteUrl,
lastModified: currentDate, lastModified: now,
changeFrequency: "monthly", changeFrequency: "monthly",
priority: 1.0, priority: 1.0,
}, },
{ {
url: `${baseUrl}/#about`, url: `${siteUrl}/servicos`,
lastModified: currentDate, lastModified: now,
changeFrequency: "monthly", changeFrequency: "monthly",
priority: 0.8, priority: 0.9,
}, },
{ {
url: `${baseUrl}/#skills`, url: `${siteUrl}/cases`,
lastModified: currentDate, lastModified: now,
changeFrequency: "monthly", changeFrequency: "monthly",
priority: 0.8, priority: 0.9,
}, },
{ ...servicePages.map((service) => ({
url: `${baseUrl}/#experience`, url: `${siteUrl}/servicos/${service.slug}`,
lastModified: currentDate, lastModified: now,
changeFrequency: "monthly", changeFrequency: "monthly" as const,
priority: 0.8, 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",
},
],
},
]; ];
}, },