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

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

No files matched your search

+31 -34
View File
@@ -1,4 +1,4 @@
"use client";
"use client";
import { motion, type Variants, useReducedMotion } from "framer-motion";
import profilePhoto from "@/public/edev.jpeg";
@@ -8,39 +8,34 @@ import { LazyRevealImage } from "./animations/LazyRevealImage";
const STORY_DATA = [
{
id: "HARDWARE_BRIDGE",
title: "Do físico ao digital",
label: "Hardware + software",
title: "Do sinal fisico ao produto digital",
content:
"Construo a ponte entre hardware e software. Projeto sistemas capazes de orquestrar dispositivos físicos, protocolos de baixo nível e interfaces web modernas com baixa latência e alta resiliência.",
"Construo a ponte entre dispositivos, protocolos de baixo nivel, backend e interface para que o sistema inteiro pareca uma unica experiencia coerente.",
},
{
id: "RUNTIME_EXEC",
title: "Entrega sob restrições",
label: "Execution",
title: "Entrega clara mesmo sob restricao",
content:
"Atuo com naturalidade em cenários críticos envolvendo integrações de hardware, APIs complexas e infraestrutura legada, sempre priorizando a estabilidade e a legibilidade técnica.",
"Trabalho bem em contextos com legado, integracoes complexas e pouca margem para erro, priorizando estabilidade, leitura tecnica e evolucao sustentavel.",
},
];
const VALUES_DATA = [
const PROFILE_POINTS = [
{
title: "Pragmatismo arquitetural",
title: "Arquitetura pragmatica",
description:
"Decisões guiadas por contexto real e operabilidade. A melhor tecnologia é aquela que resolve o problema sem adicionar complexidade inútil ao sistema.",
"Escolhas guiadas por contexto, operabilidade e impacto real no produto.",
},
{
title: "Fundação & Integração",
title: "Integracao profunda",
description:
"Domínio de protocolos de rede e arquiteturas distribuídas para conectar desde microcontroladores até sistemas robustos na nuvem.",
"Capacidade de atravessar camada fisica, servicos e interface sem perder consistencia.",
},
{
title: "Ownership ponta a ponta",
description:
"Responsabilidade que atravessa camadas: do pulso elétrico no hardware até a renderização do último componente na tela do usuário.",
},
{
title: "Security & Safety",
description:
"Segurança e resiliência tratadas como base. Sistemas desenhados para falhar graciosamente e proteger dados do dispositivo à nuvem.",
"Responsabilidade que cobre discovery, implementacao, qualidade e operacao.",
},
];
@@ -84,9 +79,9 @@ export default function About() {
return (
<Section
id="about"
eyebrow="WHOAMI.sys"
title="Engenharia orientada por clareza, nao por ruido"
description="Transformo complexidade tecnica em sistemas estaveis, operaveis e alinhados ao negocio."
eyebrow="Operating context"
title="Engenharia de software, arquitetura e integracoes complexas"
description="Atuo na intersecao entre arquitetura de software, integracoes de sistemas, hardware + software e entrega full-stack para produtos que precisam funcionar com confiabilidade."
disableChildrenAnimation
aside={
<div className="mt-sp-4 ds-metric">
@@ -108,14 +103,14 @@ export default function About() {
>
{STORY_DATA.map((step) => (
<motion.article
key={step.id}
key={step.title}
className="ds-card"
variants={cardVariants}
whileHover={cardHover}
whileTap={shouldReduceMotion ? undefined : { scale: 0.99 }}
transition={{ type: "spring", stiffness: 180, damping: 18 }}
>
<div className="ds-kicker">{step.id}</div>
<div className="ds-kicker">{step.label}</div>
<DrawLine delay={0.03} />
<h3 className="ds-card-title">{step.title}</h3>
<p className="ds-copy">{step.content}</p>
@@ -136,25 +131,25 @@ export default function About() {
className="mb-sp-6 h-[220px] w-full overflow-hidden rounded-[6px] border border-[var(--border)]"
imageClassName="object-cover"
/>
<div className="ds-kicker">System Signature</div>
<div className="ds-kicker">Technical profile</div>
<DrawLine delay={0.05} />
<h3 className="ds-card-title">Base tecnica e postura de entrega</h3>
<p className="ds-copy">
Evandro Fonseca Junior. Engenheiro de Software Full-Stack com foco
na convergência entre hardware e aplicações web modernas.
Especialista em TypeScript, Node.js, ecossistema React e automação
de dispositivos físicos.
Sou engenheiro de software full-stack com foco em integrar camadas
que normalmente ficam separadas: hardware, servicos, operacao e
interface. Meu trabalho e dar forma clara ao que costuma nascer
fragmentado.
</p>
<div className="ds-grid-2 mt-sp-6">
{VALUES_DATA.map((value) => (
<div className="mt-sp-6 space-y-sp-4">
{PROFILE_POINTS.map((point) => (
<div
key={value.title}
key={point.title}
className="border-t border-[var(--border)] pt-sp-4"
>
<div className="text-[11px] font-medium uppercase tracking-[0.08em]">
{value.title}
<div className="text-[12px] font-medium tracking-[0.02em]">
{point.title}
</div>
<p className="ds-copy !mt-sp-2">{value.description}</p>
<p className="ds-copy !mt-sp-2">{point.description}</p>
</div>
))}
</div>
@@ -163,3 +158,5 @@ export default function About() {
</Section>
);
}
+44 -31
View File
@@ -10,6 +10,8 @@ const WHATSAPP_NUMBER = "5547992742365";
export default function Contact() {
const shouldReduceMotion = useReducedMotion();
const fieldClassName =
"mt-sp-2 w-full border border-[rgba(255,255,255,.12)] bg-[rgba(255,255,255,.06)] px-sp-4 py-[14px] text-[13px] leading-6 text-white outline-none transition-[border-color,box-shadow] focus-visible:border-[var(--slash)] focus-visible:ring-2 focus-visible:ring-[rgba(204,255,0,0.34)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)]";
const [formData, setFormData] = useState({
name: "",
email: "",
@@ -55,9 +57,9 @@ export default function Contact() {
return (
<Section
id="contact"
eyebrow="CONTACT_PROTOCOL.init"
title="Vamos discutir o proximo sistema"
description="Descreva o contexto, o gargalo ou o produto. O contato abre direto no WhatsApp com briefing estruturado."
eyebrow="Contact"
title="Consultoria tecnica para software, arquitetura e integracoes criticas"
description="Fale comigo se voce precisa de engenharia de software, consultoria tecnica, arquitetura de software ou apoio em projetos com Next.js, Tauri, Rust e IoT."
dark
disableChildrenAnimation
>
@@ -82,18 +84,22 @@ export default function Contact() {
whileTap={shouldReduceMotion ? undefined : { scale: 0.995 }}
transition={{ type: "spring", stiffness: 180, damping: 18 }}
>
<div className="ds-kicker">Response SLA</div>
<div className="ds-kicker">Availability</div>
<DrawLine delay={0.04} />
<h3 className="ds-card-title">Retorno inicial em ate 24h</h3>
<h3 className="ds-card-title">Resposta inicial em ate 24h</h3>
<p className="ds-copy">
Ideal para discovery tecnico, revisao de arquitetura, rescue de
projeto, otimizacao de performance ou construcao de produto do zero.
projeto, performance ou construcao de produto com ownership real.
</p>
<div className="mt-sp-6 space-y-sp-4 text-[12px] leading-7 text-[rgba(255,255,255,.7)]">
<div className="mt-sp-6 space-y-sp-4 text-[13px] leading-7 text-[rgba(255,255,255,.85)]">
<div>WhatsApp: +55 47 99274-2365</div>
<div>E-mail: evandrofonsecajunior@gmail.com</div>
<div>Base: Garuva, Santa Catarina | atuacao remota</div>
</div>
<div className="mt-sp-6 border-t border-[rgba(255,255,255,.08)] pt-sp-4 text-[12px] leading-6 text-[rgba(255,255,255,.75)]">
Envie contexto, prazo e objetivo. Eu cuido de transformar isso em
conversa tecnica objetiva.
</div>
</motion.div>
<motion.form
@@ -111,54 +117,60 @@ export default function Contact() {
transition={{ type: "spring", stiffness: 180, damping: 18 }}
>
<div>
<label htmlFor="contact-name" className="ds-kicker block">
<label
htmlFor="contact-name"
className="block text-[12px] font-medium text-[rgba(255,255,255,.85)]"
>
Nome completo
</label>
<input
id="contact-name"
name="name"
required
type="text"
autoComplete="name"
value={formData.name}
onChange={(e) =>
setFormData({ ...formData, name: e.target.value })
}
className="mt-sp-2 w-full border border-[rgba(255,255,255,.12)] bg-[rgba(255,255,255,.06)] px-sp-4 py-[14px] text-[12px] outline-none transition-colors focus:border-slash"
style={{
color: "#fff",
backgroundColor: "rgba(255,255,255,0.06)",
}}
className={fieldClassName}
/>
</div>
<div>
<label htmlFor="contact-email" className="ds-kicker block">
E-mail corporativo
<label
htmlFor="contact-email"
className="block text-[12px] font-medium text-[rgba(255,255,255,.85)]"
>
E-mail
</label>
<input
id="contact-email"
name="email"
required
type="email"
autoComplete="email"
value={formData.email}
onChange={(e) =>
setFormData({ ...formData, email: e.target.value })
}
className="mt-sp-2 w-full border border-[rgba(255,255,255,.12)] bg-[rgba(255,255,255,.06)] px-sp-4 py-[14px] text-[12px] outline-none transition-colors focus:border-slash"
style={{
color: "#fff",
backgroundColor: "rgba(255,255,255,0.06)",
}}
className={fieldClassName}
/>
</div>
<div>
<label htmlFor="contact-subject" className="ds-kicker block">
<label
htmlFor="contact-subject"
className="block text-[12px] font-medium text-[rgba(255,255,255,.85)]"
>
Tipo de projeto
</label>
<select
id="contact-subject"
name="subject"
value={formData.subject}
onChange={(e) =>
setFormData({ ...formData, subject: e.target.value })
}
className="mt-sp-2 w-full border border-[rgba(255,255,255,.12)] px-sp-4 py-[14px] text-[12px] outline-none transition-colors focus:border-slash"
className={fieldClassName}
style={{ color: "#fff", backgroundColor: "#1a1a1a" }}
>
<option value="full-stack">Desenvolvimento Full-Stack</option>
@@ -168,33 +180,34 @@ export default function Contact() {
</select>
</div>
<div>
<label htmlFor="contact-message" className="ds-kicker block">
Detalhes do projeto
<label
htmlFor="contact-message"
className="block text-[12px] font-medium text-[rgba(255,255,255,.85)]"
>
Contexto do projeto
</label>
<textarea
id="contact-message"
name="message"
required
rows={6}
autoComplete="off"
value={formData.message}
onChange={(e) =>
setFormData({ ...formData, message: e.target.value })
}
className="mt-sp-2 w-full resize-none border border-[rgba(255,255,255,.12)] bg-[rgba(255,255,255,.06)] px-sp-4 py-[14px] text-[12px] outline-none transition-colors focus:border-slash"
style={{
color: "#fff",
backgroundColor: "rgba(255,255,255,0.06)",
}}
className={`${fieldClassName} resize-none`}
/>
</div>
<motion.button
type="submit"
className="ds-link ds-link-accent border-0"
className="ds-link ds-link-accent min-h-12 border-0"
whileHover={shouldReduceMotion ? undefined : { scale: 1.03 }}
whileTap={shouldReduceMotion ? undefined : { scale: 0.97 }}
transition={{ type: "spring", stiffness: 260, damping: 20 }}
>
Iniciar protocolo de contato
Enviar briefing pelo WhatsApp
</motion.button>
</motion.form>
</motion.div>
+18 -22
View File
@@ -1,11 +1,10 @@
"use client";
"use client";
import { useMemo } from "react";
import { motion, type Variants, useReducedMotion } from "framer-motion";
import { usePortfolioData } from "@/lib/usePortfolioData";
import { Section } from "./Section";
import { DrawLine } from "./animations/DrawLine";
import { CounterValue } from "./animations/CounterValue";
import { SectionLoading } from "./SectionLoading";
export default function Experience() {
@@ -24,11 +23,11 @@ export default function Experience() {
}, [data]);
if (loading) {
return <SectionLoading message="Loading system logs" />;
return <SectionLoading message="Loading selected experience" />;
}
if (error || !data) {
return <div className="ds-error">Error loading system logs</div>;
return <div className="ds-error">Error loading selected experience</div>;
}
const listVariants: Variants = {
@@ -58,9 +57,9 @@ export default function Experience() {
return (
<Section
id="experience"
eyebrow="SYSTEM_LOGS.history"
title="Trajetoria focada em produto, legado e crescimento sustentavel"
description="Experiencias recentes onde combinei entrega rapida com responsabilidade arquitetural."
eyebrow="Selected experience"
title="Experiencia em engenharia de software e sistemas criticos"
description="Projetos e contextos em que combinei arquitetura de software, integracoes complexas e entrega com responsabilidade operacional."
disableChildrenAnimation
>
<motion.div
@@ -86,33 +85,28 @@ export default function Experience() {
whileTap={shouldReduceMotion ? undefined : { scale: 0.995 }}
transition={{ type: "spring", stiffness: 170, damping: 20 }}
>
<div className="flex flex-col lg:grid lg:grid-cols-[1fr_auto] gap-sp-4 items-start">
<div>
<div className="flex flex-col gap-sp-4 lg:flex-row lg:items-start lg:justify-between">
<div className="max-w-[720px]">
<div className="ds-kicker">{exp.period}</div>
<DrawLine delay={0.04} />
<h3 className="ds-card-title">{exp.title}</h3>
<div className="mt-sp-3 text-[11px] uppercase tracking-[0.1em] text-meta">
{exp.company} | {exp.type}
</div>
</div>
<div className="flex flex-wrap items-start justify-start gap-sp-3 lg:justify-end">
<span className="ds-tag">{exp.location}</span>
<span className="ds-tag">
<CounterValue
value={exp.technologies.length}
suffix=" tecnologias"
/>
</span>
<p className="mt-sp-3 text-[13px] leading-6 text-[rgba(10,10,10,.64)] dark:text-[rgba(255,255,255,.6)]">
{exp.company} • {exp.type}
</p>
</div>
<div className="ds-metric">{exp.location}</div>
</div>
<p className="ds-copy">{exp.description}</p>
<ul className="ds-list">
{exp.achievements?.map((achievement) => (
<li key={achievement}>{achievement}</li>
))}
</ul>
<ul className="ds-tags mt-sp-6">
{exp.technologies?.map((tech) => (
{exp.technologies?.slice(0, 6).map((tech) => (
<li key={tech} className="ds-tag">
{tech}
</li>
@@ -124,3 +118,5 @@ export default function Experience() {
</Section>
);
}
+48 -33
View File
@@ -1,23 +1,10 @@
"use client";
/**
* Footer Component
*
* Displays contact information, social links, and copyright in a consistent footer layout.
* Follows the minimalist design system with Monument Extended typography and #ccff00 accent color.
*
* Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7
*/
// TypeScript interface for social media links
export interface SocialLink {
name: string; // Display name (e.g., "GitHub")
href: string; // Full URL
icon: string; // SVG path or icon identifier
ariaLabel: string; // Accessibility label
name: string;
href: string;
icon: string;
ariaLabel: string;
}
// Social links data
const socialLinks: SocialLink[] = [
{
name: "GitHub",
@@ -39,6 +26,16 @@ const socialLinks: SocialLink[] = [
},
];
const quickLinks = [
{ label: "Hub de servicos", href: "/servicos" },
{ label: "Cases", href: "/cases" },
{ label: "Consultoria Next.js", href: "/servicos/consultoria-nextjs" },
{
label: "Integracao hardware + software",
href: "/servicos/integracao-hardware-software-iot",
},
];
export default function Footer() {
const currentYear = new Date().getFullYear();
@@ -46,22 +43,26 @@ export default function Footer() {
<footer className="ds-section">
<div className="ds-section-shell">
<div className="ds-grid-2">
{/* Contact & Legal */}
<div className="ds-card flex flex-col justify-between">
<div>
<div className="ds-kicker">SYSTEM_INFO</div>
<div className="ds-kicker">Contato</div>
<span className="ds-draw-line" />
<h3 className="ds-card-title">
Transformando complexidade técnica em sistemas estáveis
Engenharia com menos ruido e mais clareza de execucao
</h3>
<p className="ds-copy max-w-[48ch]">
Se voce precisa de alguem para conectar produto, integracao e
entrega com mais criterio tecnico, este portfolio e o ponto de
partida.
</p>
<div className="mt-sp-6">
<p className="text-[12px] uppercase tracking-wider text-[var(--meta)] mb-2">
Contato
<p className="mb-2 text-[12px] tracking-[0.02em] text-[var(--meta)]">
E-mail direto
</p>
<a
href="mailto:[email protected]"
className="inline-block text-[18px] font-bold text-[var(--ink)] hover:text-[var(--slash)] transition-colors duration-200"
className="inline-block text-[18px] font-bold text-[var(--ink)] transition-colors duration-200 hover:text-[var(--slash)]"
>
evandro@dev.br
</a>
@@ -70,21 +71,20 @@ export default function Footer() {
<div className="mt-sp-8">
<div className="ds-metric">
<span className="inline-block h-1.5 w-1.5 rounded-full bg-slash animate-pulse" />
<span className="inline-block h-1.5 w-1.5 animate-pulse rounded-full bg-slash" />
Garuva, SC | Remote worldwide
</div>
<p className="mt-sp-4 text-[12px] text-[var(--meta)]">
© {currentYear} Evandro Fonseca Junior. All rights reserved.
Copyright {currentYear} Evandro Fonseca Junior. All rights reserved.
</p>
</div>
</div>
{/* Social Links */}
<div className="ds-card flex flex-col justify-between">
<div>
<div className="ds-kicker">SOCIAL_LINKS</div>
<div className="ds-kicker">Presenca digital</div>
<span className="ds-draw-line" />
<h3 className="ds-card-title">Redes profissionais</h3>
<h3 className="ds-card-title">Canais profissionais</h3>
<div className="mt-sp-6 flex flex-col gap-sp-4">
{socialLinks.map((link) => (
@@ -94,27 +94,42 @@ export default function Footer() {
target="_blank"
rel="noopener noreferrer"
aria-label={link.ariaLabel}
className="group flex items-center justify-between p-sp-4 border border-[var(--border)] bg-[rgba(10,10,10,0.02)] rounded-[8px] hover:bg-[rgba(10,10,10,0.04)] hover:border-[var(--slash)] transition-all duration-300"
className="group flex items-center justify-between rounded-[8px] border border-[var(--border)] bg-[rgba(10,10,10,0.02)] p-sp-4 transition-all duration-300 hover:border-[var(--slash)] hover:bg-[rgba(10,10,10,0.04)]"
>
<div className="flex items-center gap-sp-3">
<svg
className="w-5 h-5 text-[var(--meta)] group-hover:text-[var(--ink)] transition-colors"
className="h-5 w-5 text-[var(--meta)] transition-colors group-hover:text-[var(--ink)]"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path d={link.icon} />
</svg>
<span className="text-[14px] font-medium text-[var(--ink-80)] group-hover:text-[var(--ink)] transition-colors">
<span className="text-[14px] font-medium text-[var(--ink-80)] transition-colors group-hover:text-[var(--ink)]">
{link.name}
</span>
</div>
<span className="text-[var(--slash)] opacity-0 -translate-x-2 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-300 font-bold">
→
<span className="text-[12px] tracking-[0.08em] text-[var(--meta)] transition-colors group-hover:text-[var(--slash)]">
abrir
</span>
</a>
))}
</div>
<div className="mt-sp-8 border-t border-[var(--border)] pt-sp-5">
<div className="ds-kicker">Explorar</div>
<div className="mt-sp-4 grid gap-sp-3 md:grid-cols-2">
{quickLinks.map((link) => (
<a
key={link.href}
href={link.href}
className="rounded-[8px] border border-[var(--border)] bg-[rgba(10,10,10,0.02)] px-sp-4 py-sp-3 text-[13px] leading-6 text-[var(--ink-80)] transition-colors duration-200 hover:border-[var(--slash)] hover:text-[var(--ink)]"
>
{link.label}
</a>
))}
</div>
</div>
</div>
<div className="mt-sp-8 ds-metric">Open to collaboration</div>
+6 -2
View File
@@ -5,10 +5,14 @@ import Script from "next/script";
const GA_MEASUREMENT_ID = "G-QTT9QT0T3S";
export default function GoogleAnalytics() {
if (process.env.NODE_ENV !== "production") {
return null;
}
return (
<>
<Script src="/sys/libs/vendor-utils.js" strategy="afterInteractive" />
<Script id="google-analytics" strategy="afterInteractive">
<Script src="/sys/libs/vendor-utils.js" strategy="lazyOnload" />
<Script id="google-analytics" strategy="lazyOnload">
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
+107 -117
View File
@@ -30,21 +30,27 @@ const SOCIAL_LINKS = [
},
];
const HERO_POINTS = [
"Produtos conectados, integracoes complexas e operacoes criticas.",
"Arquitetura mais clara para evoluir sem aumentar o caos tecnico.",
"Execucao ponta a ponta, do discovery a entrega em producao.",
];
const PROOF_ITEMS = [
{
label: "Especialidade",
label: "Quem eu ajudo",
value:
"Produtos conectados, interfaces de alta performance e sistemas criticos.",
"Empresas que dependem de integracoes, hardware ou fluxos criticos para operar com seguranca e previsibilidade.",
},
{
label: "Escopo",
label: "Como entrego",
value:
"Da integracao com hardware e APIs complexas ao frontend e operacao.",
"Traduzo complexidade tecnica em backlog claro, arquitetura legivel e decisao mais rapida para o time.",
},
{
label: "Formato",
label: "Por que conversar",
value:
"Consultoria, execucao ponta a ponta e colaboracao remota para squads.",
"Voce ganha um parceiro tecnico capaz de conectar produto, backend, interface e operacao sem perder contexto.",
},
];
@@ -73,39 +79,44 @@ export default function Hero() {
/>
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-40 bg-gradient-to-t from-[var(--ink)] to-transparent" />
<div className="relative z-10 mx-auto flex w-full max-w-[1240px] flex-col px-sp-6 py-sp-16 lg:px-sp-16 lg:py-0 pointer-events-none">
<div className="relative z-10 mx-auto flex w-full max-w-[1240px] flex-col px-sp-6 py-sp-16 lg:px-sp-16 lg:py-sp-12">
<motion.div
initial={shouldReduceMotion ? { opacity: 0 } : { opacity: 0, y: 24 }}
animate={shouldReduceMotion ? { opacity: 1 } : { opacity: 1, y: 0 }}
transition={{ duration: 0.8, ease: [0.16, 1, 0.3, 1] }}
className="grid items-end gap-sp-10 lg:grid-cols-[minmax(0,1.2fr)_minmax(320px,0.8fr)] pointer-events-auto"
className="grid items-start gap-sp-8 lg:grid-cols-[minmax(0,1.1fr)_minmax(340px,0.9fr)] lg:gap-sp-10"
>
<div className="max-w-[720px]">
<div className="mb-sp-6 inline-flex items-center gap-sp-3 border border-[rgba(255,255,255,0.12)] bg-[rgba(10,10,10,0.4)] px-4 py-2 backdrop-blur-xl">
<motion.span
className="h-2 w-2 bg-[var(--slash)]"
animate={
shouldReduceMotion
? { opacity: 1 }
: { opacity: [1, 0.25, 1] }
}
transition={
shouldReduceMotion
? { duration: 0 }
: {
duration: 1.5,
ease: "easeInOut",
repeat: Number.POSITIVE_INFINITY,
}
}
/>
<span className="font-mono text-[10px] uppercase tracking-[0.2em] text-[rgba(244,242,237,.82)] drop-shadow-md">
Available for selected builds {"//"} Remote worldwide
</span>
<div className="max-w-[760px]">
<div className="mb-sp-5 flex flex-wrap items-center gap-sp-3">
<div className="inline-flex items-center gap-sp-3 border border-[rgba(255,255,255,0.12)] bg-[rgba(10,10,10,0.42)] px-4 py-2 backdrop-blur-xl">
<motion.span
className="h-2 w-2 bg-[var(--slash)]"
animate={
shouldReduceMotion
? { opacity: 1 }
: { opacity: [1, 0.25, 1] }
}
transition={
shouldReduceMotion
? { duration: 0 }
: {
duration: 1.5,
ease: "easeInOut",
repeat: Number.POSITIVE_INFINITY,
}
}
/>
<span className="font-mono text-[10px] uppercase tracking-[0.12em] text-[rgba(244,242,237,.82)]">
Available for selected builds
</span>
</div>
<div className="inline-flex items-center gap-sp-2 border border-[rgba(255,255,255,0.08)] bg-[rgba(255,255,255,0.02)] px-4 py-2 text-[11px] text-[rgba(244,242,237,.72)] backdrop-blur-xl">
Produtos conectados, integracoes criticas e entrega ponta a ponta
</div>
</div>
<motion.p
className="mb-sp-4 max-w-[220px] font-mono text-[10px] uppercase tracking-[0.22em] text-[var(--slash)] drop-shadow-lg"
className="mb-sp-4 max-w-[540px] font-mono text-[10px] uppercase tracking-[0.12em] text-[var(--slash)]"
initial={
shouldReduceMotion ? { opacity: 0 } : { opacity: 0, y: 12 }
}
@@ -118,19 +129,18 @@ export default function Hero() {
ease: [0.16, 1, 0.3, 1],
}}
>
Software engineering for products, operations & critical flows
Evandro Fonseca Junior | Engenheiro de software full-stack
</motion.p>
<h1
className="max-w-[920px] text-balance font-display text-[clamp(2.9rem,7vw,6.4rem)] font-extrabold leading-[0.92] tracking-[-0.04em] text-transparent bg-clip-text bg-gradient-to-br from-[#ffffff] to-[#a3a3a3]"
aria-label="evandro.dev.br"
className="max-w-[920px] text-balance font-display text-[clamp(3rem,7vw,6.2rem)] font-extrabold leading-[0.9] tracking-[-0.055em] text-transparent bg-clip-text bg-gradient-to-br from-[#ffffff] to-[#a7a7a7]"
aria-label="Engenharia para sistemas que nao podem falhar"
>
Engenharia de software para produtos conectados, interfaces de
alta performance e sistemas criticos.
Engenharia de software para sistemas que nao podem falhar.
</h1>
<motion.p
className="mt-sp-6 max-w-[640px] text-[15px] leading-7 text-[rgba(244,242,237,.9)] lg:text-[16px] drop-shadow-md"
className="mt-sp-6 max-w-[680px] text-[16px] leading-8 text-[rgba(244,242,237,.88)] lg:text-[17px]"
initial={
shouldReduceMotion ? { opacity: 0 } : { opacity: 0, y: 16 }
}
@@ -139,15 +149,40 @@ export default function Hero() {
}
transition={{
duration: 0.8,
delay: 0.16,
delay: 0.14,
ease: [0.16, 1, 0.3, 1],
}}
>
Da integracao com hardware e APIs complexas ao frontend moderno,
construo sistemas estaveis, operaveis e prontos para crescer sem
sacrificar clareza tecnica.
Ajudo empresas com produtos conectados, integracoes complexas e
operacoes criticas a transformar arquitetura confusa em software
estavel, mais rapido de evoluir e muito mais facil de operar.
</motion.p>
<motion.ul
className="mt-sp-8 grid gap-sp-3 sm:grid-cols-3"
initial={
shouldReduceMotion ? { opacity: 0 } : { opacity: 0, y: 16 }
}
animate={
shouldReduceMotion ? { opacity: 1 } : { opacity: 1, y: 0 }
}
transition={{
duration: 0.8,
delay: 0.22,
ease: [0.16, 1, 0.3, 1],
}}
>
{HERO_POINTS.map((point) => (
<li
key={point}
className="flex min-w-0 gap-sp-3 rounded-[8px] border border-[rgba(255,255,255,0.08)] bg-[rgba(255,255,255,0.02)] px-sp-4 py-sp-4 text-[13px] leading-6 text-[rgba(244,242,237,.82)] backdrop-blur-sm"
>
<span className="mt-[8px] h-1.5 w-1.5 shrink-0 rounded-full bg-[var(--slash)]" />
<span>{point}</span>
</li>
))}
</motion.ul>
<motion.div
className="mt-sp-8 flex flex-wrap items-center gap-sp-4"
initial={
@@ -158,32 +193,32 @@ export default function Hero() {
}
transition={{
duration: 0.8,
delay: 0.24,
delay: 0.3,
ease: [0.16, 1, 0.3, 1],
}}
>
<motion.a
href={`https://wa.me/5547992742365?text=${encodeURIComponent(greeting)}`}
className="inline-flex min-h-12 items-center justify-center bg-[var(--slash)] px-sp-8 font-mono text-[11px] font-bold uppercase tracking-[0.12em] text-[#0A0A0A] no-underline transition-[transform,background-color,box-shadow] duration-300 hover:bg-white hover:shadow-[0_0_20px_rgba(204,255,0,0.4)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--slash)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)]"
className="inline-flex min-h-12 items-center justify-center bg-[var(--slash)] px-sp-8 font-mono text-[11px] font-bold uppercase tracking-[0.1em] text-[#0A0A0A] no-underline transition-[transform,background-color,box-shadow] duration-300 hover:bg-white hover:shadow-[0_0_20px_rgba(204,255,0,0.4)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--slash)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)]"
whileHover={shouldReduceMotion ? undefined : { scale: 1.02 }}
whileTap={shouldReduceMotion ? undefined : { scale: 0.98 }}
>
Iniciar projeto
Agendar conversa tecnica
</motion.a>
<motion.a
href="#projects"
className="inline-flex min-h-12 items-center justify-center border border-[rgba(244,242,237,.28)] bg-[rgba(10,10,10,0.4)] backdrop-blur-md px-sp-8 font-mono text-[11px] font-bold uppercase tracking-[0.12em] text-[#f4f2ed] no-underline transition-[transform,border-color,background-color,color] duration-300 hover:border-[var(--slash)] hover:bg-[rgba(204,255,0,0.08)] hover:text-[var(--slash)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--slash)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)]"
href="/cases"
className="inline-flex min-h-12 items-center justify-center border border-[rgba(244,242,237,.24)] bg-[rgba(10,10,10,0.4)] px-sp-8 font-mono text-[11px] font-bold uppercase tracking-[0.1em] text-[#f4f2ed] no-underline transition-[transform,border-color,background-color,color] duration-300 hover:border-[var(--slash)] hover:bg-[rgba(204,255,0,0.08)] hover:text-[var(--slash)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--slash)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)]"
whileHover={shouldReduceMotion ? undefined : { scale: 1.02 }}
whileTap={shouldReduceMotion ? undefined : { scale: 0.98 }}
>
Ver projetos
Ver casos selecionados
</motion.a>
<a
href="/assets/Evandro_Fonseca_Junior_CV.pdf"
download
className="inline-flex min-h-12 items-center justify-center px-sp-2 text-[12px] font-medium text-[rgba(244,242,237,.9)] no-underline transition-colors duration-300 hover:text-[var(--slash)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--slash)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)] drop-shadow-md"
className="inline-flex min-h-12 items-center justify-center px-sp-2 text-[12px] font-medium text-[rgba(244,242,237,.76)] no-underline transition-colors duration-300 hover:text-[var(--slash)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--slash)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)]"
>
Download CV
</a>
@@ -199,15 +234,15 @@ export default function Hero() {
}
transition={{
duration: 0.8,
delay: 0.32,
delay: 0.38,
ease: [0.16, 1, 0.3, 1],
}}
>
<div className="inline-flex items-center gap-sp-3 text-[11px] uppercase tracking-[0.12em] text-[rgba(244,242,237,.72)]">
<div className="inline-flex items-center gap-sp-3 text-[11px] uppercase tracking-[0.08em] text-[rgba(244,242,237,.68)]">
<span className="h-2 w-2 rounded-full bg-[var(--slash)] shadow-[0_0_8px_var(--slash)]" />
Garuva, SC / Brasil
</div>
<div className="inline-flex items-center gap-sp-3 text-[11px] uppercase tracking-[0.12em] text-[rgba(244,242,237,.72)]">
<div className="inline-flex items-center gap-sp-3 text-[11px] uppercase tracking-[0.08em] text-[rgba(244,242,237,.68)]">
<span className="h-[1px] w-6 bg-[rgba(255,255,255,.2)]" />
Next.js, Tauri, Rust, Node.js
</div>
@@ -222,7 +257,7 @@ export default function Hero() {
? "noopener noreferrer"
: undefined
}
className="inline-flex h-11 w-11 items-center justify-center rounded-full border border-[rgba(244,242,237,0.18)] bg-[rgba(10,10,10,0.4)] backdrop-blur-md text-[rgba(244,242,237,.88)] transition-[transform,border-color,color,background-color] duration-300 hover:border-[var(--slash)] hover:bg-[rgba(204,255,0,0.1)] hover:text-[var(--slash)] hover:shadow-[0_0_15px_rgba(204,255,0,0.2)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--slash)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)]"
className="inline-flex h-11 w-11 items-center justify-center rounded-full border border-[rgba(244,242,237,0.16)] bg-[rgba(10,10,10,0.34)] text-[rgba(244,242,237,.86)] transition-[transform,border-color,color,background-color] duration-300 hover:border-[var(--slash)] hover:bg-[rgba(204,255,0,0.1)] hover:text-[var(--slash)] hover:shadow-[0_0_15px_rgba(204,255,0,0.18)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--slash)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)]"
aria-label={link.label}
>
<svg
@@ -232,8 +267,7 @@ export default function Hero() {
fill={link.stroke ? "none" : "currentColor"}
stroke={link.stroke ? "currentColor" : "none"}
strokeWidth={link.stroke ? "2" : undefined}
role="img"
aria-label={link.label}
aria-hidden="true"
>
{link.icon}
</svg>
@@ -244,28 +278,26 @@ export default function Hero() {
</div>
<motion.aside
className="relative overflow-hidden border border-[rgba(255,255,255,0.15)] bg-[linear-gradient(180deg,rgba(10,10,10,0.6),rgba(10,10,10,0.3))] p-sp-6 backdrop-blur-xl lg:p-sp-8"
className="relative overflow-hidden border border-[rgba(255,255,255,0.12)] bg-[linear-gradient(180deg,rgba(10,10,10,0.58),rgba(10,10,10,0.36))] p-sp-6 backdrop-blur-xl lg:p-sp-8"
initial={
shouldReduceMotion ? { opacity: 0 } : { opacity: 0, x: 24 }
}
animate={shouldReduceMotion ? { opacity: 1 } : { opacity: 1, x: 0 }}
transition={{ duration: 0.9, delay: 0.22, ease: [0.16, 1, 0.3, 1] }}
>
<div className="absolute inset-x-0 top-0 h-px bg-[linear-gradient(90deg,transparent,rgba(204,255,0,0.9),transparent)]" />
<div className="absolute inset-y-0 left-0 w-px bg-[linear-gradient(180deg,rgba(204,255,0,0.4),transparent)]" />
<div className="absolute inset-x-0 top-0 h-px bg-[linear-gradient(90deg,transparent,rgba(204,255,0,0.8),transparent)]" />
<div className="flex items-start justify-between gap-sp-4 border-b border-[rgba(255,255,255,0.08)] pb-sp-5">
<div>
<div className="font-mono text-[10px] uppercase tracking-[0.18em] text-[var(--slash)]">
Execution Brief
</div>
<h2 className="mt-sp-2 font-display text-[clamp(1.35rem,3vw,2rem)] font-extrabold leading-[1.02] tracking-[-0.03em] text-[#f7f4ee]">
Clareza de arquitetura antes de complexidade.
</h2>
</div>
<div className="shrink-0 border border-[rgba(204,255,0,0.3)] bg-[rgba(204,255,0,0.05)] px-3 py-2 font-mono text-[10px] uppercase tracking-[0.14em] text-[var(--slash)]">
2026
<div className="border-b border-[rgba(255,255,255,0.08)] pb-sp-5">
<div className="font-mono text-[10px] uppercase tracking-[0.14em] text-[var(--slash)]">
Why this positioning works
</div>
<h2 className="mt-sp-3 font-display text-[clamp(1.35rem,3vw,2rem)] font-extrabold leading-[1.04] tracking-[-0.03em] text-[#f7f4ee]">
Menos atrito entre produto, engenharia e operacao.
</h2>
<p className="mt-sp-4 text-[14px] leading-7 text-[rgba(244,242,237,.78)]">
Entro onde a complexidade cresce e ajudo a devolver clareza,
ritmo de entrega e confiabilidade para o sistema e para o time.
</p>
</div>
<div className="mt-sp-6 space-y-sp-5">
@@ -281,68 +313,26 @@ export default function Hero() {
}
transition={{
duration: 0.65,
delay: 0.28 + index * 0.08,
delay: 0.3 + index * 0.08,
ease: [0.16, 1, 0.3, 1],
}}
>
<div className="font-mono text-[10px] uppercase tracking-[0.18em] text-[rgba(244,242,237,.7)]">
<div className="font-mono text-[10px] uppercase tracking-[0.12em] text-[rgba(244,242,237,.56)]">
{item.label}
</div>
<p className="mt-sp-2 text-[14px] leading-6 text-[rgba(244,242,237,.94)]">
<p className="mt-sp-2 text-[14px] leading-7 text-[rgba(244,242,237,.92)]">
{item.value}
</p>
</motion.article>
))}
</div>
<motion.div
className="mt-sp-8 grid gap-sp-4 sm:grid-cols-2"
initial={
shouldReduceMotion ? { opacity: 0 } : { opacity: 0, y: 16 }
}
animate={
shouldReduceMotion ? { opacity: 1 } : { opacity: 1, y: 0 }
}
transition={{
duration: 0.75,
delay: 0.48,
ease: [0.16, 1, 0.3, 1],
}}
>
<div className="border border-[rgba(255,255,255,0.1)] bg-[rgba(10,10,10,0.4)] p-sp-4 hover:border-[var(--slash)] transition-colors duration-300">
<div className="font-mono text-[10px] uppercase tracking-[0.18em] text-[var(--slash)]">
Focus
</div>
<p className="mt-sp-2 text-[13px] leading-6 text-[rgba(244,242,237,.9)]">
Sistemas com baixa latencia, alta previsibilidade e manutencao
sustentavel.
</p>
</div>
<div className="border border-[rgba(255,255,255,0.1)] bg-[rgba(10,10,10,0.4)] p-sp-4 hover:border-[var(--slash)] transition-colors duration-300">
<div className="font-mono text-[10px] uppercase tracking-[0.18em] text-[var(--slash)]">
Availability
</div>
<p className="mt-sp-2 text-[13px] leading-6 text-[rgba(244,242,237,.9)]">
Aberto a novos projetos com ownership tecnico, discovery e
entrega iterativa.
</p>
</div>
</motion.div>
</motion.aside>
</motion.div>
</div>
<motion.div
className="absolute bottom-10 left-1/2 flex -translate-x-1/2 flex-col items-center gap-3 opacity-50 z-10 pointer-events-none"
initial={shouldReduceMotion ? { opacity: 0 } : { opacity: 0 }}
animate={shouldReduceMotion ? { opacity: 0.5 } : { opacity: 0.5 }}
transition={{ delay: 1, duration: 1 }}
>
<span className="font-mono text-[9px] uppercase tracking-widest text-[var(--slash)]">
Scroll
</span>
<div className="h-10 w-[1px] bg-gradient-to-b from-[var(--slash)] to-transparent" />
</motion.div>
</section>
);
}
+33 -35
View File
@@ -5,7 +5,6 @@ import { motion, type Variants, useReducedMotion } from "framer-motion";
import { usePortfolioData } from "@/lib/usePortfolioData";
import { Section } from "./Section";
import { DrawLine } from "./animations/DrawLine";
import { CounterValue } from "./animations/CounterValue";
import { SectionLoading } from "./SectionLoading";
export default function Projects() {
@@ -18,11 +17,11 @@ export default function Projects() {
);
if (loading) {
return <SectionLoading dark message="Loading featured projects" />;
return <SectionLoading dark message="Loading selected work" />;
}
if (error) {
return <div className="ds-error">Error loading featured projects</div>;
return <div className="ds-error">Error loading selected work</div>;
}
const gridVariants: Variants = {
@@ -52,9 +51,9 @@ export default function Projects() {
return (
<Section
id="projects"
eyebrow="PRODUCAO_INTELECTUAL"
title="Projetos com recorte tecnico claro e impacto observavel"
description="Selecao de produtos e ferramentas onde performance, confiabilidade e clareza de arquitetura foram requisitos reais."
eyebrow="Selected work"
title="Projetos em Next.js, Tauri, Rust e integracao de sistemas"
description="Cases e produtos que mostram minha atuacao em engenharia de software, consultoria tecnica e construcao de sistemas de alta exigencia."
dark
disableChildrenAnimation
>
@@ -82,19 +81,29 @@ export default function Projects() {
transition={{ type: "spring", stiffness: 170, damping: 18 }}
>
<div className="flex items-start justify-between gap-sp-6">
<div>
<div className="max-w-[560px]">
<div className="ds-kicker">{project.category}</div>
<DrawLine delay={0.05} />
<h3 className="ds-card-title">{project.title}</h3>
<p className="mt-sp-2 text-[11px] uppercase tracking-[0.1em] text-[rgba(255,255,255,.36)]">
{project.subtitle}
</p>
{project.subtitle && (
<p className="mt-sp-3 text-[13px] leading-6 text-[rgba(255,255,255,.56)]">
{project.subtitle}
</p>
)}
</div>
<span className="ds-tag">{project.status}</span>
</div>
<p className="ds-copy">{project.description}</p>
<ul className="ds-tags mt-sp-6">
{project.technologies.slice(0, 5).map((tech: string) => (
<li key={tech} className="ds-tag">
{tech}
</li>
))}
</ul>
<div className="mt-sp-6 flex flex-wrap gap-sp-3">
{project.githubUrl && (
<motion.a
@@ -106,7 +115,7 @@ export default function Projects() {
whileTap={shouldReduceMotion ? undefined : { scale: 0.97 }}
transition={{ type: "spring", stiffness: 260, damping: 20 }}
>
GitHub
Ver codigo
</motion.a>
)}
{project.demoUrl && (
@@ -119,36 +128,25 @@ export default function Projects() {
whileTap={shouldReduceMotion ? undefined : { scale: 0.97 }}
transition={{ type: "spring", stiffness: 260, damping: 20 }}
>
Live demo
Abrir projeto
</motion.a>
)}
</div>
<ul className="ds-tags mt-sp-6">
{project.technologies.slice(0, 6).map((tech: string) => (
<li key={tech} className="ds-tag">
{tech}
</li>
))}
</ul>
<div className="mt-sp-6 flex flex-wrap gap-sp-4">
<span className="ds-metric">
Complexity{" "}
<CounterValue
value={project.metrics?.complexity || 3}
suffix="/5"
/>
</span>
{project.metrics?.teamSize && (
<span className="ds-metric">
Team {project.metrics.teamSize}
</span>
)}
</div>
</motion.article>
))}
</motion.div>
<div className="mt-sp-8 flex flex-wrap gap-sp-3">
<a href="/cases" className="ds-link ds-link-accent">
Explorar todos os cases
</a>
<a href="/servicos" className="ds-link ds-link-muted">
Ver servicos relacionados
</a>
</div>
</Section>
);
}
+14 -15
View File
@@ -1,10 +1,9 @@
"use client";
"use client";
import { motion, type Variants, useReducedMotion } from "framer-motion";
import { usePortfolioData } from "@/lib/usePortfolioData";
import { Section } from "./Section";
import { DrawLine } from "./animations/DrawLine";
import { CounterValue } from "./animations/CounterValue";
import { SectionLoading } from "./SectionLoading";
export default function Skills() {
@@ -12,11 +11,11 @@ export default function Skills() {
const shouldReduceMotion = useReducedMotion();
if (loading) {
return <SectionLoading dark message="Loading skill registry" />;
return <SectionLoading dark message="Loading core stack" />;
}
if (error || !data) {
return <div className="ds-error">Error loading skill registry</div>;
return <div className="ds-error">Error loading core stack</div>;
}
const gridVariants: Variants = {
@@ -46,9 +45,9 @@ export default function Skills() {
return (
<Section
id="skills"
eyebrow="MODULE_REGISTRY.lib"
title="Stack modular para produto, operacao e escala"
description="Ferramentas escolhidas por previsibilidade operacional, velocidade de entrega e facilidade de manutencao."
eyebrow="Core stack"
title="Next.js, Tauri, Rust e stack para produtos criticos"
description="Tecnologias e fundamentos que uso em engenharia de software, arquitetura de software, IoT e integracoes complexas."
dark
disableChildrenAnimation
>
@@ -59,7 +58,7 @@ export default function Skills() {
viewport={{ once: true, amount: 0.2 }}
variants={gridVariants}
>
{data.skills.technical.map((category, index) => (
{data.skills.technical.map((category) => (
<motion.article
key={category.category}
className="ds-card h-full"
@@ -75,14 +74,12 @@ export default function Skills() {
whileTap={shouldReduceMotion ? undefined : { scale: 0.99 }}
transition={{ type: "spring", stiffness: 180, damping: 18 }}
>
<div className="flex items-center justify-between gap-sp-4">
<div className="ds-kicker">ID_0{index + 1}X</div>
<div className="ds-metric">
<CounterValue value={category.skills.length} suffix=" items" />
</div>
</div>
<h3 className="ds-card-title !mt-0">{category.category}</h3>
<DrawLine delay={0.05} />
<h3 className="ds-card-title mb-sp-8">{category.category}</h3>
<p className="ds-copy !mt-sp-4">
Ferramentas que uso para construir com clareza tecnica e ritmo de
entrega.
</p>
<ul className="ds-tags">
{category.skills.map((skill) => (
<li key={skill} className="ds-tag">
@@ -96,3 +93,5 @@ export default function Skills() {
</Section>
);
}
+63 -26
View File
@@ -1,6 +1,8 @@
"use client";
import Link from "next/link";
import { motion, type Variants, useReducedMotion } from "framer-motion";
import { serviceHubLinks } from "@/lib/site-content";
import { Section } from "./Section";
import { DrawLine } from "./animations/DrawLine";
@@ -87,8 +89,7 @@ function TierCard({ tier }: Readonly<{ tier: Tier }>) {
return (
<motion.article
tabIndex={0}
className={`ds-card h-full relative outline-none ${
className={`ds-card relative flex h-full min-w-0 flex-col overflow-hidden outline-none max-md:w-full max-md:max-w-none max-md:px-sp-5 max-md:py-sp-5 ${
tier.featured ? "border-[var(--slash)] shadow-sm" : ""
}`}
style={{
@@ -105,42 +106,50 @@ function TierCard({ tier }: Readonly<{ tier: Tier }>) {
whileTap={shouldReduceMotion ? undefined : { scale: 0.99 }}
transition={{ type: "spring", stiffness: 180, damping: 18 }}
>
<div className="flex justify-between items-start">
<span className="ds-kicker">{tier.badge}</span>
<span className="font-display text-[11px] font-extrabold tracking-widest text-[var(--meta)]">
<div className="flex min-w-0 items-start justify-between gap-sp-4">
<span className="ds-kicker max-md:text-[10px] max-md:leading-[1.6]">
{tier.badge}
</span>
<span className="text-[11px] tracking-[0.08em] text-[var(--meta)]">
{tier.level}
</span>
</div>
<DrawLine delay={0.04} />
<div className="mt-sp-4">
<h3 className="ds-card-title">{tier.title}</h3>
<p className="mt-sp-2 text-[11px] font-medium leading-relaxed text-[var(--meta)]">
<div className="mt-sp-4 min-w-0">
<h3 className="ds-card-title !mt-0 text-balance !text-[clamp(22px,5vw,28px)] !leading-[1.02] !tracking-[-0.035em] [overflow-wrap:break-word] [word-break:normal]">
{tier.title}
</h3>
<p className="mt-sp-2 text-[12px] leading-[1.7] text-[var(--meta)] md:text-[11px]">
{tier.tagline}
</p>
</div>
<p className="ds-copy">{tier.description}</p>
<p className="ds-copy !mt-sp-4 !text-[13px] !leading-[1.75] [overflow-wrap:break-word] [word-break:normal] md:!text-[12px]">
{tier.description}
</p>
<ul className="ds-list">
<ul className="ds-list !mt-sp-5 !pl-[1.15rem] !text-[13px] !leading-[1.75] [overflow-wrap:break-word] [word-break:normal] md:!text-[12px]">
{tier.deliverables.map((deliverable) => (
<li key={deliverable}>{deliverable}</li>
<li key={deliverable} className="pl-sp-1">
{deliverable}
</li>
))}
</ul>
<div className="mt-sp-6 border-t border-[var(--border)] pt-sp-4">
<div className="font-display text-[clamp(14px,1.5vw,18px)] font-extrabold tracking-tight">
<div className="mt-sp-6 space-y-sp-2 border-t border-[var(--border)] pt-sp-5">
<div className="font-display text-[clamp(17px,4.6vw,18px)] font-extrabold leading-none tracking-tight">
{tier.pricing}
</div>
<div className="mt-1 text-[10px] tracking-widest text-[var(--meta)] uppercase">
<div className="text-[12px] leading-[1.7] text-[var(--meta)]">
{tier.pricingNote}
</div>
</div>
<motion.a
href={tier.ctaHref}
className={`ds-link mt-sp-6 justify-center w-full ${
className={`ds-link mt-sp-6 min-h-[50px] w-full justify-center px-sp-4 py-sp-3 text-center text-[11px] leading-none ${
tier.featured ? "ds-link-accent" : "ds-link-muted"
}`}
whileHover={shouldReduceMotion ? undefined : { scale: 1.02 }}
@@ -148,7 +157,6 @@ function TierCard({ tier }: Readonly<{ tier: Tier }>) {
transition={{ type: "spring", stiffness: 260, damping: 20 }}
>
{tier.ctaLabel}
{" ->"}
</motion.a>
</motion.article>
);
@@ -184,25 +192,53 @@ export function WorkModalities() {
return (
<Section
id="modalities"
eyebrow="CONTRACT_PROTOCOLS.sys"
title="Engenharia sob demanda. Previsibilidade de entrega sem overhead."
description="Tres niveis de contratacao desenhados como uma esteira de valor, do diagnostico rapido a parceria estrategica de longo prazo."
eyebrow="Working models"
title="Servicos de engenharia de software e consultoria tecnica."
description="Formatos de contratacao para arquitetura de software, integracoes criticas, produtos conectados e evolucao de sistemas em producao."
disableChildrenAnimation
>
<div className="mb-sp-8 ds-card bg-[rgba(10,10,10,0.02)]">
<div className="flex flex-col gap-sp-4 lg:flex-row lg:items-end lg:justify-between">
<div className="max-w-[760px]">
<div className="ds-kicker">Paginas de servico</div>
<h3 className="ds-card-title !mt-sp-3">Especialidades organizadas por intencao de busca</h3>
<p className="ds-copy !mt-sp-3 max-w-[62ch]">
Se voce chegou buscando uma stack, um tipo de consultoria ou um contexto tecnico especifico,
aqui esta o caminho mais direto para a pagina certa.
</p>
</div>
<Link href="/servicos" className="ds-link ds-link-accent">
Ver hub de servicos
</Link>
</div>
<div className="mt-sp-6 grid gap-sp-3 md:grid-cols-2 xl:grid-cols-3">
{serviceHubLinks.map((service) => (
<Link
key={service.href}
href={service.href}
className="rounded-[8px] border border-[var(--border)] bg-white/70 px-sp-4 py-sp-4 transition-colors duration-200 hover:border-[var(--slash)] hover:bg-white"
>
<div className="text-[12px] font-semibold tracking-[0.02em] text-[var(--ink)]">
{service.label}
</div>
<div className="mt-sp-2 text-[12px] leading-6 text-[var(--meta)]">
{service.description}
</div>
</Link>
))}
</div>
</div>
<motion.div
style={{
display: "grid",
gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
gap: "var(--sp-6)",
}}
className="max-md:grid-cols-1 max-md:!flex max-md:!flex-col"
className="grid grid-cols-1 gap-sp-4 md:grid-cols-2 md:gap-sp-5 lg:grid-cols-3 lg:gap-sp-6"
initial="hidden"
whileInView="visible"
viewport={{ once: true, amount: 0.2 }}
variants={containerVariants}
>
{TIERS.map((tier) => (
<motion.div key={tier.id} variants={itemVariants}>
<motion.div key={tier.id} variants={itemVariants} className="min-w-0">
<TierCard tier={tier} />
</motion.div>
))}
@@ -210,3 +246,4 @@ export function WorkModalities() {
</Section>
);
}
+5 -13
View File
@@ -21,7 +21,6 @@ export function LazyRevealImage({
}: Readonly<LazyRevealImageProps>) {
const shouldReduceMotion = useReducedMotion();
const [loaded, setLoaded] = useState(false);
const [animating, setAnimating] = useState(!shouldReduceMotion);
return (
<motion.div
@@ -29,28 +28,24 @@ export function LazyRevealImage({
initial={
shouldReduceMotion
? { opacity: 0 }
: { opacity: 0, filter: "blur(20px)", scale: 1.05 }
: { opacity: 0, y: 20, scale: 1.02 }
}
animate={
loaded
? shouldReduceMotion
? { opacity: 1 }
: { opacity: 1, filter: "blur(0px)", scale: 1 }
: { opacity: 1, y: 0, scale: 1 }
: shouldReduceMotion
? { opacity: 0 }
: { opacity: 0, filter: "blur(20px)", scale: 1.05 }
: { opacity: 0, y: 20, scale: 1.02 }
}
transition={{
duration: shouldReduceMotion ? 0.25 : 0.75,
ease: [0.16, 1, 0.3, 1],
}}
style={{
willChange:
animating && !shouldReduceMotion
? "filter, transform, opacity"
: undefined,
willChange: !shouldReduceMotion ? "transform, opacity" : undefined,
}}
onAnimationComplete={() => setAnimating(false)}
>
<Image
src={src}
@@ -59,10 +54,7 @@ export function LazyRevealImage({
loading="lazy"
sizes={sizes}
placeholder={typeof src === "string" ? "empty" : "blur"}
onLoad={() => {
setAnimating(true);
setLoaded(true);
}}
onLoad={() => setLoaded(true)}
className={imageClassName}
/>
</motion.div>