feat: initialize portfolio site with core components, pages, and content structure, and update Next.js configuration.
This commit is contained in:
1 parent
223746b898
commit
d00258dcd1
22 files changed
+1512
-8038
No files matched your search
@@ -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
@@ -1,4 +1,4 @@
|
||||
"use client";
|
||||
"use client";
|
||||
|
||||
import { motion, type Variants, useReducedMotion } from "framer-motion";
|
||||
import profilePhoto from "@/public/edev.jpeg";
|
||||
@@ -8,39 +8,34 @@ import { LazyRevealImage } from "./animations/LazyRevealImage";
|
||||
|
||||
const STORY_DATA = [
|
||||
{
|
||||
id: "HARDWARE_BRIDGE",
|
||||
title: "Do físico ao digital",
|
||||
label: "Hardware + software",
|
||||
title: "Do sinal fisico ao produto digital",
|
||||
content:
|
||||
"Construo a ponte entre hardware e software. Projeto sistemas capazes de orquestrar dispositivos físicos, protocolos de baixo nível e interfaces web modernas com baixa latência e alta resiliência.",
|
||||
"Construo a ponte entre dispositivos, protocolos de baixo nivel, backend e interface para que o sistema inteiro pareca uma unica experiencia coerente.",
|
||||
},
|
||||
{
|
||||
id: "RUNTIME_EXEC",
|
||||
title: "Entrega sob restrições",
|
||||
label: "Execution",
|
||||
title: "Entrega clara mesmo sob restricao",
|
||||
content:
|
||||
"Atuo com naturalidade em cenários críticos envolvendo integrações de hardware, APIs complexas e infraestrutura legada, sempre priorizando a estabilidade e a legibilidade técnica.",
|
||||
"Trabalho bem em contextos com legado, integracoes complexas e pouca margem para erro, priorizando estabilidade, leitura tecnica e evolucao sustentavel.",
|
||||
},
|
||||
];
|
||||
|
||||
const VALUES_DATA = [
|
||||
const PROFILE_POINTS = [
|
||||
{
|
||||
title: "Pragmatismo arquitetural",
|
||||
title: "Arquitetura pragmatica",
|
||||
description:
|
||||
"Decisões guiadas por contexto real e operabilidade. A melhor tecnologia é aquela que resolve o problema sem adicionar complexidade inútil ao sistema.",
|
||||
"Escolhas guiadas por contexto, operabilidade e impacto real no produto.",
|
||||
},
|
||||
{
|
||||
title: "Fundação & Integração",
|
||||
title: "Integracao profunda",
|
||||
description:
|
||||
"Domínio de protocolos de rede e arquiteturas distribuídas para conectar desde microcontroladores até sistemas robustos na nuvem.",
|
||||
"Capacidade de atravessar camada fisica, servicos e interface sem perder consistencia.",
|
||||
},
|
||||
{
|
||||
title: "Ownership ponta a ponta",
|
||||
description:
|
||||
"Responsabilidade que atravessa camadas: do pulso elétrico no hardware até a renderização do último componente na tela do usuário.",
|
||||
},
|
||||
{
|
||||
title: "Security & Safety",
|
||||
description:
|
||||
"Segurança e resiliência tratadas como base. Sistemas desenhados para falhar graciosamente e proteger dados do dispositivo à nuvem.",
|
||||
"Responsabilidade que cobre discovery, implementacao, qualidade e operacao.",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -84,9 +79,9 @@ export default function About() {
|
||||
return (
|
||||
<Section
|
||||
id="about"
|
||||
eyebrow="WHOAMI.sys"
|
||||
title="Engenharia orientada por clareza, nao por ruido"
|
||||
description="Transformo complexidade tecnica em sistemas estaveis, operaveis e alinhados ao negocio."
|
||||
eyebrow="Operating context"
|
||||
title="Engenharia de software, arquitetura e integracoes complexas"
|
||||
description="Atuo na intersecao entre arquitetura de software, integracoes de sistemas, hardware + software e entrega full-stack para produtos que precisam funcionar com confiabilidade."
|
||||
disableChildrenAnimation
|
||||
aside={
|
||||
<div className="mt-sp-4 ds-metric">
|
||||
@@ -108,14 +103,14 @@ export default function About() {
|
||||
>
|
||||
{STORY_DATA.map((step) => (
|
||||
<motion.article
|
||||
key={step.id}
|
||||
key={step.title}
|
||||
className="ds-card"
|
||||
variants={cardVariants}
|
||||
whileHover={cardHover}
|
||||
whileTap={shouldReduceMotion ? undefined : { scale: 0.99 }}
|
||||
transition={{ type: "spring", stiffness: 180, damping: 18 }}
|
||||
>
|
||||
<div className="ds-kicker">{step.id}</div>
|
||||
<div className="ds-kicker">{step.label}</div>
|
||||
<DrawLine delay={0.03} />
|
||||
<h3 className="ds-card-title">{step.title}</h3>
|
||||
<p className="ds-copy">{step.content}</p>
|
||||
@@ -136,25 +131,25 @@ export default function About() {
|
||||
className="mb-sp-6 h-[220px] w-full overflow-hidden rounded-[6px] border border-[var(--border)]"
|
||||
imageClassName="object-cover"
|
||||
/>
|
||||
<div className="ds-kicker">System Signature</div>
|
||||
<div className="ds-kicker">Technical profile</div>
|
||||
<DrawLine delay={0.05} />
|
||||
<h3 className="ds-card-title">Base tecnica e postura de entrega</h3>
|
||||
<p className="ds-copy">
|
||||
Evandro Fonseca Junior. Engenheiro de Software Full-Stack com foco
|
||||
na convergência entre hardware e aplicações web modernas.
|
||||
Especialista em TypeScript, Node.js, ecossistema React e automação
|
||||
de dispositivos físicos.
|
||||
Sou engenheiro de software full-stack com foco em integrar camadas
|
||||
que normalmente ficam separadas: hardware, servicos, operacao e
|
||||
interface. Meu trabalho e dar forma clara ao que costuma nascer
|
||||
fragmentado.
|
||||
</p>
|
||||
<div className="ds-grid-2 mt-sp-6">
|
||||
{VALUES_DATA.map((value) => (
|
||||
<div className="mt-sp-6 space-y-sp-4">
|
||||
{PROFILE_POINTS.map((point) => (
|
||||
<div
|
||||
key={value.title}
|
||||
key={point.title}
|
||||
className="border-t border-[var(--border)] pt-sp-4"
|
||||
>
|
||||
<div className="text-[11px] font-medium uppercase tracking-[0.08em]">
|
||||
{value.title}
|
||||
<div className="text-[12px] font-medium tracking-[0.02em]">
|
||||
{point.title}
|
||||
</div>
|
||||
<p className="ds-copy !mt-sp-2">{value.description}</p>
|
||||
<p className="ds-copy !mt-sp-2">{point.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -163,3 +158,5 @@ export default function About() {
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+44
-31
@@ -10,6 +10,8 @@ const WHATSAPP_NUMBER = "5547992742365";
|
||||
|
||||
export default function Contact() {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
const fieldClassName =
|
||||
"mt-sp-2 w-full border border-[rgba(255,255,255,.12)] bg-[rgba(255,255,255,.06)] px-sp-4 py-[14px] text-[13px] leading-6 text-white outline-none transition-[border-color,box-shadow] focus-visible:border-[var(--slash)] focus-visible:ring-2 focus-visible:ring-[rgba(204,255,0,0.34)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)]";
|
||||
const [formData, setFormData] = useState({
|
||||
name: "",
|
||||
email: "",
|
||||
@@ -55,9 +57,9 @@ export default function Contact() {
|
||||
return (
|
||||
<Section
|
||||
id="contact"
|
||||
eyebrow="CONTACT_PROTOCOL.init"
|
||||
title="Vamos discutir o proximo sistema"
|
||||
description="Descreva o contexto, o gargalo ou o produto. O contato abre direto no WhatsApp com briefing estruturado."
|
||||
eyebrow="Contact"
|
||||
title="Consultoria tecnica para software, arquitetura e integracoes criticas"
|
||||
description="Fale comigo se voce precisa de engenharia de software, consultoria tecnica, arquitetura de software ou apoio em projetos com Next.js, Tauri, Rust e IoT."
|
||||
dark
|
||||
disableChildrenAnimation
|
||||
>
|
||||
@@ -82,18 +84,22 @@ export default function Contact() {
|
||||
whileTap={shouldReduceMotion ? undefined : { scale: 0.995 }}
|
||||
transition={{ type: "spring", stiffness: 180, damping: 18 }}
|
||||
>
|
||||
<div className="ds-kicker">Response SLA</div>
|
||||
<div className="ds-kicker">Availability</div>
|
||||
<DrawLine delay={0.04} />
|
||||
<h3 className="ds-card-title">Retorno inicial em ate 24h</h3>
|
||||
<h3 className="ds-card-title">Resposta inicial em ate 24h</h3>
|
||||
<p className="ds-copy">
|
||||
Ideal para discovery tecnico, revisao de arquitetura, rescue de
|
||||
projeto, otimizacao de performance ou construcao de produto do zero.
|
||||
projeto, performance ou construcao de produto com ownership real.
|
||||
</p>
|
||||
<div className="mt-sp-6 space-y-sp-4 text-[12px] leading-7 text-[rgba(255,255,255,.7)]">
|
||||
<div className="mt-sp-6 space-y-sp-4 text-[13px] leading-7 text-[rgba(255,255,255,.85)]">
|
||||
<div>WhatsApp: +55 47 99274-2365</div>
|
||||
<div>E-mail: evandrofonsecajunior@gmail.com</div>
|
||||
<div>Base: Garuva, Santa Catarina | atuacao remota</div>
|
||||
</div>
|
||||
<div className="mt-sp-6 border-t border-[rgba(255,255,255,.08)] pt-sp-4 text-[12px] leading-6 text-[rgba(255,255,255,.75)]">
|
||||
Envie contexto, prazo e objetivo. Eu cuido de transformar isso em
|
||||
conversa tecnica objetiva.
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<motion.form
|
||||
@@ -111,54 +117,60 @@ export default function Contact() {
|
||||
transition={{ type: "spring", stiffness: 180, damping: 18 }}
|
||||
>
|
||||
<div>
|
||||
<label htmlFor="contact-name" className="ds-kicker block">
|
||||
<label
|
||||
htmlFor="contact-name"
|
||||
className="block text-[12px] font-medium text-[rgba(255,255,255,.85)]"
|
||||
>
|
||||
Nome completo
|
||||
</label>
|
||||
<input
|
||||
id="contact-name"
|
||||
name="name"
|
||||
required
|
||||
type="text"
|
||||
autoComplete="name"
|
||||
value={formData.name}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, name: e.target.value })
|
||||
}
|
||||
className="mt-sp-2 w-full border border-[rgba(255,255,255,.12)] bg-[rgba(255,255,255,.06)] px-sp-4 py-[14px] text-[12px] outline-none transition-colors focus:border-slash"
|
||||
style={{
|
||||
color: "#fff",
|
||||
backgroundColor: "rgba(255,255,255,0.06)",
|
||||
}}
|
||||
className={fieldClassName}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="contact-email" className="ds-kicker block">
|
||||
E-mail corporativo
|
||||
<label
|
||||
htmlFor="contact-email"
|
||||
className="block text-[12px] font-medium text-[rgba(255,255,255,.85)]"
|
||||
>
|
||||
E-mail
|
||||
</label>
|
||||
<input
|
||||
id="contact-email"
|
||||
name="email"
|
||||
required
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
value={formData.email}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, email: e.target.value })
|
||||
}
|
||||
className="mt-sp-2 w-full border border-[rgba(255,255,255,.12)] bg-[rgba(255,255,255,.06)] px-sp-4 py-[14px] text-[12px] outline-none transition-colors focus:border-slash"
|
||||
style={{
|
||||
color: "#fff",
|
||||
backgroundColor: "rgba(255,255,255,0.06)",
|
||||
}}
|
||||
className={fieldClassName}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="contact-subject" className="ds-kicker block">
|
||||
<label
|
||||
htmlFor="contact-subject"
|
||||
className="block text-[12px] font-medium text-[rgba(255,255,255,.85)]"
|
||||
>
|
||||
Tipo de projeto
|
||||
</label>
|
||||
<select
|
||||
id="contact-subject"
|
||||
name="subject"
|
||||
value={formData.subject}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, subject: e.target.value })
|
||||
}
|
||||
className="mt-sp-2 w-full border border-[rgba(255,255,255,.12)] px-sp-4 py-[14px] text-[12px] outline-none transition-colors focus:border-slash"
|
||||
className={fieldClassName}
|
||||
style={{ color: "#fff", backgroundColor: "#1a1a1a" }}
|
||||
>
|
||||
<option value="full-stack">Desenvolvimento Full-Stack</option>
|
||||
@@ -168,33 +180,34 @@ export default function Contact() {
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="contact-message" className="ds-kicker block">
|
||||
Detalhes do projeto
|
||||
<label
|
||||
htmlFor="contact-message"
|
||||
className="block text-[12px] font-medium text-[rgba(255,255,255,.85)]"
|
||||
>
|
||||
Contexto do projeto
|
||||
</label>
|
||||
<textarea
|
||||
id="contact-message"
|
||||
name="message"
|
||||
required
|
||||
rows={6}
|
||||
autoComplete="off"
|
||||
value={formData.message}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, message: e.target.value })
|
||||
}
|
||||
className="mt-sp-2 w-full resize-none border border-[rgba(255,255,255,.12)] bg-[rgba(255,255,255,.06)] px-sp-4 py-[14px] text-[12px] outline-none transition-colors focus:border-slash"
|
||||
style={{
|
||||
color: "#fff",
|
||||
backgroundColor: "rgba(255,255,255,0.06)",
|
||||
}}
|
||||
className={`${fieldClassName} resize-none`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<motion.button
|
||||
type="submit"
|
||||
className="ds-link ds-link-accent border-0"
|
||||
className="ds-link ds-link-accent min-h-12 border-0"
|
||||
whileHover={shouldReduceMotion ? undefined : { scale: 1.03 }}
|
||||
whileTap={shouldReduceMotion ? undefined : { scale: 0.97 }}
|
||||
transition={{ type: "spring", stiffness: 260, damping: 20 }}
|
||||
>
|
||||
Iniciar protocolo de contato
|
||||
Enviar briefing pelo WhatsApp
|
||||
</motion.button>
|
||||
</motion.form>
|
||||
</motion.div>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
"use client";
|
||||
"use client";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { motion, type Variants, useReducedMotion } from "framer-motion";
|
||||
import { usePortfolioData } from "@/lib/usePortfolioData";
|
||||
import { Section } from "./Section";
|
||||
import { DrawLine } from "./animations/DrawLine";
|
||||
import { CounterValue } from "./animations/CounterValue";
|
||||
import { SectionLoading } from "./SectionLoading";
|
||||
|
||||
export default function Experience() {
|
||||
@@ -24,11 +23,11 @@ export default function Experience() {
|
||||
}, [data]);
|
||||
|
||||
if (loading) {
|
||||
return <SectionLoading message="Loading system logs" />;
|
||||
return <SectionLoading message="Loading selected experience" />;
|
||||
}
|
||||
|
||||
if (error || !data) {
|
||||
return <div className="ds-error">Error loading system logs</div>;
|
||||
return <div className="ds-error">Error loading selected experience</div>;
|
||||
}
|
||||
|
||||
const listVariants: Variants = {
|
||||
@@ -58,9 +57,9 @@ export default function Experience() {
|
||||
return (
|
||||
<Section
|
||||
id="experience"
|
||||
eyebrow="SYSTEM_LOGS.history"
|
||||
title="Trajetoria focada em produto, legado e crescimento sustentavel"
|
||||
description="Experiencias recentes onde combinei entrega rapida com responsabilidade arquitetural."
|
||||
eyebrow="Selected experience"
|
||||
title="Experiencia em engenharia de software e sistemas criticos"
|
||||
description="Projetos e contextos em que combinei arquitetura de software, integracoes complexas e entrega com responsabilidade operacional."
|
||||
disableChildrenAnimation
|
||||
>
|
||||
<motion.div
|
||||
@@ -86,33 +85,28 @@ export default function Experience() {
|
||||
whileTap={shouldReduceMotion ? undefined : { scale: 0.995 }}
|
||||
transition={{ type: "spring", stiffness: 170, damping: 20 }}
|
||||
>
|
||||
<div className="flex flex-col lg:grid lg:grid-cols-[1fr_auto] gap-sp-4 items-start">
|
||||
<div>
|
||||
<div className="flex flex-col gap-sp-4 lg:flex-row lg:items-start lg:justify-between">
|
||||
<div className="max-w-[720px]">
|
||||
<div className="ds-kicker">{exp.period}</div>
|
||||
<DrawLine delay={0.04} />
|
||||
<h3 className="ds-card-title">{exp.title}</h3>
|
||||
<div className="mt-sp-3 text-[11px] uppercase tracking-[0.1em] text-meta">
|
||||
{exp.company} | {exp.type}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-start justify-start gap-sp-3 lg:justify-end">
|
||||
<span className="ds-tag">{exp.location}</span>
|
||||
<span className="ds-tag">
|
||||
<CounterValue
|
||||
value={exp.technologies.length}
|
||||
suffix=" tecnologias"
|
||||
/>
|
||||
</span>
|
||||
<p className="mt-sp-3 text-[13px] leading-6 text-[rgba(10,10,10,.64)] dark:text-[rgba(255,255,255,.6)]">
|
||||
{exp.company} • {exp.type}
|
||||
</p>
|
||||
</div>
|
||||
<div className="ds-metric">{exp.location}</div>
|
||||
</div>
|
||||
|
||||
<p className="ds-copy">{exp.description}</p>
|
||||
|
||||
<ul className="ds-list">
|
||||
{exp.achievements?.map((achievement) => (
|
||||
<li key={achievement}>{achievement}</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<ul className="ds-tags mt-sp-6">
|
||||
{exp.technologies?.map((tech) => (
|
||||
{exp.technologies?.slice(0, 6).map((tech) => (
|
||||
<li key={tech} className="ds-tag">
|
||||
{tech}
|
||||
</li>
|
||||
@@ -124,3 +118,5 @@ export default function Experience() {
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+48
-33
@@ -1,23 +1,10 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* Footer Component
|
||||
*
|
||||
* Displays contact information, social links, and copyright in a consistent footer layout.
|
||||
* Follows the minimalist design system with Monument Extended typography and #ccff00 accent color.
|
||||
*
|
||||
* Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7
|
||||
*/
|
||||
|
||||
// TypeScript interface for social media links
|
||||
export interface SocialLink {
|
||||
name: string; // Display name (e.g., "GitHub")
|
||||
href: string; // Full URL
|
||||
icon: string; // SVG path or icon identifier
|
||||
ariaLabel: string; // Accessibility label
|
||||
name: string;
|
||||
href: string;
|
||||
icon: string;
|
||||
ariaLabel: string;
|
||||
}
|
||||
|
||||
// Social links data
|
||||
const socialLinks: SocialLink[] = [
|
||||
{
|
||||
name: "GitHub",
|
||||
@@ -39,6 +26,16 @@ const socialLinks: SocialLink[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const quickLinks = [
|
||||
{ label: "Hub de servicos", href: "/servicos" },
|
||||
{ label: "Cases", href: "/cases" },
|
||||
{ label: "Consultoria Next.js", href: "/servicos/consultoria-nextjs" },
|
||||
{
|
||||
label: "Integracao hardware + software",
|
||||
href: "/servicos/integracao-hardware-software-iot",
|
||||
},
|
||||
];
|
||||
|
||||
export default function Footer() {
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
@@ -46,22 +43,26 @@ export default function Footer() {
|
||||
<footer className="ds-section">
|
||||
<div className="ds-section-shell">
|
||||
<div className="ds-grid-2">
|
||||
{/* Contact & Legal */}
|
||||
<div className="ds-card flex flex-col justify-between">
|
||||
<div>
|
||||
<div className="ds-kicker">SYSTEM_INFO</div>
|
||||
<div className="ds-kicker">Contato</div>
|
||||
<span className="ds-draw-line" />
|
||||
<h3 className="ds-card-title">
|
||||
Transformando complexidade técnica em sistemas estáveis
|
||||
Engenharia com menos ruido e mais clareza de execucao
|
||||
</h3>
|
||||
<p className="ds-copy max-w-[48ch]">
|
||||
Se voce precisa de alguem para conectar produto, integracao e
|
||||
entrega com mais criterio tecnico, este portfolio e o ponto de
|
||||
partida.
|
||||
</p>
|
||||
|
||||
<div className="mt-sp-6">
|
||||
<p className="text-[12px] uppercase tracking-wider text-[var(--meta)] mb-2">
|
||||
Contato
|
||||
<p className="mb-2 text-[12px] tracking-[0.02em] text-[var(--meta)]">
|
||||
E-mail direto
|
||||
</p>
|
||||
<a
|
||||
href="mailto:[email protected]"
|
||||
className="inline-block text-[18px] font-bold text-[var(--ink)] hover:text-[var(--slash)] transition-colors duration-200"
|
||||
className="inline-block text-[18px] font-bold text-[var(--ink)] transition-colors duration-200 hover:text-[var(--slash)]"
|
||||
>
|
||||
evandro@dev.br
|
||||
</a>
|
||||
@@ -70,21 +71,20 @@ export default function Footer() {
|
||||
|
||||
<div className="mt-sp-8">
|
||||
<div className="ds-metric">
|
||||
<span className="inline-block h-1.5 w-1.5 rounded-full bg-slash animate-pulse" />
|
||||
<span className="inline-block h-1.5 w-1.5 animate-pulse rounded-full bg-slash" />
|
||||
Garuva, SC | Remote worldwide
|
||||
</div>
|
||||
<p className="mt-sp-4 text-[12px] text-[var(--meta)]">
|
||||
© {currentYear} Evandro Fonseca Junior. All rights reserved.
|
||||
Copyright {currentYear} Evandro Fonseca Junior. All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Social Links */}
|
||||
<div className="ds-card flex flex-col justify-between">
|
||||
<div>
|
||||
<div className="ds-kicker">SOCIAL_LINKS</div>
|
||||
<div className="ds-kicker">Presenca digital</div>
|
||||
<span className="ds-draw-line" />
|
||||
<h3 className="ds-card-title">Redes profissionais</h3>
|
||||
<h3 className="ds-card-title">Canais profissionais</h3>
|
||||
|
||||
<div className="mt-sp-6 flex flex-col gap-sp-4">
|
||||
{socialLinks.map((link) => (
|
||||
@@ -94,27 +94,42 @@ export default function Footer() {
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label={link.ariaLabel}
|
||||
className="group flex items-center justify-between p-sp-4 border border-[var(--border)] bg-[rgba(10,10,10,0.02)] rounded-[8px] hover:bg-[rgba(10,10,10,0.04)] hover:border-[var(--slash)] transition-all duration-300"
|
||||
className="group flex items-center justify-between rounded-[8px] border border-[var(--border)] bg-[rgba(10,10,10,0.02)] p-sp-4 transition-all duration-300 hover:border-[var(--slash)] hover:bg-[rgba(10,10,10,0.04)]"
|
||||
>
|
||||
<div className="flex items-center gap-sp-3">
|
||||
<svg
|
||||
className="w-5 h-5 text-[var(--meta)] group-hover:text-[var(--ink)] transition-colors"
|
||||
className="h-5 w-5 text-[var(--meta)] transition-colors group-hover:text-[var(--ink)]"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d={link.icon} />
|
||||
</svg>
|
||||
<span className="text-[14px] font-medium text-[var(--ink-80)] group-hover:text-[var(--ink)] transition-colors">
|
||||
<span className="text-[14px] font-medium text-[var(--ink-80)] transition-colors group-hover:text-[var(--ink)]">
|
||||
{link.name}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-[var(--slash)] opacity-0 -translate-x-2 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-300 font-bold">
|
||||
→
|
||||
<span className="text-[12px] tracking-[0.08em] text-[var(--meta)] transition-colors group-hover:text-[var(--slash)]">
|
||||
abrir
|
||||
</span>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-sp-8 border-t border-[var(--border)] pt-sp-5">
|
||||
<div className="ds-kicker">Explorar</div>
|
||||
<div className="mt-sp-4 grid gap-sp-3 md:grid-cols-2">
|
||||
{quickLinks.map((link) => (
|
||||
<a
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
className="rounded-[8px] border border-[var(--border)] bg-[rgba(10,10,10,0.02)] px-sp-4 py-sp-3 text-[13px] leading-6 text-[var(--ink-80)] transition-colors duration-200 hover:border-[var(--slash)] hover:text-[var(--ink)]"
|
||||
>
|
||||
{link.label}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-sp-8 ds-metric">Open to collaboration</div>
|
||||
|
||||
@@ -5,10 +5,14 @@ import Script from "next/script";
|
||||
const GA_MEASUREMENT_ID = "G-QTT9QT0T3S";
|
||||
|
||||
export default function GoogleAnalytics() {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Script src="/sys/libs/vendor-utils.js" strategy="afterInteractive" />
|
||||
<Script id="google-analytics" strategy="afterInteractive">
|
||||
<Script src="/sys/libs/vendor-utils.js" strategy="lazyOnload" />
|
||||
<Script id="google-analytics" strategy="lazyOnload">
|
||||
{`
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
|
||||
+107
-117
@@ -30,21 +30,27 @@ const SOCIAL_LINKS = [
|
||||
},
|
||||
];
|
||||
|
||||
const HERO_POINTS = [
|
||||
"Produtos conectados, integracoes complexas e operacoes criticas.",
|
||||
"Arquitetura mais clara para evoluir sem aumentar o caos tecnico.",
|
||||
"Execucao ponta a ponta, do discovery a entrega em producao.",
|
||||
];
|
||||
|
||||
const PROOF_ITEMS = [
|
||||
{
|
||||
label: "Especialidade",
|
||||
label: "Quem eu ajudo",
|
||||
value:
|
||||
"Produtos conectados, interfaces de alta performance e sistemas criticos.",
|
||||
"Empresas que dependem de integracoes, hardware ou fluxos criticos para operar com seguranca e previsibilidade.",
|
||||
},
|
||||
{
|
||||
label: "Escopo",
|
||||
label: "Como entrego",
|
||||
value:
|
||||
"Da integracao com hardware e APIs complexas ao frontend e operacao.",
|
||||
"Traduzo complexidade tecnica em backlog claro, arquitetura legivel e decisao mais rapida para o time.",
|
||||
},
|
||||
{
|
||||
label: "Formato",
|
||||
label: "Por que conversar",
|
||||
value:
|
||||
"Consultoria, execucao ponta a ponta e colaboracao remota para squads.",
|
||||
"Voce ganha um parceiro tecnico capaz de conectar produto, backend, interface e operacao sem perder contexto.",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -73,39 +79,44 @@ export default function Hero() {
|
||||
/>
|
||||
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-40 bg-gradient-to-t from-[var(--ink)] to-transparent" />
|
||||
|
||||
<div className="relative z-10 mx-auto flex w-full max-w-[1240px] flex-col px-sp-6 py-sp-16 lg:px-sp-16 lg:py-0 pointer-events-none">
|
||||
<div className="relative z-10 mx-auto flex w-full max-w-[1240px] flex-col px-sp-6 py-sp-16 lg:px-sp-16 lg:py-sp-12">
|
||||
<motion.div
|
||||
initial={shouldReduceMotion ? { opacity: 0 } : { opacity: 0, y: 24 }}
|
||||
animate={shouldReduceMotion ? { opacity: 1 } : { opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="grid items-end gap-sp-10 lg:grid-cols-[minmax(0,1.2fr)_minmax(320px,0.8fr)] pointer-events-auto"
|
||||
className="grid items-start gap-sp-8 lg:grid-cols-[minmax(0,1.1fr)_minmax(340px,0.9fr)] lg:gap-sp-10"
|
||||
>
|
||||
<div className="max-w-[720px]">
|
||||
<div className="mb-sp-6 inline-flex items-center gap-sp-3 border border-[rgba(255,255,255,0.12)] bg-[rgba(10,10,10,0.4)] px-4 py-2 backdrop-blur-xl">
|
||||
<motion.span
|
||||
className="h-2 w-2 bg-[var(--slash)]"
|
||||
animate={
|
||||
shouldReduceMotion
|
||||
? { opacity: 1 }
|
||||
: { opacity: [1, 0.25, 1] }
|
||||
}
|
||||
transition={
|
||||
shouldReduceMotion
|
||||
? { duration: 0 }
|
||||
: {
|
||||
duration: 1.5,
|
||||
ease: "easeInOut",
|
||||
repeat: Number.POSITIVE_INFINITY,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<span className="font-mono text-[10px] uppercase tracking-[0.2em] text-[rgba(244,242,237,.82)] drop-shadow-md">
|
||||
Available for selected builds {"//"} Remote worldwide
|
||||
</span>
|
||||
<div className="max-w-[760px]">
|
||||
<div className="mb-sp-5 flex flex-wrap items-center gap-sp-3">
|
||||
<div className="inline-flex items-center gap-sp-3 border border-[rgba(255,255,255,0.12)] bg-[rgba(10,10,10,0.42)] px-4 py-2 backdrop-blur-xl">
|
||||
<motion.span
|
||||
className="h-2 w-2 bg-[var(--slash)]"
|
||||
animate={
|
||||
shouldReduceMotion
|
||||
? { opacity: 1 }
|
||||
: { opacity: [1, 0.25, 1] }
|
||||
}
|
||||
transition={
|
||||
shouldReduceMotion
|
||||
? { duration: 0 }
|
||||
: {
|
||||
duration: 1.5,
|
||||
ease: "easeInOut",
|
||||
repeat: Number.POSITIVE_INFINITY,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<span className="font-mono text-[10px] uppercase tracking-[0.12em] text-[rgba(244,242,237,.82)]">
|
||||
Available for selected builds
|
||||
</span>
|
||||
</div>
|
||||
<div className="inline-flex items-center gap-sp-2 border border-[rgba(255,255,255,0.08)] bg-[rgba(255,255,255,0.02)] px-4 py-2 text-[11px] text-[rgba(244,242,237,.72)] backdrop-blur-xl">
|
||||
Produtos conectados, integracoes criticas e entrega ponta a ponta
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<motion.p
|
||||
className="mb-sp-4 max-w-[220px] font-mono text-[10px] uppercase tracking-[0.22em] text-[var(--slash)] drop-shadow-lg"
|
||||
className="mb-sp-4 max-w-[540px] font-mono text-[10px] uppercase tracking-[0.12em] text-[var(--slash)]"
|
||||
initial={
|
||||
shouldReduceMotion ? { opacity: 0 } : { opacity: 0, y: 12 }
|
||||
}
|
||||
@@ -118,19 +129,18 @@ export default function Hero() {
|
||||
ease: [0.16, 1, 0.3, 1],
|
||||
}}
|
||||
>
|
||||
Software engineering for products, operations & critical flows
|
||||
Evandro Fonseca Junior | Engenheiro de software full-stack
|
||||
</motion.p>
|
||||
|
||||
<h1
|
||||
className="max-w-[920px] text-balance font-display text-[clamp(2.9rem,7vw,6.4rem)] font-extrabold leading-[0.92] tracking-[-0.04em] text-transparent bg-clip-text bg-gradient-to-br from-[#ffffff] to-[#a3a3a3]"
|
||||
aria-label="evandro.dev.br"
|
||||
className="max-w-[920px] text-balance font-display text-[clamp(3rem,7vw,6.2rem)] font-extrabold leading-[0.9] tracking-[-0.055em] text-transparent bg-clip-text bg-gradient-to-br from-[#ffffff] to-[#a7a7a7]"
|
||||
aria-label="Engenharia para sistemas que nao podem falhar"
|
||||
>
|
||||
Engenharia de software para produtos conectados, interfaces de
|
||||
alta performance e sistemas criticos.
|
||||
Engenharia de software para sistemas que nao podem falhar.
|
||||
</h1>
|
||||
|
||||
<motion.p
|
||||
className="mt-sp-6 max-w-[640px] text-[15px] leading-7 text-[rgba(244,242,237,.9)] lg:text-[16px] drop-shadow-md"
|
||||
className="mt-sp-6 max-w-[680px] text-[16px] leading-8 text-[rgba(244,242,237,.88)] lg:text-[17px]"
|
||||
initial={
|
||||
shouldReduceMotion ? { opacity: 0 } : { opacity: 0, y: 16 }
|
||||
}
|
||||
@@ -139,15 +149,40 @@ export default function Hero() {
|
||||
}
|
||||
transition={{
|
||||
duration: 0.8,
|
||||
delay: 0.16,
|
||||
delay: 0.14,
|
||||
ease: [0.16, 1, 0.3, 1],
|
||||
}}
|
||||
>
|
||||
Da integracao com hardware e APIs complexas ao frontend moderno,
|
||||
construo sistemas estaveis, operaveis e prontos para crescer sem
|
||||
sacrificar clareza tecnica.
|
||||
Ajudo empresas com produtos conectados, integracoes complexas e
|
||||
operacoes criticas a transformar arquitetura confusa em software
|
||||
estavel, mais rapido de evoluir e muito mais facil de operar.
|
||||
</motion.p>
|
||||
|
||||
<motion.ul
|
||||
className="mt-sp-8 grid gap-sp-3 sm:grid-cols-3"
|
||||
initial={
|
||||
shouldReduceMotion ? { opacity: 0 } : { opacity: 0, y: 16 }
|
||||
}
|
||||
animate={
|
||||
shouldReduceMotion ? { opacity: 1 } : { opacity: 1, y: 0 }
|
||||
}
|
||||
transition={{
|
||||
duration: 0.8,
|
||||
delay: 0.22,
|
||||
ease: [0.16, 1, 0.3, 1],
|
||||
}}
|
||||
>
|
||||
{HERO_POINTS.map((point) => (
|
||||
<li
|
||||
key={point}
|
||||
className="flex min-w-0 gap-sp-3 rounded-[8px] border border-[rgba(255,255,255,0.08)] bg-[rgba(255,255,255,0.02)] px-sp-4 py-sp-4 text-[13px] leading-6 text-[rgba(244,242,237,.82)] backdrop-blur-sm"
|
||||
>
|
||||
<span className="mt-[8px] h-1.5 w-1.5 shrink-0 rounded-full bg-[var(--slash)]" />
|
||||
<span>{point}</span>
|
||||
</li>
|
||||
))}
|
||||
</motion.ul>
|
||||
|
||||
<motion.div
|
||||
className="mt-sp-8 flex flex-wrap items-center gap-sp-4"
|
||||
initial={
|
||||
@@ -158,32 +193,32 @@ export default function Hero() {
|
||||
}
|
||||
transition={{
|
||||
duration: 0.8,
|
||||
delay: 0.24,
|
||||
delay: 0.3,
|
||||
ease: [0.16, 1, 0.3, 1],
|
||||
}}
|
||||
>
|
||||
<motion.a
|
||||
href={`https://wa.me/5547992742365?text=${encodeURIComponent(greeting)}`}
|
||||
className="inline-flex min-h-12 items-center justify-center bg-[var(--slash)] px-sp-8 font-mono text-[11px] font-bold uppercase tracking-[0.12em] text-[#0A0A0A] no-underline transition-[transform,background-color,box-shadow] duration-300 hover:bg-white hover:shadow-[0_0_20px_rgba(204,255,0,0.4)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--slash)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)]"
|
||||
className="inline-flex min-h-12 items-center justify-center bg-[var(--slash)] px-sp-8 font-mono text-[11px] font-bold uppercase tracking-[0.1em] text-[#0A0A0A] no-underline transition-[transform,background-color,box-shadow] duration-300 hover:bg-white hover:shadow-[0_0_20px_rgba(204,255,0,0.4)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--slash)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)]"
|
||||
whileHover={shouldReduceMotion ? undefined : { scale: 1.02 }}
|
||||
whileTap={shouldReduceMotion ? undefined : { scale: 0.98 }}
|
||||
>
|
||||
Iniciar projeto
|
||||
Agendar conversa tecnica
|
||||
</motion.a>
|
||||
|
||||
<motion.a
|
||||
href="#projects"
|
||||
className="inline-flex min-h-12 items-center justify-center border border-[rgba(244,242,237,.28)] bg-[rgba(10,10,10,0.4)] backdrop-blur-md px-sp-8 font-mono text-[11px] font-bold uppercase tracking-[0.12em] text-[#f4f2ed] no-underline transition-[transform,border-color,background-color,color] duration-300 hover:border-[var(--slash)] hover:bg-[rgba(204,255,0,0.08)] hover:text-[var(--slash)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--slash)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)]"
|
||||
href="/cases"
|
||||
className="inline-flex min-h-12 items-center justify-center border border-[rgba(244,242,237,.24)] bg-[rgba(10,10,10,0.4)] px-sp-8 font-mono text-[11px] font-bold uppercase tracking-[0.1em] text-[#f4f2ed] no-underline transition-[transform,border-color,background-color,color] duration-300 hover:border-[var(--slash)] hover:bg-[rgba(204,255,0,0.08)] hover:text-[var(--slash)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--slash)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)]"
|
||||
whileHover={shouldReduceMotion ? undefined : { scale: 1.02 }}
|
||||
whileTap={shouldReduceMotion ? undefined : { scale: 0.98 }}
|
||||
>
|
||||
Ver projetos
|
||||
Ver casos selecionados
|
||||
</motion.a>
|
||||
|
||||
<a
|
||||
href="/assets/Evandro_Fonseca_Junior_CV.pdf"
|
||||
download
|
||||
className="inline-flex min-h-12 items-center justify-center px-sp-2 text-[12px] font-medium text-[rgba(244,242,237,.9)] no-underline transition-colors duration-300 hover:text-[var(--slash)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--slash)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)] drop-shadow-md"
|
||||
className="inline-flex min-h-12 items-center justify-center px-sp-2 text-[12px] font-medium text-[rgba(244,242,237,.76)] no-underline transition-colors duration-300 hover:text-[var(--slash)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--slash)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)]"
|
||||
>
|
||||
Download CV
|
||||
</a>
|
||||
@@ -199,15 +234,15 @@ export default function Hero() {
|
||||
}
|
||||
transition={{
|
||||
duration: 0.8,
|
||||
delay: 0.32,
|
||||
delay: 0.38,
|
||||
ease: [0.16, 1, 0.3, 1],
|
||||
}}
|
||||
>
|
||||
<div className="inline-flex items-center gap-sp-3 text-[11px] uppercase tracking-[0.12em] text-[rgba(244,242,237,.72)]">
|
||||
<div className="inline-flex items-center gap-sp-3 text-[11px] uppercase tracking-[0.08em] text-[rgba(244,242,237,.68)]">
|
||||
<span className="h-2 w-2 rounded-full bg-[var(--slash)] shadow-[0_0_8px_var(--slash)]" />
|
||||
Garuva, SC / Brasil
|
||||
</div>
|
||||
<div className="inline-flex items-center gap-sp-3 text-[11px] uppercase tracking-[0.12em] text-[rgba(244,242,237,.72)]">
|
||||
<div className="inline-flex items-center gap-sp-3 text-[11px] uppercase tracking-[0.08em] text-[rgba(244,242,237,.68)]">
|
||||
<span className="h-[1px] w-6 bg-[rgba(255,255,255,.2)]" />
|
||||
Next.js, Tauri, Rust, Node.js
|
||||
</div>
|
||||
@@ -222,7 +257,7 @@ export default function Hero() {
|
||||
? "noopener noreferrer"
|
||||
: undefined
|
||||
}
|
||||
className="inline-flex h-11 w-11 items-center justify-center rounded-full border border-[rgba(244,242,237,0.18)] bg-[rgba(10,10,10,0.4)] backdrop-blur-md text-[rgba(244,242,237,.88)] transition-[transform,border-color,color,background-color] duration-300 hover:border-[var(--slash)] hover:bg-[rgba(204,255,0,0.1)] hover:text-[var(--slash)] hover:shadow-[0_0_15px_rgba(204,255,0,0.2)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--slash)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)]"
|
||||
className="inline-flex h-11 w-11 items-center justify-center rounded-full border border-[rgba(244,242,237,0.16)] bg-[rgba(10,10,10,0.34)] text-[rgba(244,242,237,.86)] transition-[transform,border-color,color,background-color] duration-300 hover:border-[var(--slash)] hover:bg-[rgba(204,255,0,0.1)] hover:text-[var(--slash)] hover:shadow-[0_0_15px_rgba(204,255,0,0.18)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--slash)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)]"
|
||||
aria-label={link.label}
|
||||
>
|
||||
<svg
|
||||
@@ -232,8 +267,7 @@ export default function Hero() {
|
||||
fill={link.stroke ? "none" : "currentColor"}
|
||||
stroke={link.stroke ? "currentColor" : "none"}
|
||||
strokeWidth={link.stroke ? "2" : undefined}
|
||||
role="img"
|
||||
aria-label={link.label}
|
||||
aria-hidden="true"
|
||||
>
|
||||
{link.icon}
|
||||
</svg>
|
||||
@@ -244,28 +278,26 @@ export default function Hero() {
|
||||
</div>
|
||||
|
||||
<motion.aside
|
||||
className="relative overflow-hidden border border-[rgba(255,255,255,0.15)] bg-[linear-gradient(180deg,rgba(10,10,10,0.6),rgba(10,10,10,0.3))] p-sp-6 backdrop-blur-xl lg:p-sp-8"
|
||||
className="relative overflow-hidden border border-[rgba(255,255,255,0.12)] bg-[linear-gradient(180deg,rgba(10,10,10,0.58),rgba(10,10,10,0.36))] p-sp-6 backdrop-blur-xl lg:p-sp-8"
|
||||
initial={
|
||||
shouldReduceMotion ? { opacity: 0 } : { opacity: 0, x: 24 }
|
||||
}
|
||||
animate={shouldReduceMotion ? { opacity: 1 } : { opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.9, delay: 0.22, ease: [0.16, 1, 0.3, 1] }}
|
||||
>
|
||||
<div className="absolute inset-x-0 top-0 h-px bg-[linear-gradient(90deg,transparent,rgba(204,255,0,0.9),transparent)]" />
|
||||
<div className="absolute inset-y-0 left-0 w-px bg-[linear-gradient(180deg,rgba(204,255,0,0.4),transparent)]" />
|
||||
<div className="absolute inset-x-0 top-0 h-px bg-[linear-gradient(90deg,transparent,rgba(204,255,0,0.8),transparent)]" />
|
||||
|
||||
<div className="flex items-start justify-between gap-sp-4 border-b border-[rgba(255,255,255,0.08)] pb-sp-5">
|
||||
<div>
|
||||
<div className="font-mono text-[10px] uppercase tracking-[0.18em] text-[var(--slash)]">
|
||||
Execution Brief
|
||||
</div>
|
||||
<h2 className="mt-sp-2 font-display text-[clamp(1.35rem,3vw,2rem)] font-extrabold leading-[1.02] tracking-[-0.03em] text-[#f7f4ee]">
|
||||
Clareza de arquitetura antes de complexidade.
|
||||
</h2>
|
||||
</div>
|
||||
<div className="shrink-0 border border-[rgba(204,255,0,0.3)] bg-[rgba(204,255,0,0.05)] px-3 py-2 font-mono text-[10px] uppercase tracking-[0.14em] text-[var(--slash)]">
|
||||
2026
|
||||
<div className="border-b border-[rgba(255,255,255,0.08)] pb-sp-5">
|
||||
<div className="font-mono text-[10px] uppercase tracking-[0.14em] text-[var(--slash)]">
|
||||
Why this positioning works
|
||||
</div>
|
||||
<h2 className="mt-sp-3 font-display text-[clamp(1.35rem,3vw,2rem)] font-extrabold leading-[1.04] tracking-[-0.03em] text-[#f7f4ee]">
|
||||
Menos atrito entre produto, engenharia e operacao.
|
||||
</h2>
|
||||
<p className="mt-sp-4 text-[14px] leading-7 text-[rgba(244,242,237,.78)]">
|
||||
Entro onde a complexidade cresce e ajudo a devolver clareza,
|
||||
ritmo de entrega e confiabilidade para o sistema e para o time.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-sp-6 space-y-sp-5">
|
||||
@@ -281,68 +313,26 @@ export default function Hero() {
|
||||
}
|
||||
transition={{
|
||||
duration: 0.65,
|
||||
delay: 0.28 + index * 0.08,
|
||||
delay: 0.3 + index * 0.08,
|
||||
ease: [0.16, 1, 0.3, 1],
|
||||
}}
|
||||
>
|
||||
<div className="font-mono text-[10px] uppercase tracking-[0.18em] text-[rgba(244,242,237,.7)]">
|
||||
<div className="font-mono text-[10px] uppercase tracking-[0.12em] text-[rgba(244,242,237,.56)]">
|
||||
{item.label}
|
||||
</div>
|
||||
<p className="mt-sp-2 text-[14px] leading-6 text-[rgba(244,242,237,.94)]">
|
||||
<p className="mt-sp-2 text-[14px] leading-7 text-[rgba(244,242,237,.92)]">
|
||||
{item.value}
|
||||
</p>
|
||||
</motion.article>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
className="mt-sp-8 grid gap-sp-4 sm:grid-cols-2"
|
||||
initial={
|
||||
shouldReduceMotion ? { opacity: 0 } : { opacity: 0, y: 16 }
|
||||
}
|
||||
animate={
|
||||
shouldReduceMotion ? { opacity: 1 } : { opacity: 1, y: 0 }
|
||||
}
|
||||
transition={{
|
||||
duration: 0.75,
|
||||
delay: 0.48,
|
||||
ease: [0.16, 1, 0.3, 1],
|
||||
}}
|
||||
>
|
||||
<div className="border border-[rgba(255,255,255,0.1)] bg-[rgba(10,10,10,0.4)] p-sp-4 hover:border-[var(--slash)] transition-colors duration-300">
|
||||
<div className="font-mono text-[10px] uppercase tracking-[0.18em] text-[var(--slash)]">
|
||||
Focus
|
||||
</div>
|
||||
<p className="mt-sp-2 text-[13px] leading-6 text-[rgba(244,242,237,.9)]">
|
||||
Sistemas com baixa latencia, alta previsibilidade e manutencao
|
||||
sustentavel.
|
||||
</p>
|
||||
</div>
|
||||
<div className="border border-[rgba(255,255,255,0.1)] bg-[rgba(10,10,10,0.4)] p-sp-4 hover:border-[var(--slash)] transition-colors duration-300">
|
||||
<div className="font-mono text-[10px] uppercase tracking-[0.18em] text-[var(--slash)]">
|
||||
Availability
|
||||
</div>
|
||||
<p className="mt-sp-2 text-[13px] leading-6 text-[rgba(244,242,237,.9)]">
|
||||
Aberto a novos projetos com ownership tecnico, discovery e
|
||||
entrega iterativa.
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.aside>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
className="absolute bottom-10 left-1/2 flex -translate-x-1/2 flex-col items-center gap-3 opacity-50 z-10 pointer-events-none"
|
||||
initial={shouldReduceMotion ? { opacity: 0 } : { opacity: 0 }}
|
||||
animate={shouldReduceMotion ? { opacity: 0.5 } : { opacity: 0.5 }}
|
||||
transition={{ delay: 1, duration: 1 }}
|
||||
>
|
||||
<span className="font-mono text-[9px] uppercase tracking-widest text-[var(--slash)]">
|
||||
Scroll
|
||||
</span>
|
||||
<div className="h-10 w-[1px] bg-gradient-to-b from-[var(--slash)] to-transparent" />
|
||||
</motion.div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
+33
-35
@@ -5,7 +5,6 @@ import { motion, type Variants, useReducedMotion } from "framer-motion";
|
||||
import { usePortfolioData } from "@/lib/usePortfolioData";
|
||||
import { Section } from "./Section";
|
||||
import { DrawLine } from "./animations/DrawLine";
|
||||
import { CounterValue } from "./animations/CounterValue";
|
||||
import { SectionLoading } from "./SectionLoading";
|
||||
|
||||
export default function Projects() {
|
||||
@@ -18,11 +17,11 @@ export default function Projects() {
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
return <SectionLoading dark message="Loading featured projects" />;
|
||||
return <SectionLoading dark message="Loading selected work" />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <div className="ds-error">Error loading featured projects</div>;
|
||||
return <div className="ds-error">Error loading selected work</div>;
|
||||
}
|
||||
|
||||
const gridVariants: Variants = {
|
||||
@@ -52,9 +51,9 @@ export default function Projects() {
|
||||
return (
|
||||
<Section
|
||||
id="projects"
|
||||
eyebrow="PRODUCAO_INTELECTUAL"
|
||||
title="Projetos com recorte tecnico claro e impacto observavel"
|
||||
description="Selecao de produtos e ferramentas onde performance, confiabilidade e clareza de arquitetura foram requisitos reais."
|
||||
eyebrow="Selected work"
|
||||
title="Projetos em Next.js, Tauri, Rust e integracao de sistemas"
|
||||
description="Cases e produtos que mostram minha atuacao em engenharia de software, consultoria tecnica e construcao de sistemas de alta exigencia."
|
||||
dark
|
||||
disableChildrenAnimation
|
||||
>
|
||||
@@ -82,19 +81,29 @@ export default function Projects() {
|
||||
transition={{ type: "spring", stiffness: 170, damping: 18 }}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-sp-6">
|
||||
<div>
|
||||
<div className="max-w-[560px]">
|
||||
<div className="ds-kicker">{project.category}</div>
|
||||
<DrawLine delay={0.05} />
|
||||
<h3 className="ds-card-title">{project.title}</h3>
|
||||
<p className="mt-sp-2 text-[11px] uppercase tracking-[0.1em] text-[rgba(255,255,255,.36)]">
|
||||
{project.subtitle}
|
||||
</p>
|
||||
{project.subtitle && (
|
||||
<p className="mt-sp-3 text-[13px] leading-6 text-[rgba(255,255,255,.56)]">
|
||||
{project.subtitle}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<span className="ds-tag">{project.status}</span>
|
||||
</div>
|
||||
|
||||
<p className="ds-copy">{project.description}</p>
|
||||
|
||||
<ul className="ds-tags mt-sp-6">
|
||||
{project.technologies.slice(0, 5).map((tech: string) => (
|
||||
<li key={tech} className="ds-tag">
|
||||
{tech}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<div className="mt-sp-6 flex flex-wrap gap-sp-3">
|
||||
{project.githubUrl && (
|
||||
<motion.a
|
||||
@@ -106,7 +115,7 @@ export default function Projects() {
|
||||
whileTap={shouldReduceMotion ? undefined : { scale: 0.97 }}
|
||||
transition={{ type: "spring", stiffness: 260, damping: 20 }}
|
||||
>
|
||||
GitHub
|
||||
Ver codigo
|
||||
</motion.a>
|
||||
)}
|
||||
{project.demoUrl && (
|
||||
@@ -119,36 +128,25 @@ export default function Projects() {
|
||||
whileTap={shouldReduceMotion ? undefined : { scale: 0.97 }}
|
||||
transition={{ type: "spring", stiffness: 260, damping: 20 }}
|
||||
>
|
||||
Live demo
|
||||
Abrir projeto
|
||||
</motion.a>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ul className="ds-tags mt-sp-6">
|
||||
{project.technologies.slice(0, 6).map((tech: string) => (
|
||||
<li key={tech} className="ds-tag">
|
||||
{tech}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<div className="mt-sp-6 flex flex-wrap gap-sp-4">
|
||||
<span className="ds-metric">
|
||||
Complexity{" "}
|
||||
<CounterValue
|
||||
value={project.metrics?.complexity || 3}
|
||||
suffix="/5"
|
||||
/>
|
||||
</span>
|
||||
{project.metrics?.teamSize && (
|
||||
<span className="ds-metric">
|
||||
Team {project.metrics.teamSize}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</motion.article>
|
||||
))}
|
||||
</motion.div>
|
||||
|
||||
<div className="mt-sp-8 flex flex-wrap gap-sp-3">
|
||||
<a href="/cases" className="ds-link ds-link-accent">
|
||||
Explorar todos os cases
|
||||
</a>
|
||||
<a href="/servicos" className="ds-link ds-link-muted">
|
||||
Ver servicos relacionados
|
||||
</a>
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+14
-15
@@ -1,10 +1,9 @@
|
||||
"use client";
|
||||
"use client";
|
||||
|
||||
import { motion, type Variants, useReducedMotion } from "framer-motion";
|
||||
import { usePortfolioData } from "@/lib/usePortfolioData";
|
||||
import { Section } from "./Section";
|
||||
import { DrawLine } from "./animations/DrawLine";
|
||||
import { CounterValue } from "./animations/CounterValue";
|
||||
import { SectionLoading } from "./SectionLoading";
|
||||
|
||||
export default function Skills() {
|
||||
@@ -12,11 +11,11 @@ export default function Skills() {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
|
||||
if (loading) {
|
||||
return <SectionLoading dark message="Loading skill registry" />;
|
||||
return <SectionLoading dark message="Loading core stack" />;
|
||||
}
|
||||
|
||||
if (error || !data) {
|
||||
return <div className="ds-error">Error loading skill registry</div>;
|
||||
return <div className="ds-error">Error loading core stack</div>;
|
||||
}
|
||||
|
||||
const gridVariants: Variants = {
|
||||
@@ -46,9 +45,9 @@ export default function Skills() {
|
||||
return (
|
||||
<Section
|
||||
id="skills"
|
||||
eyebrow="MODULE_REGISTRY.lib"
|
||||
title="Stack modular para produto, operacao e escala"
|
||||
description="Ferramentas escolhidas por previsibilidade operacional, velocidade de entrega e facilidade de manutencao."
|
||||
eyebrow="Core stack"
|
||||
title="Next.js, Tauri, Rust e stack para produtos criticos"
|
||||
description="Tecnologias e fundamentos que uso em engenharia de software, arquitetura de software, IoT e integracoes complexas."
|
||||
dark
|
||||
disableChildrenAnimation
|
||||
>
|
||||
@@ -59,7 +58,7 @@ export default function Skills() {
|
||||
viewport={{ once: true, amount: 0.2 }}
|
||||
variants={gridVariants}
|
||||
>
|
||||
{data.skills.technical.map((category, index) => (
|
||||
{data.skills.technical.map((category) => (
|
||||
<motion.article
|
||||
key={category.category}
|
||||
className="ds-card h-full"
|
||||
@@ -75,14 +74,12 @@ export default function Skills() {
|
||||
whileTap={shouldReduceMotion ? undefined : { scale: 0.99 }}
|
||||
transition={{ type: "spring", stiffness: 180, damping: 18 }}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-sp-4">
|
||||
<div className="ds-kicker">ID_0{index + 1}X</div>
|
||||
<div className="ds-metric">
|
||||
<CounterValue value={category.skills.length} suffix=" items" />
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="ds-card-title !mt-0">{category.category}</h3>
|
||||
<DrawLine delay={0.05} />
|
||||
<h3 className="ds-card-title mb-sp-8">{category.category}</h3>
|
||||
<p className="ds-copy !mt-sp-4">
|
||||
Ferramentas que uso para construir com clareza tecnica e ritmo de
|
||||
entrega.
|
||||
</p>
|
||||
<ul className="ds-tags">
|
||||
{category.skills.map((skill) => (
|
||||
<li key={skill} className="ds-tag">
|
||||
@@ -96,3 +93,5 @@ export default function Skills() {
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { motion, type Variants, useReducedMotion } from "framer-motion";
|
||||
import { serviceHubLinks } from "@/lib/site-content";
|
||||
import { Section } from "./Section";
|
||||
import { DrawLine } from "./animations/DrawLine";
|
||||
|
||||
@@ -87,8 +89,7 @@ function TierCard({ tier }: Readonly<{ tier: Tier }>) {
|
||||
|
||||
return (
|
||||
<motion.article
|
||||
tabIndex={0}
|
||||
className={`ds-card h-full relative outline-none ${
|
||||
className={`ds-card relative flex h-full min-w-0 flex-col overflow-hidden outline-none max-md:w-full max-md:max-w-none max-md:px-sp-5 max-md:py-sp-5 ${
|
||||
tier.featured ? "border-[var(--slash)] shadow-sm" : ""
|
||||
}`}
|
||||
style={{
|
||||
@@ -105,42 +106,50 @@ function TierCard({ tier }: Readonly<{ tier: Tier }>) {
|
||||
whileTap={shouldReduceMotion ? undefined : { scale: 0.99 }}
|
||||
transition={{ type: "spring", stiffness: 180, damping: 18 }}
|
||||
>
|
||||
<div className="flex justify-between items-start">
|
||||
<span className="ds-kicker">{tier.badge}</span>
|
||||
<span className="font-display text-[11px] font-extrabold tracking-widest text-[var(--meta)]">
|
||||
<div className="flex min-w-0 items-start justify-between gap-sp-4">
|
||||
<span className="ds-kicker max-md:text-[10px] max-md:leading-[1.6]">
|
||||
{tier.badge}
|
||||
</span>
|
||||
<span className="text-[11px] tracking-[0.08em] text-[var(--meta)]">
|
||||
{tier.level}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<DrawLine delay={0.04} />
|
||||
|
||||
<div className="mt-sp-4">
|
||||
<h3 className="ds-card-title">{tier.title}</h3>
|
||||
<p className="mt-sp-2 text-[11px] font-medium leading-relaxed text-[var(--meta)]">
|
||||
<div className="mt-sp-4 min-w-0">
|
||||
<h3 className="ds-card-title !mt-0 text-balance !text-[clamp(22px,5vw,28px)] !leading-[1.02] !tracking-[-0.035em] [overflow-wrap:break-word] [word-break:normal]">
|
||||
{tier.title}
|
||||
</h3>
|
||||
<p className="mt-sp-2 text-[12px] leading-[1.7] text-[var(--meta)] md:text-[11px]">
|
||||
{tier.tagline}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p className="ds-copy">{tier.description}</p>
|
||||
<p className="ds-copy !mt-sp-4 !text-[13px] !leading-[1.75] [overflow-wrap:break-word] [word-break:normal] md:!text-[12px]">
|
||||
{tier.description}
|
||||
</p>
|
||||
|
||||
<ul className="ds-list">
|
||||
<ul className="ds-list !mt-sp-5 !pl-[1.15rem] !text-[13px] !leading-[1.75] [overflow-wrap:break-word] [word-break:normal] md:!text-[12px]">
|
||||
{tier.deliverables.map((deliverable) => (
|
||||
<li key={deliverable}>{deliverable}</li>
|
||||
<li key={deliverable} className="pl-sp-1">
|
||||
{deliverable}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<div className="mt-sp-6 border-t border-[var(--border)] pt-sp-4">
|
||||
<div className="font-display text-[clamp(14px,1.5vw,18px)] font-extrabold tracking-tight">
|
||||
<div className="mt-sp-6 space-y-sp-2 border-t border-[var(--border)] pt-sp-5">
|
||||
<div className="font-display text-[clamp(17px,4.6vw,18px)] font-extrabold leading-none tracking-tight">
|
||||
{tier.pricing}
|
||||
</div>
|
||||
<div className="mt-1 text-[10px] tracking-widest text-[var(--meta)] uppercase">
|
||||
<div className="text-[12px] leading-[1.7] text-[var(--meta)]">
|
||||
{tier.pricingNote}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<motion.a
|
||||
href={tier.ctaHref}
|
||||
className={`ds-link mt-sp-6 justify-center w-full ${
|
||||
className={`ds-link mt-sp-6 min-h-[50px] w-full justify-center px-sp-4 py-sp-3 text-center text-[11px] leading-none ${
|
||||
tier.featured ? "ds-link-accent" : "ds-link-muted"
|
||||
}`}
|
||||
whileHover={shouldReduceMotion ? undefined : { scale: 1.02 }}
|
||||
@@ -148,7 +157,6 @@ function TierCard({ tier }: Readonly<{ tier: Tier }>) {
|
||||
transition={{ type: "spring", stiffness: 260, damping: 20 }}
|
||||
>
|
||||
{tier.ctaLabel}
|
||||
{" ->"}
|
||||
</motion.a>
|
||||
</motion.article>
|
||||
);
|
||||
@@ -184,25 +192,53 @@ export function WorkModalities() {
|
||||
return (
|
||||
<Section
|
||||
id="modalities"
|
||||
eyebrow="CONTRACT_PROTOCOLS.sys"
|
||||
title="Engenharia sob demanda. Previsibilidade de entrega sem overhead."
|
||||
description="Tres niveis de contratacao desenhados como uma esteira de valor, do diagnostico rapido a parceria estrategica de longo prazo."
|
||||
eyebrow="Working models"
|
||||
title="Servicos de engenharia de software e consultoria tecnica."
|
||||
description="Formatos de contratacao para arquitetura de software, integracoes criticas, produtos conectados e evolucao de sistemas em producao."
|
||||
disableChildrenAnimation
|
||||
>
|
||||
<div className="mb-sp-8 ds-card bg-[rgba(10,10,10,0.02)]">
|
||||
<div className="flex flex-col gap-sp-4 lg:flex-row lg:items-end lg:justify-between">
|
||||
<div className="max-w-[760px]">
|
||||
<div className="ds-kicker">Paginas de servico</div>
|
||||
<h3 className="ds-card-title !mt-sp-3">Especialidades organizadas por intencao de busca</h3>
|
||||
<p className="ds-copy !mt-sp-3 max-w-[62ch]">
|
||||
Se voce chegou buscando uma stack, um tipo de consultoria ou um contexto tecnico especifico,
|
||||
aqui esta o caminho mais direto para a pagina certa.
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/servicos" className="ds-link ds-link-accent">
|
||||
Ver hub de servicos
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="mt-sp-6 grid gap-sp-3 md:grid-cols-2 xl:grid-cols-3">
|
||||
{serviceHubLinks.map((service) => (
|
||||
<Link
|
||||
key={service.href}
|
||||
href={service.href}
|
||||
className="rounded-[8px] border border-[var(--border)] bg-white/70 px-sp-4 py-sp-4 transition-colors duration-200 hover:border-[var(--slash)] hover:bg-white"
|
||||
>
|
||||
<div className="text-[12px] font-semibold tracking-[0.02em] text-[var(--ink)]">
|
||||
{service.label}
|
||||
</div>
|
||||
<div className="mt-sp-2 text-[12px] leading-6 text-[var(--meta)]">
|
||||
{service.description}
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
|
||||
gap: "var(--sp-6)",
|
||||
}}
|
||||
className="max-md:grid-cols-1 max-md:!flex max-md:!flex-col"
|
||||
className="grid grid-cols-1 gap-sp-4 md:grid-cols-2 md:gap-sp-5 lg:grid-cols-3 lg:gap-sp-6"
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true, amount: 0.2 }}
|
||||
variants={containerVariants}
|
||||
>
|
||||
{TIERS.map((tier) => (
|
||||
<motion.div key={tier.id} variants={itemVariants}>
|
||||
<motion.div key={tier.id} variants={itemVariants} className="min-w-0">
|
||||
<TierCard tier={tier} />
|
||||
</motion.div>
|
||||
))}
|
||||
@@ -210,3 +246,4 @@ export function WorkModalities() {
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ export function LazyRevealImage({
|
||||
}: Readonly<LazyRevealImageProps>) {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const [animating, setAnimating] = useState(!shouldReduceMotion);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
@@ -29,28 +28,24 @@ export function LazyRevealImage({
|
||||
initial={
|
||||
shouldReduceMotion
|
||||
? { opacity: 0 }
|
||||
: { opacity: 0, filter: "blur(20px)", scale: 1.05 }
|
||||
: { opacity: 0, y: 20, scale: 1.02 }
|
||||
}
|
||||
animate={
|
||||
loaded
|
||||
? shouldReduceMotion
|
||||
? { opacity: 1 }
|
||||
: { opacity: 1, filter: "blur(0px)", scale: 1 }
|
||||
: { opacity: 1, y: 0, scale: 1 }
|
||||
: shouldReduceMotion
|
||||
? { opacity: 0 }
|
||||
: { opacity: 0, filter: "blur(20px)", scale: 1.05 }
|
||||
: { opacity: 0, y: 20, scale: 1.02 }
|
||||
}
|
||||
transition={{
|
||||
duration: shouldReduceMotion ? 0.25 : 0.75,
|
||||
ease: [0.16, 1, 0.3, 1],
|
||||
}}
|
||||
style={{
|
||||
willChange:
|
||||
animating && !shouldReduceMotion
|
||||
? "filter, transform, opacity"
|
||||
: undefined,
|
||||
willChange: !shouldReduceMotion ? "transform, opacity" : undefined,
|
||||
}}
|
||||
onAnimationComplete={() => setAnimating(false)}
|
||||
>
|
||||
<Image
|
||||
src={src}
|
||||
@@ -59,10 +54,7 @@ export function LazyRevealImage({
|
||||
loading="lazy"
|
||||
sizes={sizes}
|
||||
placeholder={typeof src === "string" ? "empty" : "blur"}
|
||||
onLoad={() => {
|
||||
setAnimating(true);
|
||||
setLoaded(true);
|
||||
}}
|
||||
onLoad={() => setLoaded(true)}
|
||||
className={imageClassName}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
+89
-72
@@ -1,21 +1,3 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Syne:wght@400;500;600;700;800&family=Geist+Mono:wght@300;400;500;600&display=swap");
|
||||
|
||||
@font-face {
|
||||
font-family: "Monument Extended";
|
||||
src: url("/fonts/MonumentExtended-Regular.otf") format("opentype");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Monument Extended";
|
||||
src: url("/fonts/MonumentExtended-Ultrabold.otf") format("opentype");
|
||||
font-weight: 800;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@@ -46,7 +28,7 @@
|
||||
--white: #ffffff;
|
||||
--slash: #ccff00;
|
||||
--slash-dim: rgba(204, 255, 0, 0.16);
|
||||
--meta: #777;
|
||||
--meta: #595959;
|
||||
--border: rgba(10, 10, 10, 0.1);
|
||||
--border-light: rgba(10, 10, 10, 0.06);
|
||||
--sp-1: 4px;
|
||||
@@ -65,9 +47,9 @@
|
||||
--r-md: 8px;
|
||||
--r-lg: 16px;
|
||||
--r-full: 999px;
|
||||
--font-display: "Syne", sans-serif;
|
||||
--font-hero: "Monument Extended", sans-serif;
|
||||
--font-mono: "Geist Mono", monospace;
|
||||
--font-display: var(--font-syne), sans-serif;
|
||||
--font-hero: var(--font-monument-extended), sans-serif;
|
||||
--font-mono: var(--font-geist-mono), monospace;
|
||||
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
|
||||
--ease-in: cubic-bezier(0.4, 0, 1, 1);
|
||||
}
|
||||
@@ -88,8 +70,8 @@ body {
|
||||
background: var(--paper);
|
||||
color: var(--ink);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
font-size: 14px;
|
||||
line-height: 1.68;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
@@ -124,11 +106,11 @@ textarea {
|
||||
}
|
||||
|
||||
.ds-main {
|
||||
/* padding-top: 52px; removed since there is no header now */
|
||||
/* no top padding needed */
|
||||
}
|
||||
|
||||
.ds-section {
|
||||
padding: var(--sp-20) var(--sp-16);
|
||||
padding: clamp(72px, 9vw, 112px) clamp(24px, 6vw, 64px);
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
@@ -153,18 +135,18 @@ textarea {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: var(--sp-8);
|
||||
margin-bottom: var(--sp-12);
|
||||
gap: var(--sp-6);
|
||||
margin-bottom: var(--sp-10);
|
||||
}
|
||||
|
||||
.ds-section-meta {
|
||||
max-width: 700px;
|
||||
max-width: 760px;
|
||||
}
|
||||
|
||||
.ds-eyebrow,
|
||||
.ds-kicker {
|
||||
font-size: 9px;
|
||||
letter-spacing: 0.16em;
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--meta);
|
||||
}
|
||||
@@ -175,16 +157,17 @@ textarea {
|
||||
|
||||
.ds-section-title {
|
||||
font-family: var(--font-display);
|
||||
font-size: clamp(24px, 4vw, 40px);
|
||||
line-height: 1.05;
|
||||
font-size: clamp(28px, 4vw, 44px);
|
||||
line-height: 1.02;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.03em;
|
||||
letter-spacing: -0.035em;
|
||||
}
|
||||
|
||||
.ds-section-description {
|
||||
color: var(--meta);
|
||||
font-size: 11px;
|
||||
line-height: 1.8;
|
||||
max-width: 58ch;
|
||||
color: rgba(10, 10, 10, 0.62);
|
||||
font-size: 14px;
|
||||
line-height: 1.82;
|
||||
}
|
||||
|
||||
.ds-grid-2,
|
||||
@@ -209,14 +192,15 @@ textarea {
|
||||
}
|
||||
|
||||
.ds-card {
|
||||
background: rgba(255, 255, 255, 0.55);
|
||||
padding: var(--sp-6);
|
||||
background: rgba(255, 255, 255, 0.74);
|
||||
padding: clamp(20px, 3vw, 28px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.ds-panel {
|
||||
background: rgba(10, 10, 10, 0.02);
|
||||
background: rgba(10, 10, 10, 0.03);
|
||||
padding: var(--sp-5);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -224,16 +208,16 @@ textarea {
|
||||
|
||||
.ds-card-title {
|
||||
font-family: var(--font-display);
|
||||
font-size: clamp(18px, 2.4vw, 28px);
|
||||
line-height: 1.1;
|
||||
letter-spacing: -0.02em;
|
||||
margin-top: var(--sp-3);
|
||||
font-size: clamp(20px, 2.4vw, 30px);
|
||||
line-height: 1.08;
|
||||
letter-spacing: -0.025em;
|
||||
margin-top: var(--sp-4);
|
||||
}
|
||||
|
||||
.ds-copy {
|
||||
margin-top: var(--sp-4);
|
||||
color: var(--ink-80);
|
||||
font-size: 12px;
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
flex-grow: 1;
|
||||
}
|
||||
@@ -241,27 +225,30 @@ textarea {
|
||||
.ds-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--sp-3);
|
||||
gap: 10px;
|
||||
list-style: none;
|
||||
margin-top: auto;
|
||||
padding-top: var(--sp-8);
|
||||
padding-top: var(--sp-6);
|
||||
}
|
||||
|
||||
.ds-tag {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid var(--border);
|
||||
border: 1px solid rgba(10, 10, 10, 0.12);
|
||||
background: rgba(10, 10, 10, 0.03);
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: none;
|
||||
font-weight: 500;
|
||||
white-space: normal;
|
||||
word-break: normal;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.ds-metric {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--sp-2);
|
||||
color: var(--meta);
|
||||
color: rgba(10, 10, 10, 0.65);
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
@@ -272,8 +259,8 @@ textarea {
|
||||
align-items: center;
|
||||
gap: var(--sp-2);
|
||||
text-decoration: none;
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.12em;
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
transition:
|
||||
transform 0.2s ease,
|
||||
@@ -297,10 +284,10 @@ textarea {
|
||||
}
|
||||
|
||||
.ds-list {
|
||||
margin-top: var(--sp-6);
|
||||
padding-left: var(--sp-5);
|
||||
font-size: 12px;
|
||||
line-height: 1.8;
|
||||
margin-top: var(--sp-5);
|
||||
padding-left: 18px;
|
||||
font-size: 14px;
|
||||
line-height: 1.85;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
@@ -310,31 +297,31 @@ textarea {
|
||||
|
||||
.ds-loading,
|
||||
.ds-error {
|
||||
padding: var(--sp-10) var(--sp-16);
|
||||
padding: var(--sp-10) clamp(24px, 6vw, 64px);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.14em;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.ds-section-dark .ds-eyebrow,
|
||||
.ds-section-dark .ds-kicker,
|
||||
.ds-section-dark .ds-metric {
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
color: rgba(255, 255, 255, 0.65);
|
||||
}
|
||||
|
||||
.ds-section-dark .ds-section-description {
|
||||
color: rgba(255, 255, 255, 0.38);
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
|
||||
.ds-section-dark .ds-card,
|
||||
.ds-section-dark .ds-panel {
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border-color: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.ds-section-dark .ds-copy,
|
||||
.ds-section-dark .ds-list {
|
||||
color: rgba(255, 255, 255, 0.68);
|
||||
color: rgba(255, 255, 255, 0.74);
|
||||
}
|
||||
|
||||
.ds-section-dark .ds-tag {
|
||||
@@ -350,19 +337,19 @@ textarea {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
margin-top: var(--sp-5);
|
||||
margin-top: var(--sp-4);
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(10, 10, 10, 0.55) 0%,
|
||||
rgba(10, 10, 10, 0.08) 100%
|
||||
rgba(10, 10, 10, 0.38) 0%,
|
||||
rgba(10, 10, 10, 0.06) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.ds-section-dark .ds-draw-line {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(204, 255, 0, 0.75) 0%,
|
||||
rgba(204, 255, 0, 0.1) 100%
|
||||
rgba(204, 255, 0, 0.5) 0%,
|
||||
rgba(204, 255, 0, 0.08) 100%
|
||||
);
|
||||
}
|
||||
|
||||
@@ -453,14 +440,44 @@ textarea {
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.ds-section {
|
||||
padding: var(--sp-16) var(--sp-6);
|
||||
.ds-section-header {
|
||||
flex-direction: column;
|
||||
gap: var(--sp-4);
|
||||
}
|
||||
|
||||
.ds-section-header,
|
||||
.ds-grid-2,
|
||||
.ds-grid-4 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.ds-section {
|
||||
padding: 72px 20px;
|
||||
}
|
||||
|
||||
.ds-section-title {
|
||||
font-size: clamp(26px, 8vw, 34px);
|
||||
}
|
||||
|
||||
.ds-section-description,
|
||||
.ds-copy,
|
||||
.ds-list {
|
||||
font-size: 13px;
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
.ds-tags {
|
||||
gap: 8px;
|
||||
padding-top: var(--sp-5);
|
||||
}
|
||||
|
||||
.ds-tag {
|
||||
font-size: 10px;
|
||||
padding: 7px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+175
-64
@@ -1,53 +1,88 @@
|
||||
import type { Metadata, Viewport } from "next";
|
||||
import { Geist_Mono, Syne } from "next/font/google";
|
||||
import localFont from "next/font/local";
|
||||
import type { ReactNode } from "react";
|
||||
import GoogleAnalytics from "./components/GoogleAnalytics";
|
||||
import { RouteTransition } from "./components/RouteTransition";
|
||||
import { SmoothScrollProvider } from "./providers/SmoothScrollProvider";
|
||||
import { AnimationProvider } from "./providers/AnimationProvider";
|
||||
import { SmoothScrollProvider } from "./providers/SmoothScrollProvider";
|
||||
import "./globals.css";
|
||||
|
||||
const siteUrl = "https://evandro.dev.br";
|
||||
const seoTitle =
|
||||
"Engenharia de Software, Next.js, Tauri, Rust e Consultoria Tecnica | Evandro Fonseca Junior";
|
||||
const seoDescription =
|
||||
"Consultoria e engenharia de software para Next.js, Tauri, Rust, IoT e sistemas criticos. Arquitetura de software e integracoes hardware + software para empresas que precisam de estabilidade e escala.";
|
||||
|
||||
const syne = Syne({
|
||||
subsets: ["latin"],
|
||||
weight: ["400", "500", "600", "700", "800"],
|
||||
display: "swap",
|
||||
variable: "--font-syne",
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
subsets: ["latin"],
|
||||
weight: ["400", "500", "600"],
|
||||
display: "swap",
|
||||
variable: "--font-geist-mono",
|
||||
});
|
||||
|
||||
const monumentExtended = localFont({
|
||||
src: [
|
||||
{
|
||||
path: "../public/fonts/MonumentExtended-Regular.otf",
|
||||
weight: "400",
|
||||
style: "normal",
|
||||
},
|
||||
{
|
||||
path: "../public/fonts/MonumentExtended-Ultrabold.otf",
|
||||
weight: "800",
|
||||
style: "normal",
|
||||
},
|
||||
],
|
||||
display: "swap",
|
||||
variable: "--font-monument-extended",
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title:
|
||||
"Evandro Fonseca Junior | Engenheiro de Software (Next.js, Tauri & Rust)",
|
||||
description:
|
||||
"Engenheiro Full-Stack especializado em aplicacoes de alta performance e sistemas criticos. Desenvolvimento com Next.js, React, Tauri e arquitetura escalavel.",
|
||||
title: seoTitle,
|
||||
description: seoDescription,
|
||||
keywords: [
|
||||
"engenheiro full-stack",
|
||||
"engenharia de software",
|
||||
"engenheiro de software",
|
||||
"Next.js",
|
||||
"React",
|
||||
"Tauri",
|
||||
"Rust",
|
||||
"Python",
|
||||
"TypeScript",
|
||||
"PostgreSQL",
|
||||
"MySQL",
|
||||
"cloud computing",
|
||||
"IoT",
|
||||
"automacao",
|
||||
"consultoria tecnica",
|
||||
"consultoria next.js",
|
||||
"next.js",
|
||||
"tauri",
|
||||
"rust",
|
||||
"arquitetura de software",
|
||||
"sistemas escalaveis",
|
||||
"high performance",
|
||||
"sistemas criticos",
|
||||
"integracao hardware software",
|
||||
"iot",
|
||||
"software para iot",
|
||||
"engenharia full-stack",
|
||||
"aplicacoes de alta performance",
|
||||
],
|
||||
authors: [{ name: "Evandro Fonseca Junior" }],
|
||||
creator: "Evandro Fonseca Junior",
|
||||
publisher: "Evandro Fonseca Junior",
|
||||
applicationName: "Evandro.dev.br",
|
||||
category: "Technology",
|
||||
metadataBase: new URL(siteUrl),
|
||||
alternates: {
|
||||
canonical: "/",
|
||||
},
|
||||
formatDetection: {
|
||||
email: false,
|
||||
address: false,
|
||||
telephone: false,
|
||||
},
|
||||
metadataBase: new URL("https://evandro.dev.br"),
|
||||
alternates: {
|
||||
canonical: "/",
|
||||
},
|
||||
openGraph: {
|
||||
title:
|
||||
"Evandro Fonseca Junior | Engenheiro de Software (Next.js, Tauri & Rust)",
|
||||
description:
|
||||
"Engenheiro Full-Stack especializado em aplicacoes de alta performance e sistemas criticos. Desenvolvimento com Next.js, React, Tauri e arquitetura escalavel.",
|
||||
url: "https://evandro.dev.br",
|
||||
siteName: "Evandro Fonseca Junior",
|
||||
title: seoTitle,
|
||||
description: seoDescription,
|
||||
url: siteUrl,
|
||||
siteName: "Evandro.dev.br",
|
||||
locale: "pt_BR",
|
||||
type: "website",
|
||||
images: [
|
||||
@@ -55,16 +90,14 @@ export const metadata: Metadata = {
|
||||
url: "/opengraph-image",
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: "Evandro Fonseca Junior - Engenheiro de Software",
|
||||
alt: "Evandro Fonseca Junior | Engenharia de Software, Next.js, Tauri e Rust",
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title:
|
||||
"Evandro Fonseca Junior | Engenheiro de Software (Next.js, Tauri & Rust)",
|
||||
description:
|
||||
"Engenheiro Full-Stack especializado em aplicacoes de alta performance e sistemas criticos.",
|
||||
title: seoTitle,
|
||||
description: seoDescription,
|
||||
creator: "@evandrodevbr",
|
||||
images: ["/opengraph-image"],
|
||||
},
|
||||
@@ -95,41 +128,119 @@ interface RootLayoutProps {
|
||||
export default function RootLayout({ children }: Readonly<RootLayoutProps>) {
|
||||
const jsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "ProfilePage",
|
||||
mainEntity: {
|
||||
"@type": "Person",
|
||||
name: "Evandro Fonseca Junior",
|
||||
alternateName: "Evandro.dev",
|
||||
jobTitle: "Engenheiro de Software",
|
||||
url: "https://evandro.dev.br",
|
||||
sameAs: [
|
||||
"https://github.com/evandrodevbr",
|
||||
"https://www.linkedin.com/in/evandro-fonseca-junior-0969aa1b0/",
|
||||
],
|
||||
knowsAbout: [
|
||||
"Software Engineering",
|
||||
"Next.js",
|
||||
"React",
|
||||
"Tauri",
|
||||
"Rust",
|
||||
"TypeScript",
|
||||
"System Architecture",
|
||||
"Cloud Computing",
|
||||
"IoT",
|
||||
],
|
||||
worksFor: {
|
||||
"@type": "Organization",
|
||||
name: "Freelancer / Consultor Independente",
|
||||
"@graph": [
|
||||
{
|
||||
"@type": "WebSite",
|
||||
"@id": `${siteUrl}/#website`,
|
||||
url: siteUrl,
|
||||
name: "Evandro.dev.br",
|
||||
inLanguage: "pt-BR",
|
||||
description: seoDescription,
|
||||
},
|
||||
description:
|
||||
"Engenheiro de software especializado em aplicacoes de alta performance e sistemas criticos. Desenvolvimento com Next.js 15, React 19, Tauri e arquitetura escalavel.",
|
||||
},
|
||||
{
|
||||
"@type": "Person",
|
||||
"@id": `${siteUrl}/#person`,
|
||||
name: "Evandro Fonseca Junior",
|
||||
alternateName: ["Evandro.dev", "evandrodevbr"],
|
||||
jobTitle: "Engenheiro de Software Full-Stack",
|
||||
description: seoDescription,
|
||||
url: siteUrl,
|
||||
image: `${siteUrl}/opengraph-image`,
|
||||
sameAs: [
|
||||
"https://github.com/evandrodevbr",
|
||||
"https://www.linkedin.com/in/evandro-fonseca-junior-0969aa1b0/",
|
||||
"https://www.instagram.com/evandrodevbr/",
|
||||
],
|
||||
worksFor: {
|
||||
"@type": "Organization",
|
||||
name: "Consultoria Independente",
|
||||
},
|
||||
homeLocation: {
|
||||
"@type": "Place",
|
||||
name: "Garuva, Santa Catarina, Brasil",
|
||||
},
|
||||
knowsAbout: [
|
||||
"Engenharia de Software",
|
||||
"Next.js",
|
||||
"Tauri",
|
||||
"Rust",
|
||||
"Arquitetura de Software",
|
||||
"Sistemas Criticos",
|
||||
"Integracao hardware e software",
|
||||
"IoT",
|
||||
"TypeScript",
|
||||
"Cloud Computing",
|
||||
],
|
||||
},
|
||||
{
|
||||
"@type": "ProfessionalService",
|
||||
"@id": `${siteUrl}/#services`,
|
||||
name: "Evandro Fonseca Junior | Engenharia de Software e Consultoria Tecnica",
|
||||
url: siteUrl,
|
||||
description:
|
||||
"Servico de engenharia de software, arquitetura de software, integracoes criticas e consultoria tecnica para Next.js, Tauri, Rust, IoT e produtos conectados.",
|
||||
areaServed: [
|
||||
{
|
||||
"@type": "Country",
|
||||
name: "Brasil",
|
||||
},
|
||||
{
|
||||
"@type": "Place",
|
||||
name: "Remote worldwide",
|
||||
},
|
||||
],
|
||||
provider: {
|
||||
"@id": `${siteUrl}/#person`,
|
||||
},
|
||||
serviceType: [
|
||||
"Engenharia de Software",
|
||||
"Consultoria Tecnica",
|
||||
"Arquitetura de Software",
|
||||
"Integracao hardware + software",
|
||||
"Desenvolvimento Next.js",
|
||||
"Desenvolvimento Tauri",
|
||||
"Desenvolvimento Rust",
|
||||
"IoT",
|
||||
"Sistemas Criticos",
|
||||
],
|
||||
hasOfferCatalog: {
|
||||
"@type": "OfferCatalog",
|
||||
name: "Modelos de contratacao",
|
||||
itemListElement: [
|
||||
{
|
||||
"@type": "Offer",
|
||||
itemOffered: {
|
||||
"@type": "Service",
|
||||
name: "Auditoria e diagnostico tecnico",
|
||||
},
|
||||
},
|
||||
{
|
||||
"@type": "Offer",
|
||||
itemOffered: {
|
||||
"@type": "Service",
|
||||
name: "Ciclos de engenharia",
|
||||
},
|
||||
},
|
||||
{
|
||||
"@type": "Offer",
|
||||
itemOffered: {
|
||||
"@type": "Service",
|
||||
name: "Fractional Tech Lead",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return (
|
||||
<html lang="pt-BR">
|
||||
<html
|
||||
lang="pt-BR"
|
||||
className={`${syne.variable} ${geistMono.variable} ${monumentExtended.variable}`}
|
||||
>
|
||||
<head>
|
||||
<link rel="canonical" href="https://evandro.dev.br" />
|
||||
<link rel="canonical" href={siteUrl} />
|
||||
</head>
|
||||
<body>
|
||||
<SmoothScrollProvider>
|
||||
|
||||
+6
-7
@@ -1,16 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import dynamic from "next/dynamic";
|
||||
import { Suspense } from "react";
|
||||
import About from "./components/About";
|
||||
import Contact from "./components/Contact";
|
||||
import Experience from "./components/Experience";
|
||||
import Footer from "./components/Footer";
|
||||
import Hero from "./components/Hero";
|
||||
import { SectionLoading } from "./components/SectionLoading";
|
||||
import Skills from "./components/Skills";
|
||||
import { WorkModalities } from "./components/WorkModalities";
|
||||
|
||||
const About = dynamic(() => import("./components/About"));
|
||||
const Skills = dynamic(() => import("./components/Skills"));
|
||||
const Experience = dynamic(() => import("./components/Experience"));
|
||||
const WorkModalities = dynamic(() => import("./components/WorkModalities").then(mod => mod.WorkModalities));
|
||||
const Contact = dynamic(() => import("./components/Contact"));
|
||||
const Projects = dynamic(() => import("./components/Projects"));
|
||||
|
||||
export default function Home() {
|
||||
@@ -33,3 +31,4 @@ export default function Home() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,40 +1,69 @@
|
||||
"use client";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import Lenis from "lenis";
|
||||
import { gsap } from "gsap";
|
||||
import { ScrollTrigger } from "gsap/ScrollTrigger";
|
||||
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
export function SmoothScrollProvider({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const lenisRef = useRef<Lenis | null>(null);
|
||||
const teardownRef = useRef<() => void>(() => {});
|
||||
|
||||
useEffect(() => {
|
||||
const lenis = new Lenis({
|
||||
duration: 1.2,
|
||||
easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)), // Expo ease
|
||||
smoothWheel: true,
|
||||
});
|
||||
const prefersReducedMotion = window.matchMedia(
|
||||
"(prefers-reduced-motion: reduce)",
|
||||
).matches;
|
||||
const hasCoarsePointer = window.matchMedia("(pointer: coarse)").matches;
|
||||
|
||||
lenisRef.current = lenis;
|
||||
if (prefersReducedMotion || hasCoarsePointer) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Sincroniza Lenis com GSAP ScrollTrigger — critico para evitar dessync
|
||||
lenis.on("scroll", ScrollTrigger.update);
|
||||
let isMounted = true;
|
||||
|
||||
gsap.ticker.add((time) => {
|
||||
lenis.raf(time * 1000);
|
||||
});
|
||||
const setupSmoothScroll = async () => {
|
||||
const [{ default: Lenis }, { gsap }, { ScrollTrigger }] =
|
||||
await Promise.all([
|
||||
import("lenis"),
|
||||
import("gsap"),
|
||||
import("gsap/ScrollTrigger"),
|
||||
]);
|
||||
|
||||
gsap.ticker.lagSmoothing(0);
|
||||
if (!isMounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
const lenis = new Lenis({
|
||||
duration: 1.2,
|
||||
easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
|
||||
smoothWheel: true,
|
||||
});
|
||||
|
||||
const handleScroll = () => ScrollTrigger.update();
|
||||
const tick = (time: number) => {
|
||||
lenis.raf(time * 1000);
|
||||
};
|
||||
|
||||
lenis.on("scroll", handleScroll);
|
||||
gsap.ticker.add(tick);
|
||||
gsap.ticker.lagSmoothing(0);
|
||||
|
||||
teardownRef.current = () => {
|
||||
lenis.off?.("scroll", handleScroll);
|
||||
gsap.ticker.remove(tick);
|
||||
lenis.destroy();
|
||||
};
|
||||
};
|
||||
|
||||
void setupSmoothScroll();
|
||||
|
||||
return () => {
|
||||
lenis.destroy();
|
||||
gsap.ticker.remove((time) => lenis.raf(time * 1000));
|
||||
isMounted = false;
|
||||
teardownRef.current();
|
||||
teardownRef.current = () => {};
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
+4
-3
@@ -1,14 +1,15 @@
|
||||
import { MetadataRoute } from "next";
|
||||
import { MetadataRoute } from "next";
|
||||
|
||||
export default function robots(): MetadataRoute.Robots {
|
||||
return {
|
||||
rules: [
|
||||
{
|
||||
userAgent: "*",
|
||||
allow: "/",
|
||||
disallow: ["/api/", "/_next/", "/admin/"],
|
||||
allow: ["/", "/_next/static/", "/_next/image/"],
|
||||
disallow: ["/api/", "/admin/"],
|
||||
},
|
||||
],
|
||||
sitemap: "https://evandro.dev.br/sitemap.xml",
|
||||
host: "https://evandro.dev.br",
|
||||
};
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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
@@ -1,45 +1,33 @@
|
||||
import { MetadataRoute } from "next";
|
||||
import { servicePages, siteUrl } from "@/lib/site-content";
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
const baseUrl = "https://evandro.dev.br";
|
||||
const currentDate = new Date().toISOString();
|
||||
const now = new Date();
|
||||
|
||||
return [
|
||||
{
|
||||
url: baseUrl,
|
||||
lastModified: currentDate,
|
||||
url: siteUrl,
|
||||
lastModified: now,
|
||||
changeFrequency: "monthly",
|
||||
priority: 1.0,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/#about`,
|
||||
lastModified: currentDate,
|
||||
url: `${siteUrl}/servicos`,
|
||||
lastModified: now,
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.8,
|
||||
priority: 0.9,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/#skills`,
|
||||
lastModified: currentDate,
|
||||
url: `${siteUrl}/cases`,
|
||||
lastModified: now,
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.8,
|
||||
priority: 0.9,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/#experience`,
|
||||
lastModified: currentDate,
|
||||
changeFrequency: "monthly",
|
||||
...servicePages.map((service) => ({
|
||||
url: `${siteUrl}/servicos/${service.slug}`,
|
||||
lastModified: now,
|
||||
changeFrequency: "monthly" as const,
|
||||
priority: 0.8,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/#projects`,
|
||||
lastModified: currentDate,
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.8,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/#contact`,
|
||||
lastModified: currentDate,
|
||||
changeFrequency: "monthly",
|
||||
priority: 0.8,
|
||||
},
|
||||
})),
|
||||
];
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
|
||||
Reference in new issue
Block a user