# Design: endurecimento técnico do portfólio **Status:** aprovado para planejamento; implementação não iniciada **Data:** 2026-09-04 ## Objetivo Reduzir fragilidade operacional, JavaScript global e código sem consumidores sem redesenhar o portfólio. O resultado mantém a identidade preto/papel/verde ácido, a tipografia e a narrativa visual, enquanto torna projetos, foco, movimento, dependências, analytics, deploy e validação previsíveis. ## Estado de checkout que faz parte do alvo A implementação futura trabalhará no checkout atual, sem worktree. Essa decisão é deliberada porque as alterações não commitadas abaixo são parte do estado-alvo e haverá um único editor; a pesquisa paralela é somente leitura: - `data/project-health.json` modificado pelo usuário: não regenerar nem reverter nesta iniciativa. - `lib/portfolio-data.ts` modificado pelo usuário: evoluir a partir do conteúdo atual, preservando a atualização apenas em desenvolvimento. - `globals.css` na raiz, não rastreado: não editar, mover, importar ou apagar. O stylesheet ativo é `app/globals.css`. - `lib/portfolio-data.load.test.ts` não rastreado: manter como baseline e atualizar suas expectativas somente quando o contrato de projetos mudar. Não usar branch, commit, reset, checkout, stash ou comandos Git destrutivos. Rollback será por arquivo e por tarefa, usando os snapshots lidos antes de cada edição. ## Escopo e arquitetura atual observada - Next.js App Router com React 19 e TypeScript estrito. - `app/page.tsx` carrega projetos no servidor por `loadLiveFeaturedProjects()` e repassa dados para `CinematicHome`. - `filterLiveProjects()` hoje transforma um snapshot ausente/expirado em lista vazia, ocultando todo projeto. - `app/layout.tsx` envolve todas as rotas em Lenis/GSAP, contexto de animação, transição de rota e overlays; `PageIntro` cria um portal modal que cobre a página por tempo fixo. - A home soma motion local, GSAP/ScrollTrigger redundante, cursores/overlays e loops contínuos de canvas. - `GoogleAnalytics` carrega GA em produção sem consentimento e usa rewrites que camuflam script/coleta. - `next.config.ts` marca diretórios públicos com nomes estáveis como `immutable` e mantém CSP somente em relatório com `unsafe-inline` e `unsafe-eval`. - Não existe workflow em `.github/workflows`. - `pnpm-lock.yaml` resolve Next `16.2.9`, contém overrides históricos e foi gerado com faixas, embora `package.json` não fixe o conjunto de dependências. - `nixpacks.toml` já escolhe Node 22 e pnpm 9, mas instala com `--no-frozen-lockfile`. - Há componentes e testes sem consumidores de produção, incluindo dois caminhos de contato, biblioteca de demos/magicui e infraestrutura de animação não consumida. ## Mapa de capacidades | Módulo | Responsabilidade | Depende de | | --- | --- | --- | | Projetos e saúde de demo | Sempre fornecer conteúdo; anexar estado observacional do link | JSON estático, snapshot opcional | | Shell acessível | Entregar conteúdo/foco sem overlay bloqueante e sem scroll engine global | layout, CSS global | | Movimento e performance | Preservar efeitos essenciais, remover duplicação e pausar trabalho invisível | hooks locais, Intersection Observer, Page Visibility | | Dependências e deploy | Fixar runtime/dependências e produzir lock reproduzível | código morto já removido | | Analytics e headers | Consentimento opt-in, revogação e carregamento direto de GA | layout, `next/script`, configuração de headers | | CI | Instalação congelada e gates determinísticos | lock final e scripts do pacote | | Documentação operacional | Descrever apenas comandos e comportamento realmente presentes | implementação concluída | Direção de dependência: conteúdo estático → projeção com saúde → home; shell/CSS → componentes; código vivo → manifesto de dependências → lock/deploy/CI. Nenhum componente de UI consulta rede para decidir se conteúdo existe. ## Decisões ### 1. Projeto é conteúdo; saúde é apenas estado do link Substituir o contrato de “projetos vivos” por uma projeção que mantém todo projeto com `featured !== false` e anexa o estado da demo: ```ts export type DemoAvailability = "available" | "unavailable" | "unknown" | "none"; export type PortfolioProjectView = PortfolioProject & { demoAvailability: DemoAvailability; demoCheckedAt: string | null; }; ``` Regras: 1. `featured === false` continua sendo a única regra de exclusão editorial. 2. Sem `demoUrl`, o estado é `none` e não há CTA de demo. 3. Sem entrada de snapshot, o estado é `unknown`; o card permanece e o URL configurado não é descartado. 4. Resposta final `200` com `Content-Type` HTML produz `available`. 5. Qualquer resultado explícito diferente produz `unavailable`. 6. `unavailable` renderiza a evidência inteira e um controle não navegável com texto “Demo indisponível na última verificação”. 7. `unknown` mantém o link e o anota como “Disponibilidade não verificada”. 8. Em desenvolvimento, snapshot ausente ou mais antigo que quinze minutos pode disparar uma atualização em memória; falha de rede retorna os projetos com o último estado conhecido/`unknown`. 9. Em produção, render nunca chama demos externas. O snapshot versionado é observacional e conteúdo não depende de cron, validade temporal ou rede. `app/page.tsx` deixa de usar ISR de cinco minutos para simular monitoramento. `scripts/check-projects.mjs` continua como comando manual de atualização atômica, não como requisito de disponibilidade do site. ### 2. Acessibilidade não depende de animação - Remover `PageIntro` e seu teste; não substituir por outro splash. - Manter o skip link e garantir que nenhum overlay o cubra. - Colocar o reset de cor de links na camada base do Tailwind, permitindo que `text-white`, `text-slate-*` e estados hover da 404 prevaleçam. - Em `prefers-reduced-motion: reduce`, desativar smooth scroll e reduzir animações/transições CSS a uma atualização praticamente instantânea. - Fallbacks estáticos permanecem com o mesmo conteúdo e ações essenciais. ### 3. Um efeito por intenção, não camadas redundantes A assinatura visual preservada é: paleta, fontes, hero “command center”, globo, starfield, notebook 3D quando elegível, cartões, divisores e tipografia de marquee. Serão removidos: - Lenis e ticker GSAP do layout global. - Transição de rota global. - `AnimationProvider`, pois nenhum componente de produção consome seu contexto. - `ReelDirector` e `CinematicSection`, que repetem revelações já feitas por componentes locais. - Cursor customizado e seu `MutationObserver`, que reaplica listeners sobre a árvore. - canvas de film grain; a textura passa a CSS estático. - loops JS de marquee e divisores; tornam-se animações CSS/SVG sem estado React por frame. GSAP/ScrollTrigger permanece somente na história do notebook, onde controla uma timeline scroll-driven real. Motion permanece nos componentes que já implementam feedback/reveal local. Nenhuma biblioteca nova será adicionada. Loops restantes obedecem aos dois sinais: ```ts const shouldRun = isIntersecting && !document.hidden && !shouldReduceMotion; ``` `HeroStarfield` já segue esse contrato e será preservado. `NotebookStory` combinará interseção e `visibilitychange`; o globo manterá flags separadas para interseção e visibilidade do documento para não reativar fora da viewport. ### 4. Hero terá somente a variante ativa `Hero` mantém exclusivamente “command-center”. Remover pesos, seleção aleatória, cinco blocos editoriais não alcançáveis, layouts alternativos e branches de `SideVisual`. `HeroScene` perde o parâmetro `variant`, hoje ignorado. O conteúdo visível, CTAs, globo, telemetria, links sociais e fallback de reduced motion permanecem. ### 5. Remoção de código morto é um corte limpo Excluir, após confirmar todos os callers na execução: - `ContactForm`, `ContactInfo` e `usePortfolioData`; `Contact` é o caminho ativo. - `CompanyLogos` e seus três arquivos de teste. - todos os demos na raiz de `components/` e `components/magicui/`, sem importadores. - `AnimationProvider`, seu teste, `app/lib/animations/config.ts`, `app/lib/animations/variants.ts` e `app/hooks/useScrollReveal.ts`. - `FloatingElements`, `HomeButton`, `HorizontalScroll` e `CinematicText`, sem importadores. - componentes de performance substituídos/removidos na decisão 3 e seus testes de implementação órfãos. Não manter aliases, reexports ou shims. ### 6. Testes protegem comportamento observável - Reescrever os testes de saúde para provar que todos os projetos editoriais permanecem e apenas o estado da demo muda. - Preservar um teste de desenvolvimento que prova atualização em memória sem permitir fetch em produção. - Cobrir o CTA disponível, indisponível e desconhecido de `Projects` por nome/estado acessível. - Remover PBTs de `CompanyLogos` junto do componente. - Remover PBTs do Footer que repetem um componente estático; `Footer.test.tsx` já cobre nomes acessíveis e segurança de links. - Substituir três PBTs de `Experience` por testes determinísticos de filtro editorial e ordem cronológica; hierarquia de heading não precisa de geração aleatória. - Remover a asserção que fixa classes internas do Footer e a asserção da hero que inspeciona props serializadas de motion. - Remover `fast-check` quando seu último consumidor desaparecer. ### 7. Runtime e dependências serão reproduzíveis - `engines.node`: `22.x`. - `packageManager`: `pnpm@9.15.9`. - Next.js: `16.3.0`, menor versão estável da mesma major que eliminou as três vulnerabilidades high mantidas por `16.2.11` e `16.2.12`. - Todas as dependências diretas e de desenvolvimento terão versão exata, sem `^`, `~` ou comparadores. - `@types/node` acompanha o runtime em `22.20.1`. - `@types/three` passa para `devDependencies`. - Remover pacotes cujo último consumidor foi apagado: `@gsap/react`, `class-variance-authority`, `lenis`, `ogl`, `tailwindcss-animate`, `tw-animate-css`, `fast-check`, `@types/resize-observer-browser` e `resize-observer-polyfill`. - Manter GSAP, cobe, Motion, React Three Fiber, Drei e Three porque ainda há consumidores ativos. - Regenerar `pnpm-lock.yaml` com pnpm 9.15.9 e sem bloco `overrides`. - Corrigir advisories atualizando o pacote direto proprietário da cadeia; não usar `audit --force`, exceções ou novos overrides. - Nixpacks usa `pnpm install --frozen-lockfile`. ### 8. CI terá um caminho único Criar `.github/workflows/ci.yml` para `push` e `pull_request`, em Ubuntu, Node 22 e pnpm 9.15.9. Ordem: 1. checkout; 2. instalação congelada com cache do pnpm; 3. `pnpm lint`; 4. `pnpm typecheck`; 5. `pnpm test -- --runInBand`; 6. `pnpm build`; 7. `pnpm audit --prod --audit-level=high`; 8. `pnpm audit --dev --audit-level=high`. Não adicionar Playwright, Cypress ou outro browser ao projeto. O smoke visual/interativo usa o Chromium disponível no harness durante a implementação; CI fica restrita aos gates aprovados. ### 9. Analytics exige opt-in e permite revogação Aplicar consentimento a todos os visitantes, sem geolocalização de IP: é menor, previsível e não arrisca carregar GA antes de uma decisão quando a obrigação legal se aplica. - `GoogleAnalytics` começa sem `