Evandro Fonseca Junior 3756905a1b
CI / verify (push) Failing after 6m28s
fix(transicao): a home nao pisca mais duas vezes ao abrir
A hidratacao e a revelacao de Suspense tambem sao transicoes do React, e o <ViewTransition name="page"> do template animava a pagina inteira (page-out/page-in) nas duas, logo na chegada. Com update="none", so a troca de pagina anima.
2026-10-14 01:13:44 -03:00
2026-03-18 06:45:44 -03:00
2025-09-01 18:43:26 -03:00

Evandro.dev.br: software sob medida

Site comercial de Evandro Fonseca Junior para projetos de software sob medida: sistemas empresariais, aplicativos e integrações, com cases e contato direto por WhatsApp.

Requisitos

  • Node.js 22.x
  • pnpm 9.15.9, fixado em package.json#packageManager

O projeto usa Next.js 16.3.3, patch de segurança que corrige GHSA-p293-qw3h-jr36 e GHSA-2xp9-vwfh-vxw4. A atualização substitui 16.3.0, afetada por esses avisos. As auditorias de produção e desenvolvimento continuam sendo verificadas na CI.

Início rápido

corepack [email protected] install --frozen-lockfile
corepack [email protected] dev

O servidor de desenvolvimento usa Turbopack. Para executar o artefato de produção:

corepack [email protected] build
corepack [email protected] start

Scripts

Comando Comportamento
pnpm dev Inicia o Next.js em desenvolvimento com Turbopack.
pnpm build Gera o build de produção com Turbopack.
pnpm start Serve um build já gerado.
pnpm lint Executa biome check.
pnpm typecheck Gera os tipos de rota com next typegen e executa TypeScript sem emitir arquivos.
pnpm format Formata o repositório com Biome.
pnpm test Executa a suíte Jest.
pnpm test:watch Executa Jest em modo watch.
pnpm check:projects Verifica URLs de demonstração e substitui atomicamente data/project-health.json.

Contagem de projetos do GitHub

O número "Mais de X projetos desenvolvidos" no topo da home vem da API do GitHub (lib/github-stats.ts), consultada só no servidor e renovada uma vez por dia (revalidate = 86400 em app/page.tsx). Conta repositórios próprios, públicos e privados, sem forks e sem arquivados.

Para incluir os privados, defina GITHUB_STATS_TOKEN no ambiente de build e de execução (não use o prefixo NEXT_PUBLIC_). O token deve ser um fine-grained personal access token com acesso a todos os repositórios da conta e uma única permissão, Metadata: Read-only. Sem o token, ou se a consulta falhar, o número simplesmente não aparece.

Vitrine de vídeos dos projetos

Cada projeto com site gravado aparece de três formas na seção Projetos da home. Tudo mora em app/components/showcase/, os dados em lib/project-showcase.ts e os estilos em showcase.css.

  • Cards. O vídeo de rolagem fica numa moldura de navegador com o endereço real do site. Só um vídeo toca por vez: o do card sob o mouse (ou com foco) e, sem mouse, o mais perto do centro da tela. Nada é baixado até o card tocar pela primeira vez, e um botão pausa os vídeos que se mexem sozinhos. Clicar na moldura abre o vídeo ampliado (<dialog> nativo, com troca computador/celular que mantém o ponto do site, capítulos clicáveis e controles próprios).
  • Vitrine animada. Logo abaixo dos cards, uma cena presa na rolagem (position: sticky) em que o site de cada projeto rola dentro de um notebook, com um celular ao lado, conforme o visitante rola a página. Os capítulos de cada projeto (chapters em lib/project-showcase.ts, marcados nos quadros dos vídeos) nomeiam o trecho na tela e mantêm notebook e celular no mesmo ponto do site. Só o projeto em cena e os vizinhos baixam vídeo, e nada baixa com "economizar dados" ligado. Com prefers-reduced-motion ou sem JavaScript ela some e os cards continuam mostrando os posters.
  • Capa de compartilhamento. /cases/<id> usa o poster do projeto como fundo da imagem Open Graph (lib/og-card.tsx), quando existe.

Arquivos de cada projeto em public/videos/<id>/:

Arquivo Para quê
desktop.{webm,mp4,webp} e mobile.{webm,mp4,webp} cards e vídeo ampliado (até 3 MB cada vídeo)
scrub-desktop.mp4 e scrub-mobile.mp4 vitrine animada: quadro-chave a cada 6 quadros, sem B-frames, para o tempo mudar a cada quadro da rolagem
og.jpg capa 1200x630 de compartilhamento

Para gravar ou regravar um projeto, use scripts/record-project-videos.mjs; para gerar os scrub-*.mp4, scripts/encode-scrub-video.mjs (as instruções de instalação do Playwright e do ffmpeg estão no cabeçalho de cada script). Depois de gravar, marque os capítulos do projeto em lib/project-showcase.ts.

Stack

Área Pacotes usados
Aplicação Next.js 16.3.3, React 19.1.0, React DOM 19.1.0 e TypeScript 5.9.2.
Estilos e composição Tailwind CSS 4, clsx e tailwind-merge.
Movimento Motion 12 e GSAP 3.
3D e gráficos Three.js, React Three Fiber e Drei.
Interface Lucide React.
Qualidade Biome, Jest, Testing Library e ambiente JSDOM.

Todas as dependências diretas estão fixadas em versões exatas no manifesto; o lockfile é instalado sem atualização implícita.

Rotas

Rota Conteúdo
/ Homepage comercial com hero, notebook 3D, projetos, soluções, processo de trabalho, engenharia com IA, critérios de entrega, perfil e contato.
/cases Índice dos seis cases.
/cases/nutriscan Case NutriScan.
/cases/twins-lab Case Twins Lab.
/cases/reddit-explorer Case Reddit Posts Explorer.
/cases/sc-plus Case SC Plus Aquicultura.
/cases/gcc-installer Case MinGW GCC Installer.
/cases/as-servicos Case AS Serviços.
/servicos Página de destino comercial: sites, sistemas e aplicativos, contato direto no WhatsApp, cases, processo e acesso aos seis serviços.
/servicos/criacao-de-sites Criação de sites.
/servicos/engenharia-de-software Sistemas web e aplicativos mobile sob medida.
/servicos/sob-medida Redirecionamento permanente (308) para /servicos/engenharia-de-software, preservando query string, UTMs e identificadores de clique.
/servicos/consultoria-nextjs Consultoria Next.js.
/servicos/tauri-rust Aplicativos desktop para Windows e Linux, com compatibilidade definida por projeto.
/servicos/integracao-hardware-software-iot Integração de hardware, software e IoT.
/servicos/arquitetura-de-software Arquitetura de software.
/curriculo Currículo em HTML, com download de /Profile.pdf.

O App Router também gera /manifest.webmanifest, /robots.txt, /sitemap.xml, /opengraph-image, /icon.png, /apple-icon.png e /favicon.ico. Caminhos desconhecidos usam a página 404 do projeto. Não há API route ativa.

As páginas de serviços mantêm escopo, exemplos ilustrativos e etapas visíveis; tecnologias e decisões de implementação ficam em um <details> nativo. Na homepage, competências e experiência também ficam recolhidas. O header prioriza Soluções (/#modalities), Projetos (/cases), Como trabalho (/#process), Sobre (/#about) e Contato (/#contact), com ajuste de chegada após a definição do layout do notebook. Currículo e download do PDF são links secundários no final do rodapé.

Comportamento da aplicação

Conteúdo e project health

O conteúdo principal vem de public/portfolio-data.json e lib/site-content.ts. A seleção da homepage usa o campo featured; o snapshot de disponibilidade não remove cards. Ele apenas controla o estado do CTA de cada demo: disponível, indisponível, desconhecido ou sem demo.

Em produção, a renderização só lê o snapshot versionado e não consulta demos externas. Em desenvolvimento, um snapshot ausente ou com mais de 15 minutos é atualizado em memória. A atualização manual opcional é pnpm check:projects e grava o arquivo por rename atômico. O contrato completo está em docs/project-health.md.

Posicionamento comercial e uso de IA

A comunicação apresenta Evandro como responsável direto pelo projeto, sem sugerir equipe, disponibilidade garantida, prazo fixo ou certificação. Os cards de projetos reutilizam os desafios e resumos dos cases quando disponíveis. O antigo painel de telemetria fictícia foi removido; os critérios de qualidade não representam notas de auditoria.

Cada card de projeto aparece ao entrar na tela, observado individualmente e sem recorte por clip-path. A revelação não depende de uma porcentagem visível do grid inteiro: em celulares, a lista de uma coluna pode ser várias vezes mais alta que a tela e tornar esse gatilho inalcançável.

app/components/EngineeringWithAI.tsx explica arquitetura, permissões, validação e recuperação de dados, com três relatos históricos de 2025 e links junto de cada afirmação:

  • Lovable: exposição relatada pela Semafor e CVE-2025-48757, incluindo a contestação do fornecedor no NVD; exposição não é apresentada como roubo comprovado.
  • Replit: exclusão de dados e restauração integral, conforme a resposta oficial da empresa.
  • Base44: falha de autenticação identificada pela Wiz, corrigida em menos de 24 horas, sem evidência reportada de exploração maliciosa.

As obrigações dos pequenos agentes de tratamento são referenciadas à Resolução CD/ANPD nº 2/2022, arts. 6º, 12 e 13. Ao atualizar esse conteúdo, preservar datas, limites dos relatos e a distinção entre vulnerabilidade, exposição e incidente confirmado.

O formulário valida os campos no navegador e abre um rascunho no WhatsApp para revisão. Ele não envia a mensagem automaticamente e orienta a não informar senhas nem dados pessoais de clientes.

A seção de contato tem título estático único, canais diretos à esquerda e formulário à direita (empilhados no celular). A silhueta do WhatsApp aparece no contato direto, no cabeçalho do formulário e no botão verde. O formulário não se move no hover; campos mantêm rótulos, validação nativa e foco visível. O link direto dispensa preenchimento e o e-mail usa mailto:.

Na página /servicos e nos seis detalhamentos, WhatsAppLink oferece um link nativo com mensagem contextual, sem exigir o formulário da home. Funciona mesmo sem consentimento de analytics ou JavaScript; a medição depende da hidratação e da tag. A página principal prioriza as três intenções dos anúncios antes das especialidades técnicas e usa os cases existentes NutriScan e SC Plus como exemplos de trabalho, não como promessa de retorno comercial.

3D progressivo e movimento reduzido

O hero combina a mensagem comercial com três placas 3D: interface, integrações e dados. A interface usa a captura real do NutriScan, recortada acima do rodapé; as outras camadas são ilustrativas, não uma declaração da arquitetura do projeto. Ponteiro, arraste horizontal e quatro botões permitem explorar a composição. O toque vertical continua rolando a página. A renderização para quando os movimentos terminam, fora do viewport e com a aba oculta. Movimento reduzido ou falha gráfica mantêm a captura HTML, os controles e o link do case disponíveis.

O notebook 3D permanece em celulares, tablets e desktop: tampa, tela, abas e cursor seguem o progresso do scroll. No mobile, a câmera adapta o enquadramento e botões externos à tela do notebook permitem selecionar os projetos. A cena renderiza sob demanda e pausa fora do viewport ou com a página oculta. Movimento reduzido, falha de WebGL ou perda de contexto ativam a navegação HTML sem animação.

As quatro capturas desktop reais ficam em public/assets/notebook/, em WebP de 1600 × 1000. A troca aguarda a decodificação da próxima imagem, mantendo a anterior visível durante o carregamento ou uma falha.

Uma entrada normal na homepage começa no topo. A recarga também volta ao início, removendo o fragmento deixado por uma navegação anterior. Links diretos como /#about continuam chegando à seção após a estabilização do notebook; rolagem deliberada durante a abertura cancela esse ajuste. A política inicial de scroll é aplicada antes da hidratação, sem disputar com a rolagem suave dos links posteriores. Assim que a chegada se resolve, a restauração volta a pertencer ao navegador: Voltar e Avançar restauram a posição do histórico em vez de forçar o topo.

Identidade visual

O header usa --site-header-height: 60px abaixo de 1280px e 68px a partir desse breakpoint. A mesma variável define a posição do menu, o respiro inicial do hero e a compensação das âncoras. O menu mobile tem fundo opaco; a barra fica levemente translúcida ao rolar. Header e hero usam Space Grotesk para leitura comercial, reservando a fonte monoespaçada aos detalhes técnicos das demais seções.

Os quatro arquivos recebidos estão preservados, com nomes descritivos, em imagens/originais/. As versões prontas para uso ficam em public/brand/:

Arquivo Aplicação
wordmark-dark.webp Logo horizontal transparente, branca e lima, no cabeçalho escuro.
wordmark-light.webp Logo horizontal transparente, escura e lima, no rodapé claro.
stacked-light.webp Logo empilhada transparente, reservada para composições maiores em fundo claro.
mark-dark.svg / mark-light.svg Símbolo vetorial para fundos escuros e claros, traçado a partir do original limpo.
mark-dark.png Símbolo de 512px incorporado aos cards Open Graph de todas as rotas.
icon-192.png Ícone de instalação do manifesto.

app/icon.png (512px), app/apple-icon.png (180px) e app/favicon.ico (16–256px) usam o símbolo sobre fundo escuro. As variantes mantêm as formas originais; o recorte transparente com bordas danificadas permanece arquivado, sem uso na interface.

Contato

O formulário da homepage valida os campos nativos de nome, e-mail, tipo e contexto. Ao enviar, ele abre wa.me em uma nova aba com o briefing preenchido; não existe backend de formulário. O rodapé mantém contato direto por e-mail.

Consentimento de analytics

Google Analytics só é carregado depois de aceite explícito. A decisão fica em evandro.analytics-consent.v1; recusar não monta o loader. As preferências permanecem acessíveis e a revogação envia consentimento negado, ativa a flag de desabilitação, remove o loader e expira apenas cookies _ga/_ga_* acessíveis.

O único loader gtag.js configura GA4 G-QTT9QT0T3S e Google Ads AW-18432694647. Os IDs GT-M3K4CKQT e GT-NBBR3PC2 informados no painel são identificadores das tags, não instalações adicionais. A integração segue https://developers.google.com/tag-platform/gtagjs/configure. ad_storage, ad_user_data e ad_personalization permanecem negados, com ads_data_redaction ativo. A tag só é montada em produção.

Medição do contato e Google Ads

lib/analytics.ts envia whatsapp_open exclusivamente ao GA4 após o acionamento de um link ou a submissão válida do formulário. O evento representa tentativa de abrir uma conversa, não mensagem recebida, lead confirmado ou venda. O contato não espera a resposta do Analytics. Eventos recusados ou ocorridos antes do consentimento não são guardados para envio posterior.

Parâmetros enviados: service (rótulo estático), placement (posição do CTA) e page_path. A URL do evento não inclui query, fragmento ou referenciador; rotas dinâmicas são agrupadas como /servicos/[slug] e /cases/[slug]. Nome, e-mail, mensagem digitada e URL de destino do WhatsApp não entram nesse evento. A distinção do serviço usa o parâmetro service.

Posições: service_hub_hero, service_hub_intent, service_hub_footer, service_detail_hero, service_detail_footer, contact_direct e contact_form. Isso permite comparar entradas de contato pelo tipo de serviço e pela posição na página. Cliques pelo menu de contexto, bloqueadores e falhas de rede podem não ser medidos.

Configuração externa ainda necessária: verificar whatsapp_open no GA4, registrá-lo como evento principal, vincular a propriedade à conta Ads correta e importar essa ação de conversão. Conferir a ação existente antes para evitar dupla contagem; não importar simultaneamente um clique automático como a mesma conversão. Para analisar os parâmetros em Explorações, cadastrar dimensões personalizadas de escopo de evento para service e placement. Nenhum rótulo de conversão Ads foi inventado e nenhuma configuração da conta foi alterada. A medição otimiza intenção de contato, não vendas.

Também revisar as opções de medição otimizada da propriedade, sobretudo eventos automáticos de cliques externos e formulários, pois essa configuração não é controlada por este código. Referência dos eventos: https://developers.google.com/analytics/devguides/collection/ga4/events.

Verificação local: build de produção, navegação responsiva, redirecionamento com parâmetros e requisição real whatsapp_open gerada pela tag. Requisições de coleta foram interceptadas antes do envio para não contaminar os relatórios; isso não comprova recebimento no GA4 nem atribuição no Ads.

Estrutura

app/                    App Router, rotas e componentes da interface
lib/                    Dados derivados, navegação, JSON-LD e project health
public/                 Conteúdo JSON, PDF, fontes, imagens e modelo 3D
scripts/check-projects.mjs
                        Atualização manual do snapshot de demos
data/project-health.json
                        Última observação versionada das demos
docs/project-health.md  Contrato operacional do snapshot
.github/workflows/ci.yml
                        Gate único de integração contínua
nixpacks.toml            Build e start para Nixpacks

CI e deploy

O workflow único em .github/workflows/ci.yml roda em cada push e pull_request com permissão contents: read. Ele instala Node 22 e pnpm conforme packageManager, restaura o cache do pnpm e executa, nesta ordem: frozen install, lint, typecheck, Jest serial, build e auditorias de produção e desenvolvimento com limite high. O passo de typecheck gera os tipos de rota antes do tsc: next-env.d.ts não é versionado e, em uma árvore recém-clonada, é ele que declara os módulos de imagem importados pelo código.

O Nixpacks usa nodejs_22, pnpm-9_x, pnpm install --frozen-lockfile, pnpm run build e pnpm run start. Não há etapa de deploy nem secret de CI neste repositório.

Validação local

corepack [email protected] install --frozen-lockfile
corepack [email protected] lint
corepack [email protected] typecheck
corepack [email protected] test -- --runInBand
corepack [email protected] build
corepack [email protected] audit --prod --audit-level=high
corepack [email protected] audit --dev --audit-level=high
S
Description
Portfólio profissional: projetos, cases, serviços e currículo
Readme
127 MiB
0 Stars 1 Watchers 0 Forks
Languages
TypeScript 90%
CSS 4.4%
JavaScript 3.5%
Python 2.1%