feat(site): blocos 3D da marca, novas ofertas e hero mais abrangente
CI / audit (push) Successful in 10m38s
CI / verify (push) Successful in 13m10s
CI / deploy (push) Successful in 1m50s
CI / e2e (push) Successful in 18m48s

Hero e movimento
- cena 3D em tempo real com os blocos da marca (kit feito no Blender, 3d/blocos.py):
  marca, tela, integrações e dados; segue a rolagem, tem vida em repouso, reage ao
  cursor e, parada no topo, passa sozinha pelos estados com legenda
- três níveis de aparelho (full, lite, static); sem WebGL fica o pôster
- título novo: "Sites e sistemas sob medida para o jeito que a sua empresa trabalha."
- faixa de clientes com o ramo, linha do primeiro passo, hero alinhado às seções
- rótulos digitados, entrada dos blocos, cortina de blocos entre páginas e capa
  compartilhada entre a lista de cases e o case

Ofertas
- atendimento no WhatsApp com IA e nota fiscal para a reforma tributária
  (páginas de serviço, cartões da home, formulário e aviso com data de validade)
- página /ia atualizada, com fontes
- peças 3D em loop nos cartões e topos de página (3d/pecas.py)

Design
- hover por papel (bloco nos botões, barra nos links), rótulos sem caixa alta
- um destino por rótulo: "Falar sobre meu projeto" leva ao formulário,
  "Chamar no WhatsApp" abre o WhatsApp
- logo montado em blocos, 404 redesenhado, grades sem célula vazia
- ícones do PWA e manifesto novos

Specs em specs/, contexto de design em DESIGN.md e PRODUCT.md.
This commit is contained in:
evandro committed 2026-11-10 03:48:25 -03:00
1 parent 4671837c44
commit c7d09d63d4
165 files changed
+7940 -3790

No files matched your search

+260
View File
@@ -0,0 +1,260 @@
# Blocos: hero 3D feito no Blender, movimento e ícone do app
**Autor:** Evandro · **Data:** 2026-11-09 · **Porte:** grande · **Status:** em execução
## 1. Objetivo
Trocar o fundo de partículas do hero por uma peça 3D modelada no Blender (os blocos da marca, que se remontam em tela, integrações e dados), dar ao site uma linguagem de movimento única (escrita, entrada e saída de componentes, troca de tela) e corrigir o ícone do aplicativo instalado. Hoje o 3D é um fundo discreto, só no computador, e o celular não recebe nada.
Leitura (frontend-orchestrator): refino com mundo visual novo no hero · Persuade (suposição S1) · web · `3d`, `premium` → impeccable, awwwards, threejs-fundamentals/geometry/materials/shaders/animation.
## 2. Escopo
- Dentro: ícones do manifesto; pipeline Blender → web em `3d/`; hero em três níveis (tempo real, tempo real leve, pôster); escrita, entrada/saída e troca de tela em CSS; peças 3D pré-renderizadas nos cartões de Soluções e no 404.
- Fora (não fazer): biblioteca de animação (GSAP, Motion) ou React Three Fiber, removidos em 2026-10-14 por desempenho; mudar texto, SEO ou estrutura das seções; service worker; modelos gerados por IA ou de bibliotecas de terceiros; vídeo de rolagem no celular; merge dos branches `wip/*`.
## 3. Critérios de aceite
- CA1 (ícone): o manifesto serve 4 ícones em URLs novas (`any` e `maskable`, 192 e 512) e `id: "/"`; no maskable a marca cabe na zona segura (caixa ≤ 57% do lado, centrada ±2%) → `pnpm test -- app/manifest.test.ts` e `node scripts/build-icons.mjs --check`.
- CA2 (ativos): `blender -b --python 3d/blocos.py` recria do zero `public/3d/` (`bloco.glb` ≤ 40 KB, `blocos.json` ≤ 40 KB em gzip, 2 matcaps ≤ 30 KB cada, `hero-poster.webp` ≤ 90 KB), com N ≤ 320 blocos e ≤ 60 mil triângulos → o comando sai com `OK` e código 0.
- CA3 (pôster): sem JavaScript ou com movimento reduzido o hero mostra o pôster da marca em blocos, todo o conteúdo visível, sem pedido a `/3d/bloco.glb` nem ao pacote do Three.js → `pnpm test:e2e -- hero-3d`.
- CA4 (computador): em aparelho `full` os blocos montam a marca em até 2,6 s e a rolagem leva marca → tela → integrações → dados dentro dos trechos de `hero-captions.ts` → `pnpm test -- lib/blocos.test.ts` e captura local com GPU nos progressos 0, 0,35, 0,62 e 0,85.
- CA5 (celular): em aparelho `lite` a cena roda com DPR ≤ 1,5 e 30 quadros por segundo sob demanda, trocando de estado sozinha enquanto está à vista; se a média dos 60 primeiros quadros passar de 28 ms, volta ao pôster e lembra na sessão → `pnpm test -- lib/device.test.ts BlocosScene` e inspeção num celular real.
- CA6 (desempenho): em 390×844 com rede e CPU limitadas, LCP < 2,5 s e continua sendo o H1, CLS < 0,05, os dois botões inteiros na primeira dobra → `shoot.mjs` do awwwards (`mobile.vitals`) sobre `pnpm build && pnpm start`.
- CA7 (escrita): rótulos em fonte mono entram digitados (cursor de bloco) e o trecho verde do H1 entra como marca-texto; o texto está no HTML desde o primeiro quadro; só `transform`, `opacity` e `clip-path` animam → `pnpm test -- Hero` e inspeção com movimento reduzido.
- CA8 (entrada e saída): menu do celular, vídeo ampliado, aviso de cookies, botão do WhatsApp e perguntas frequentes animam ao abrir e ao fechar, sem JavaScript novo; navegador sem suporte troca na hora → `pnpm test:e2e -- mobile-menu keyboard` verdes e inspeção.
- CA9 (troca de tela): a troca de página usa a cortina de blocos (≤ 700 ms) e a capa do projeto vira o topo do case; com movimento reduzido não há animação → `pnpm test:e2e -- navigation routes` verdes e captura dos quadros da troca.
- CA10 (peças): os 3 cartões de Soluções e o 404 mostram peças do mesmo kit renderizadas no Blender (loop ≤ 3 s, ≤ 250 KB por arquivo, pôster ≤ 40 KB), tocando só à vista, sem contexto WebGL extra → `ls -l public/3d` e `pnpm test:e2e`.
- CA11 (fechamento): `pnpm lint && pnpm typecheck && pnpm test && pnpm build && pnpm test:e2e` verdes; impeccable critique sem heurística abaixo de 3/4; audit sem falha; jury com 8 ou mais em cada categoria.
## 4. Contexto verificado
Código lido no `main` do Gitea (`4671837`, 2026-10-24), que é o que está no ar. O checkout local está em `b2f3899` (2026-10-05), 54 commits atrás, com `origin` num repositório do GitHub que não existe mais.
- `package.json`: só `three` 0.182 e `lenis`; animação é CSS (`animation-timeline`, View Transitions).
- `app/components/cinematic/HeroFieldScene.tsx:26,422,692,969,999`: nuvem de pontos procedural em três camadas, fundo ambiente. Padrão de referência do motor (renderizador, descarte, desenho por software).
- `app/components/cinematic/HeroField.tsx:195,201` e `lib/device.ts:42-53`: toque, ≤ 4 núcleos, ≤ 4 GB, economia de dados ou movimento reduzido não recebem cena nem baixam o Three.js.
- `app/components/Hero.tsx:64-66,92,143`: palco, H1 (LCP) e `HeroWork` (navegador e celular com trabalho real, 40% da largura).
- `app/components/hero-captions.ts`: legendas "A tela", "As integrações", "Os dados" com trechos 0,18–0,49, 0,51–0,75, 0,79–1,07.
- `app/manifest.ts:17-28`: dois ícones (`/brand/icon-192.png`, `/icon.png`), sem `purpose` nem `id`. Arquivos iguais aos do ar (md5) e com o símbolo de blocos atual. A marca ocupa 395×392 de 512 (77%), fora da zona segura.
- `app/template.tsx:29` e `app/globals.css:1038-1054`: troca de página por `<ViewTransition name="page">`, fade com escala.
- `app/globals.css:755-767,963-977,1075`: entrada do título, das seções (`ds-scene-in`, `view()`) e dos títulos por palavra. Não há saída animada.
- `app/components/WorkModalities.tsx:265,348`, `app/not-found.tsx:34-46` (404 fora da marca), `Faq.tsx:43`, `Header.tsx:247`, `showcase/ProjectLightbox.tsx:253`, `showcase/ProjectList.tsx:84`, `app/cases/_components/CaseCover.tsx:57`.
- Máquina: Blender 5.2.2 LTS (`io_scene_gltf2`, Cycles com OptiX na RTX 5060 Ti 16 GB, addon `blender_mcp`), `ffmpeg`, `magick`. Não há outro programa de 3D instalado.
## 5. Desenho
**Conceito.** Ideia: os blocos da marca são as peças com que se monta o site ou o sistema de cada cliente. Assinatura: os blocos se soltam e se remontam com a rolagem. Verbo: encaixar (chega rápido, assenta curto, sem quique). Quadro para compartilhar: a marca em porcelana com os quatro cubos verdes ao lado do título.
Decisões:
- Um kit, quatro estados (`marca`, `tela`, `integracoes`, `dados`) com o mesmo número de blocos. As três legendas atuais ficam como estão.
- Fonte é o script `3d/blocos.py` (mapas de blocos, chanfro, materiais, luz, exportação). O `.blend` é gerado em `3d/out/` (ignorado no git). Descartado: `.blend` modelado à mão e versionado, binário que ninguém revisa nem reconstrói.
- Tempo real com uma `InstancedMesh` e matcaps renderizados dos materiais do Blender; a troca de estado é interpolada no vertex shader. Descartado: luz e sombra em tempo real (peso no celular) e vídeo de rolagem (o projeto já o tirou do celular).
- `GLTFLoader` do próprio `three`, sem Draco nem meshopt (a geometria é um bloco só).
- Três níveis: `full` (regra atual), `lite` (toque, ≥ 6 núcleos, ≥ 4 GB ou não informado), `static` (o resto, e qualquer aparelho que estoure o orçamento de quadro).
- Movimento 2D só com CSS: `steps()` nos detalhes (cursor, cortina), ease-out no resto, `@starting-style` com `allow-discrete` para saída.
- Peças secundárias pré-renderizadas no Cycles (vídeo curto com pôster), nunca WebGL por cartão.
Interfaces:
```ts
// public/3d/blocos.json
{ n: number; estados: ["marca","tela","integracoes","dados"]; dados: number[] } // [estado][bloco] = x, y, z, escala, tom (0 porcelana, 1 lima)
// lib/blocos.ts
export function stateAt(p: number): { from: number; to: number; t: number }
// lib/device.ts
export type SceneTier = "full" | "lite" | "static";
export function sceneTier(): SceneTier
// app/components/cinematic/BlocosScene.tsx: mesmas props de HeroFieldSceneProps
export function prepareBlocos(): Promise<void>
export default function BlocosScene(props: HeroFieldSceneProps)
```
Mapa de arquivos. Criar: `3d/blocos.py`, `3d/pecas.py`, `public/3d/*`, `public/brand/pwa-*.png`, `scripts/build-icons.mjs`, `lib/blocos.ts`, `app/components/cinematic/BlocosScene.tsx`, `e2e/hero-3d.spec.ts`, `DESIGN.md`. Modificar: `app/manifest.ts`, `lib/device.ts`, `HeroField.tsx`, `Hero.tsx`, `app/globals.css`, `WorkModalities.tsx`, `not-found.tsx`, `ProjectList.tsx`, `CaseCover.tsx`. Remover no fim: `HeroFieldScene.tsx` e seus testes, `public/brand/icon-192.png`.
## 6. Restrições, riscos, suposições
- Restrições: Next 16 com APIs diferentes (ler `node_modules/next/dist/docs/` antes de codar, `AGENTS.md`); trabalho em branch, nunca na `main`; nenhum servidor de desenvolvimento em segundo plano (`pnpm build && pnpm start`, encerrado ao fim).
- Riscos: o CI roda sem GPU, então a cena em tempo real só se confere em captura local; o celular pode perder LCP (o orçamento de quadro e o CA6 seguram); sincronizar o checkout exige trocar o `origin`. Reverter é desfazer o branch; nada toca dados.
- O worktree `~/.cache/security-fixes/portifolio-evandrodevbr` (branch `chore/security-updates-2026-11-09`) nasceu do mesmo commit velho e vai conflitar com o `main` real.
- S1: o site é de venda (Persuade), com vitrine de portfólio.
- S2: há um celular Android e um iPhone para a inspeção dos CA5 e CA1.
- P1 (bloqueia T2): qual é a "logo nova"? O repositório e o site no ar já usam o símbolo de blocos nos ícones; nada mais novo foi encontrado. Resposta recomendada: é esse símbolo, e o que se vê no celular é ícone sem versão maskable mais o ícone guardado da instalação antiga.
- P2 (bloqueia T7): no computador, a peça 3D ocupa a coluna da direita e as molduras de `HeroWork` saem do hero (os projetos reais começam uma rolagem abaixo)? Resposta recomendada: sim.
## 7. Microtasks
### Fase 0 · base e ícone
### T0 · Sincronizar o checkout com o Gitea · N0
- Serve: todos · Depende de: "ok" do usuário
- Fazer: apagar `AGENTS.md` e `CLAUDE.md` não rastreados (idênticos aos do Gitea); `git remote set-url origin https://git.evandro.dev.br/evandro/portifolio-evandrodevbr.git`; `git pull --ff-only`; `git switch -c feat/blocos-3d-motion`; `pnpm install --frozen-lockfile`.
- Verificar: `git log -1 --format=%h` → `4671837` ou mais novo; `pnpm test` verde.
- [x] feito
### T1 · Escrever o teste do manifesto · N2
- Serve: CA1 · Depende de: T0 · Arquivos: criar `app/manifest.test.ts` · Padrão: `app/robots.test.ts`
- Fazer: afirmar `id === "/"` e exatamente estes ícones: `/brand/pwa-192.png` e `/brand/pwa-512.png` (`purpose: "any"`), `/brand/pwa-maskable-192.png` e `/brand/pwa-maskable-512.png` (`purpose: "maskable"`), todos `image/png`, e que cada arquivo existe em `public/`.
- Verificar: `pnpm test -- app/manifest.test.ts` → falha (ainda não implementado).
- [x] feito
### T2 · Gerar os ícones a partir do símbolo · N2
- Serve: CA1 · Depende de: T1, bloqueada por P1 · Arquivos: criar `scripts/build-icons.mjs` e os 4 PNG em `public/brand/`
- Fazer: com `sharp`, compor `public/brand/mark-dark.svg` sobre fundo `rgb(8,10,9)`. `any`: marca com 77% do lado. `maskable`: marca com 56% do lado, centrada. `--check` relê os PNG e falha se o tamanho ou a caixa da marca saírem do CA1.
- Verificar: `node scripts/build-icons.mjs && node scripts/build-icons.mjs --check` → `OK`.
- [x] feito
### T3 · Apontar o manifesto para os ícones novos · N2
- Serve: CA1 · Depende de: T2 · Arquivos: modificar `app/manifest.ts:17-28`; remover `public/brand/icon-192.png`
- Fazer: trocar a lista de ícones pela do T1 e acrescentar `id: "/"`.
- Verificar: `pnpm test -- app/manifest.test.ts` → passa; `grep -rn "icon-192" app lib scripts e2e` → vazio.
- [x] feito
### CP1 · após T0–T3 · N0
- Verificar: `pnpm lint && pnpm typecheck && pnpm test && pnpm build`. Depois do deploy, reinstalar o app no Android e no iPhone (o iPhone só troca o ícone reinstalando) e conferir o ícone.
### Fase 1 · Blender e hero
### T4 · Registrar conceito e movimento em DESIGN.md · N4
- Serve: CA4, CA7, CA9 · Depende de: T0 · Arquivos: criar `DESIGN.md`
- Fazer: cartão do conceito da seção 5, tokens que já existem em `globals.css`, durações e curvas do verbo "encaixar", os três níveis de aparelho.
- Pronto quando: `impeccable context` lê o arquivo sem aviso.
- [x] feito
### T5 · Montar o kit e os estados no Blender · N3
- Serve: CA2 · Depende de: T0 · Arquivos: criar `3d/blocos.py`; `.gitignore` com `3d/out/`
- Fazer: bloco chanfrado (2 segmentos); os 4 estados como mapas de blocos no próprio script, com `marca` derivado dos retângulos de `public/brand/mark-dark.svg`; exportar `bloco.glb` e `blocos.json` no formato da seção 5; afirmar no script o mesmo N em todos os estados e os limites do CA2.
- Verificar: `blender -b --python 3d/blocos.py` → `OK blocos=<N> tris=<T>` e código 0.
- [x] feito
### T6 · Acertar material, luz e pôster · N4
- Serve: CA2, CA3 · Depende de: T5 · Arquivos: modificar `3d/blocos.py`
- Fazer: porcelana fosca `#f4f3ef` e esmalte `#d5fa00` com emissão leve, luz de estúdio, Cycles com OptiX; renderizar os 2 matcaps (256 px) e `hero-poster.webp` (1200 px, fundo transparente). Ajuste fino pelo Blender MCP (`execute_blender_code`, `look`), com os valores copiados para o script.
- Pronto quando: pôster sem ruído, com as proporções de `mark-dark.svg` e 4 cubos verdes; arquivos dentro do CA2.
- [x] feito
### CP2 · após T4–T6 · N0
- Verificar: mostrar o pôster ao usuário e seguir só com o "ok" dele.
### T7 · Compor o hero com o pôster · N4
- Serve: CA3, CA6 · Depende de: T6, bloqueada por P2 · Arquivos: `Hero.tsx:64-66,143`, `app/globals.css` (bloco `.hero-*`), criar `e2e/hero-3d.spec.ts`
- Fazer: pôster via `next/image` (lazy, prioridade baixa) no lugar de `.hero-glow::after`; no computador, coluna da direita; em 390 px, sem cobrir texto e com ≥ 96 px visíveis na primeira dobra. Teste e2e do CA3 primeiro.
- Verificar: `pnpm test:e2e -- hero-3d` → passa; capturas em 1440×900 e 390×844 aprovadas pelo usuário.
- [x] feito
### T8 · Classificar o aparelho em três níveis · N2
- Serve: CA5 · Depende de: T0 · Arquivos: `lib/device.ts:42-53`, `lib/device.test.ts`
- Fazer: `sceneTier()`: `static` com movimento reduzido, economia de dados, ≤ 4 núcleos ou memória informada < 4; senão `lite` com ponteiro `coarse` (exige ≥ 6 núcleos quando informado); senão `full` pela regra atual. Remover `isLowPowerDevice` e trocar a chamada em `HeroField.tsx:195`. Testes primeiro.
- Verificar: `pnpm test -- lib/device.test.ts` → passa; `grep -rn isLowPowerDevice app lib` → vazio.
- [x] feito
### T9 · Escrever o motor da cena de blocos · N3
- Serve: CA4, CA5 · Depende de: T5, T6, T8 · Arquivos: criar `lib/blocos.ts`, `lib/blocos.test.ts`, `app/components/cinematic/BlocosScene.tsx` · Padrão: `HeroFieldScene.tsx:692-999`, `FramePulse.ts`
- Fazer: `stateAt(p)` com os trechos de `hero-captions.ts` (teste primeiro); `InstancedMesh` com os estados em atributos por instância e mistura no vertex shader; entrada em 2,6 s; `full` segue `progressRef`, `lite` troca de estado a cada 4 s à vista; orçamento de 28 ms nos 60 primeiros quadros chama `onError` e grava na sessão.
- Verificar: `pnpm test -- lib/blocos.test.ts BlocosScene` → passa.
- [x] feito
### T10 · Ligar a cena ao hero · N2
- Serve: CA3, CA4, CA5 · Depende de: T7, T9 · Arquivos: `HeroField.tsx`, `app/globals.css` (`.hero-field`)
- Fazer: importar `BlocosScene` e `prepareBlocos` no lugar de `HeroFieldScene` e `prepareField`; `static` não carrega nada; `lite` sem `trackDive`; o canvas cobre o pôster com fade quando `data-ready`.
- Verificar: `pnpm test -- HeroField` e `pnpm test:e2e -- hero-3d scroll` → passam.
- [x] feito
### CP3 · após T7–T10 · N0
- Verificar: suíte e build; captura local com GPU (CA4); celular real (CA5); `shoot.mjs` (CA6).
### T11 · Remover a cena de partículas · N2
- Serve: CA11 · Depende de: CP3 · Arquivos: remover `HeroFieldScene.tsx` e `HeroFieldScene*.test.tsx`; ajustar o comentário de `hero-captions.ts`
- Verificar: `grep -rn HeroFieldScene app lib` → vazio; `pnpm typecheck && pnpm test` → passam.
- [x] feito
### Fase 2 · movimento 2D
### T12 · Animar a escrita · N2
- Serve: CA7 · Depende de: T4 · Arquivos: `app/globals.css` (junto de `:755-767`), `Hero.tsx:92`, `Section.tsx`
- Fazer: classe `.type-line` (largura em `ch` com `steps(var(--n))`, cursor de bloco verde que pisca 3 vezes) nos rótulos mono; marca-texto do trecho verde do H1 por `scaleX` num pseudo-elemento; tudo dentro de `prefers-reduced-motion: no-preference`.
- Verificar: `pnpm test -- Hero Section` → passa; inspeção com movimento reduzido: texto inteiro, sem cursor.
- [x] feito
### T13 · Animar entrada e saída dos componentes · N2
- Serve: CA8 · Depende de: T4 · Arquivos: `app/globals.css`, `app/components/showcase/lightbox.css`, `app/components/conversion.css`
- Fazer: `@starting-style` com `transition-behavior: allow-discrete` no menu do celular, no `<dialog>` e no `::backdrop`, no aviso de cookies e no botão do WhatsApp; `interpolate-size: allow-keywords` com `::details-content` nas perguntas frequentes; 180 ms para sair, 280 ms para entrar.
- Verificar: `pnpm test:e2e -- mobile-menu keyboard contact` → passam.
- [x] feito
### T14 · Trocar de página com a cortina de blocos · N3
- Serve: CA9 · Depende de: T4 · Arquivos: `app/globals.css:1038-1054`
- Fazer: substituir `page-out` e `page-in` por uma máscara em grade que fecha e abre na diagonal em `steps()`, 240 ms para sair e 460 ms para entrar; manter o cabeçalho parado e o bloco de movimento reduzido.
- Verificar: `pnpm test:e2e -- navigation routes` → passam; captura de 3 quadros da troca.
- [x] feito
### T15 · Levar a capa do projeto ao topo do case · N3
- Serve: CA9 · Depende de: T14 · Arquivos: `showcase/ProjectList.tsx:84`, `app/cases/_components/CaseCover.tsx:57` · Padrão: `app/template.tsx:29`
- Fazer: envolver as duas imagens em `<ViewTransition name={"case-" + slug}>`, conforme a documentação do Next 16 instalada.
- Verificar: `pnpm test -- ProjectList CaseCover` e `pnpm test:e2e -- navigation` → passam.
- [x] feito
### CP4 · após T12–T15 · N0
- Verificar: suíte, build e revisão do diff de CSS (só `transform`, `opacity`, `clip-path`, `mask`).
### Fase 3 · peças 3D
### T16 · Renderizar as quatro peças · N3
- Serve: CA10 · Depende de: T6 · Arquivos: criar `3d/pecas.py`
- Fazer: com o kit do T5, montar `site` (vitrine com balão de conversa), `sistema` (grade que vira barras), `revisao` (escudo) e `404` (marca com um bloco caindo); loop de 3 s a 24 quadros por segundo no Cycles, 640 px, fundo `--ink`; `ffmpeg` gera `.webm` e `.mp4` mais pôster `.webp`; o script afirma os limites do CA10.
- Verificar: `blender -b --python 3d/pecas.py` → `OK`.
- [x] feito
### T17 · Usar as peças nos cartões de Soluções · N2
- Serve: CA10 · Depende de: T16 · Arquivos: `WorkModalities.tsx:265,348` · Padrão: `showcase/useShowcaseMedia.ts`
- Fazer: trocar a ilustração de cada cartão por `<video muted loop playsInline preload="none" poster>` que toca só à vista; com movimento reduzido ou economia de dados fica o pôster.
- Verificar: `pnpm test -- WorkModalities` → passa.
- [x] feito
### T18 · Refazer o 404 com a marca · N2
- Serve: CA10 · Depende de: T16 · Arquivos: `app/not-found.tsx:34-46`, `app/not-found.test.tsx`
- Fazer: tokens do site (`--ink`, `--slash`, fonte display) no lugar das cores `slate`, e o loop `404` no lugar do ícone `Code2`; manter título e links.
- Verificar: `pnpm test -- not-found` → passa.
- [x] feito
### CP5 · fechamento · N0
- Verificar: cada CA da seção 3, com o resultado anotado aqui; impeccable critique e audit; jury; `Status: concluída`.
## 8. Registro
2026-11-09 — spec escrita sobre o `main` do Gitea (`4671837`); o checkout local está 54 commits atrás.
2026-11-09 — aprovada pelo usuário: P1 = o símbolo que o site já usa; P2 = a peça 3D ocupa a coluna da direita e as molduras de `HeroWork` saem do hero.
2026-11-09 — T0 feito: `origin` no Gitea, branch `feat/blocos-3d-motion`, base `4671837`, 150 suítes verdes antes de qualquer mudança.
2026-11-09 — CP1: `pnpm lint`, `pnpm typecheck`, `pnpm test` (151 suítes, 1654 testes) e `pnpm build` verdes; `build-icons.mjs --check` → `OK`. Falta só conferir o ícone no celular depois do deploy.
2026-11-09 — T2: o `sharp` é resolvido a partir do pacote `next`, porque não é dependência direta do site.
2026-11-09 — T8: `lib/device.paths.test.ts` também citava `isLowPowerDevice` e foi convertido.
2026-11-09 — interface de `blocos.json` mudou de 5 para 7 números por bloco (`x, y, z, sx, sy, sz, tom`): a marca não cabe numa grade única, então cada retângulo dela é ladrilhado com blocos de escala própria e o contorno fica exato.
2026-11-09 — o `next dev` reescreve uma linha do `AGENTS.md` (nível do título); não é mudança deste trabalho.
2026-11-09 — o projeto não tem `PRODUCT.md`; o impeccable pede que ele seja escrito com o usuário antes do T7 (composição do hero).
2026-11-09 — T5: N = 238 blocos, 25.704 triângulos no total; `bloco.glb` 2,7 KB, `blocos.json` 3 KB em gzip; mesma saída em duas execuções.
2026-11-09 — T6: materiais, luz de três pontos, pôster (38 KB) e matcaps (2 a 3 KB) saem do próprio `3d/blocos.py` em Cycles com OptiX, em 6 s. O ajuste foi feito por renders sem interface, não pelo Blender MCP: dá o mesmo resultado e fica reproduzível. Os matcaps se chamam `matcap-porcelana.webp` e `matcap-lima.webp`.
2026-11-09 — CA8 não fecha como escrito: o menu do celular, o aviso de cookies e o botão do WhatsApp são desmontados pelo React ao fechar (`Header.tsx`, `GoogleAnalytics.tsx`, `FloatingWhatsApp.tsx`), e CSS não anima a saída de elemento que some do DOM. Só o vídeo ampliado (`<dialog>`) e as perguntas frequentes (`<details>`) aceitam saída sem JavaScript. T13 fica à espera da decisão do usuário.
2026-11-09 — T12: rótulos das seções digitados (`.ds-type`) e bloco verde que recolhe sobre as palavras em verde do H1 (`.hero-mark`). O rótulo do hero ficou de fora: ele usa Space Grotesk de propósito (não baixar fonte antes do LCP) e não é mono. `Reveal.test.tsx` também afirmava `ds-reveal` no rótulo e foi atualizado. Conferido no navegador em 1440×900 com as animações pausadas em 700, 1150 e 4000 ms.
2026-11-09 — `public/3d` saiu da checagem do Biome (`biome.json`): `blocos.json` é gerado e compacto de propósito.
2026-11-09 — aprovado pelo usuário: o pôster (CP2); entrada em todos os componentes e saída só onde o elemento continua no DOM (CA8 passa a valer assim); os três ganchos recomendados, em spec própria (`specs/2026-11-09-ganchos-e-ofertas.md`).
2026-11-09 — `PRODUCT.md` escrito com as respostas do usuário; o site também serve a recrutadores e empresas de tecnologia.
2026-11-09 — T7: a peça fica na coluna da direita de 1024 px em diante (até 46% da largura) e, abaixo disso, no canto, na linha do rótulo (112 a 200 px). As molduras de navegador e celular saíram do hero e a prop `workUrl` foi removida da cadeia; o cartão do site da SEV continua abaixo de 1024 px. A malha de pontos estática (`.hero-glow::after`) saiu. Conferido em 1440×900 e 390×844; a aprovação visual do usuário fica para o CP3, com o site rodando em local.
2026-11-09 — T13: entrada por `@starting-style` no menu do celular, no aviso de cookies e no botão do WhatsApp; abrir e fechar animados nas perguntas frequentes. A saída do vídeo ampliado não aparece: `ShowcaseProvider.tsx:74-79` desmonta o `<dialog>` no `close`. A altura das perguntas frequentes é a única propriedade de leiaute animada.
2026-11-09 — entrada da cena (CA4): como o pôster já mostra a marca montada, a cena assume com os blocos a até 1,2 unidade do lugar e assenta em 1,4 s, em vez de montar de longe em 2,6 s.
2026-11-09 — Fase 3 (T16 a T18) adiada para depois da spec dos ganchos: os cartões de Soluções vão mudar de 3 para 4, e as peças dependem de quais cartões existem.
2026-11-09 — T9: `lib/blocos.ts` (`parseBlocos`, `stateAt`, `createFrameBudget`, `framePiece`) e `BlocosScene.tsx` (uma chamada de desenho, estados em atributos por instância, matcaps do Blender). Conferido com GPU real (ANGLE Vulkan, RTX 5060 Ti): os quatro estados batem com as prévias do Blender.
2026-11-09 — T10: `HeroField` trata os três níveis. No `full` o canvas cobre o palco e a peça descansa sobre o pôster (`placementRef`); no mergulho ela vai para a área útil entre o cabeçalho e as legendas (`stageRef`: 6% a 94% da largura, do cabeçalho + 24 px a 70% da altura). No `lite` o canvas ocupa só a caixa do pôster. `HeroField` passou para depois de `.hero-scrim`, para a peça não ficar sob o véu. `--hero-open` e a máscara do fundo ambiente saíram.
2026-11-09 — o primeiro trecho de `stateAt` é 0,16 a 0,30 (era 0,10 a 0,26): os blocos só se mexem com o título quase apagado. Em p = 0,20 os blocos mais à esquerda encostam no fim do subtítulo, que está com 16% de opacidade.
2026-11-09 — a caixa da marca na cena é 15,25 unidades (o enquadramento real do pôster), não 13. Canvas contra pôster em 1440×900: centro a 1,4%, tamanho a 3,1%; a porcelana do canvas sai 11% mais clara que a do pôster (vem do matcap, que é de uma esfera vista de frente).
2026-11-09 — T11: `HeroFieldScene.tsx` e seus testes removidos.
2026-11-09 — T14: cortina de blocos em 600 ms (`@property --page-wipe`, 8 colunas, `round(down, …, 12.5vw)`); a página que sai fica parada. `app/template.tsx` passou a usar `default="page"` em vez de `name="page"`: com nome comum, a página antiga pulava para o topo ao sair rolada. `jest.setup.ts` repassa os filhos de `ViewTransition`.
2026-11-09 — CP3 parcial: captura com GPU em 1440×900 nos progressos 0, 0,23, 0,35, 0,63 e 0,93 vista e aprovada pela sessão principal. Faltam celular real (CA5) e a medição de LCP com rede limitada (CA6).
2026-11-09 — T15: a capa viaja com o nome `case-<id>` (`cover-transition.ts`) na lista da home no celular, em `/cases` e, no computador, a partir do projeto em exibição na vitrine (`PinnedReel`, pelo pôster e não pelo vídeo). No primeiro quadro há um pequeno salto, porque o pôster mostra o topo do site e o vídeo estava no meio da rolagem.
2026-11-09 — CP3 e CP4: `biome check`, `pnpm typecheck`, `pnpm test` (150 suítes, 1612 testes), `pnpm build` e `pnpm test:e2e` (53 testes, com axe e os três de `hero-3d`) verdes sobre `pnpm start -p 3001`, encerrado ao fim. Para rodar a suíte e2e foi baixado o Chromium do Playwright (`playwright install chromium`).
2026-11-09 — CA6 medido em 390×844 com CPU 4× mais lenta e rede de 1,6 Mbps: LCP em 812 ms, no H1; CLS 0. O `shoot.mjs` do awwwards não roda sem o pacote `playwright`; a medição foi feita com um script descartável sobre `@playwright/test`.
2026-11-09 — pendente: celular real (CA5 e ícone do CA1), aprovação visual do usuário da composição do hero, Fase 3 e fechamento (CP5).
2026-11-09 — Fase 3 revista (T16 e T17): as peças em loop vão para onde não há prova real para mostrar. São três: `sistema` (no cartão, no lugar do esquema em CSS), `whatsapp` (cartão novo) e `404`. O cartão do site continua com a captura do site real e o da revisão com o trecho de relatório: trocar trabalho real por escultura iria contra o `PRODUCT.md`. A T17 foi feita junto com a T4 da spec dos ganchos.
2026-11-09 — T16: `3d/pecas.py` gera `peca-sistema`, `peca-whatsapp` e `peca-404` (webm de 18 a 51 KB, mp4 de 21 a 49 KB, pôster de 5 a 7 KB; 640×400, 3 s, sem áudio). Em `3d/blocos.py` a luz virou `luz_de_estudio()` e `main()` ficou sob `if __name__ == "__main__"`.
2026-11-09 — T17: `OfferVideo.tsx` toca o loop só à vista e fica no pôster com movimento reduzido ou economia de dados; usado nos cartões `sistema` e `whatsapp`.
2026-11-09 — T18: 404 com os tokens do site, os botões de `button-styles.ts` e o loop da marca; sem ícones.
2026-11-09 — o matcap da porcelana sai com 0,3 ponto de exposição a menos: o centro foi de 222 para 203, contra 201 na frente dos blocos do pôster.
2026-11-09 — Impeccable atualizado para 4.8.2 pelo gate com `--force`, depois da revisão dos achados (1 alto e 29 médios, todos lidos como falsos positivos). Cópia da 4.7.0 em `~/tmp/impeccable-4.7.0-backup`.
2026-11-09 — CP5, fechamento. Resultado por critério:
- CA1: passa nos testes e no `--check`; falta ver o ícone instalado num celular depois do deploy.
- CA2: passa (`OK blocos=238 tris=25704`, todos os arquivos dentro dos limites).
- CA3: passa (`e2e/hero-3d.spec.ts`, 3 testes).
- CA4: passa, com a entrada revista (assenta em 1,4 s); visto com GPU em 1440×900.
- CA5: passa nos testes e em celular emulado com GPU; falta celular real.
- CA6: passa. LCP no título em 340 ms (computador com GPU), 116 ms (sem GPU) e 868 ms (celular, CPU 4× mais lenta e rede de 1,6 Mbps); CLS 0.
- CA7: passa.
- CA8: passa como revisto (saída só nas perguntas frequentes).
- CA9: passa; medido à parte: a cortina roda de 150 a 850 ms em home → /cases, home → /servicos e /cases → case.
- CA10: passa, com três peças em vez de quatro.
- CA11: NÃO passa. Suíte toda verde (biome, tipos, 152 suítes e 1631 testes, build, 57 testes e2e), mas a crítica do Impeccable deu 29/40 com Consistência em 2 (o botão "Falar sobre meu projeto" leva a lugares diferentes, o item "IA" do menu é ambíguo, o verde do WhatsApp não tem token), a auditoria deu 15/20 e o crítico cego do jury deu 6,4 (Design 6, Usabilidade 7, Criatividade 6, Conteúdo 7). Relatórios em `~/tmp/avaliacao/impeccable.md` e `~/tmp/.jury/127.0.0.1-3001/scorecard.md`. Diagnóstico rápido das regras de design: 9 (falha só nos rótulos em caixa alta).
2026-11-09 — achado da crítica corrigido: em computador com GPU o LCP era a imagem do pôster aos 2,3 s, porque a troca de opacidade ao esconder o pôster o repintava. O pôster agora carrega junto com a página e some por `visibility`; o LCP voltou ao título.
2026-11-09 — 404 alinhado à grade do site (`ds-section-shell`) e com a peça menor no celular, para os botões não ficarem sob o botão do WhatsApp.
+100
View File
@@ -0,0 +1,100 @@
# Ganchos novos: revisão de IA atualizada, WhatsApp com IA e nota fiscal
**Autor:** Evandro · **Data:** 2026-11-09 · **Porte:** grande · **Status:** concluída
## 1. Objetivo
Dar ao site mais dois pontos de partida além de site, sistema e revisão, e atualizar a página `/ia` com os casos de 2026 e com o Brasil. Hoje qualquer um tira a operação da planilha com IA; o que o dono não consegue sozinho é responder pelo resultado. Base: `~/tmp/reports/Ofertas para empresas com IA.md`.
## 2. Escopo
- Dentro: oferta "Atendimento no WhatsApp com IA sob controle"; oferta com prazo "Nota fiscal pronta para a reforma tributária"; dois casos de 2026 e um bloco brasileiro em `/ia`; cartões da home de 3 para 4, mais uma faixa datada da nota fiscal.
- Fora (não fazer): frase de medo sem fonte ("perderam a empresa", "processo milionário"); preço ou prazo de entrega; depoimento ou caso inventado; página de serviço nova para máquinas e sensores (já existe) ou para aplicativo; blog.
## 3. Critérios de aceite
- CA1: `offerLabels` tem `whatsapp` e `fiscal`, e cada uma aparece na home, em `/servicos`, no tipo de projeto do formulário e na mensagem do WhatsApp → `pnpm test -- offer Contact WorkModalities servicos`.
- CA2: a home mostra 4 cartões (site, sistema, WhatsApp com IA, revisão) em grade 2×2 de 1024 px em diante e a faixa da nota fiscal acima deles; em 390 px nada estoura → inspeção em 1440×900 e 390×844 e `pnpm test:e2e`.
- CA3: a faixa e a página da nota fiscal mostram as datas com link para a publicação oficial, e a faixa some sozinha depois de 2027-03-31 → `pnpm test -- fiscal` (com a data do teste antes e depois).
- CA4: `/ia` conta Lovable (abril de 2026) e RedAccess (maio de 2026) no formato atual (o que foi relatado, o que não foi provado, o cuidado técnico, o link) e um bloco brasileiro com a multa do TST e o prazo de comunicação de incidente → `pnpm test -- app/ia`.
- CA5: nenhum texto novo afirma prejuízo, multa ou processo que a fonte ligada não sustente; toda afirmação com número tem link → revisão linha a linha contra o relatório, registrada na seção 8.
- CA6: `pnpm lint && pnpm typecheck && pnpm test && pnpm build && pnpm test:e2e` verdes.
## 4. Contexto verificado
- `lib/offer-labels.ts:7-20`: três ofertas (`site`, `sistema`, `revisao`), fonte única do nome e do assunto do WhatsApp.
- `lib/site-content.ts:939-990`: `serviceOffers`, com resumo, entregáveis, primeiro passo e prova de cada oferta.
- `app/components/WorkModalities.tsx:31,348-369`: `OFFER_EXTRAS` por oferta e grade de 3 colunas; a descrição diz "Três pontos de partida".
- `app/ia/page.tsx:124-170`: `INCIDENTS` com Lovable, Replit e Base44 de 2025; `:428-455`: cartão "E a LGPD?" com a Resolução 2/2022.
- O identificador da oferta é um tipo fechado usado em cerca de 25 arquivos (formulário, WhatsApp, dados estruturados, testes).
## 5. Desenho
Textos propostos (aprovar ou corrigir):
| Oferta | Nome | Resumo |
|---|---|---|
| `whatsapp` | Atendimento no WhatsApp com IA sob controle | Um atendente automático que responde só com o que está no seu catálogo e na sua tabela de preços, chama uma pessoa quando não sabe e guarda o registro de cada conversa. |
| `fiscal` | Nota fiscal pronta para a reforma tributária | Se o seu sistema ou a sua integração emite nota, ajusto os campos de IBS e CBS e testo a emissão antes de o prazo chegar. |
- Entregáveis do WhatsApp: respostas limitadas ao seu catálogo, preços e horários; passagem para uma pessoa na mesma conversa; registro do que foi informado ao cliente; uso da API oficial do WhatsApp.
- Entregáveis da nota fiscal: levantamento do que o seu sistema emite hoje; campos de IBS e CBS no leiaute novo; teste em homologação com nota real; lista do que depende do seu contador.
- Faixa da home: "Seu sistema emite nota? O Simples Nacional entra na reforma tributária em 01/01/2027. Veja o que muda." Leva à página da oferta.
- Sem case público nas duas ofertas novas: o cartão diz isso em vez de mostrar prova que não existe.
- Decisão: 4 cartões e uma faixa, não 5 cartões. Cinco cartões iguais achatam a escolha, e a nota fiscal tem data para acabar.
- Descartado: seção "por que você precisa de um desenvolvedor" na home. O argumento fica nos entregáveis de cada oferta e em `/ia`, com fonte.
## 6. Restrições, riscos, suposições
- Riscos: as datas fiscais do relatório vêm de portais contábeis; publicar só depois de ler o ato no Diário Oficial. A oferta de nota fiscal envelhece e precisa sair do ar na data.
- S1: você entrega integração fiscal (há um `apolo-nfe` nos seus projetos) e atendente de WhatsApp pela API oficial.
- S2: não há case público para citar nas duas ofertas novas.
- P1 (bloqueia T2): os textos da tabela acima estão aprovados?
- P2 (bloqueia T5): a nota fiscal ganha página própria em `/servicos`, ou a faixa leva direto ao WhatsApp? Resposta recomendada: página própria, porque é onde as datas e as fontes cabem.
## 7. Microtasks
### T1 · Conferir as datas fiscais na fonte oficial · N4
- Serve: CA3, CA5 · Depende de: nenhuma
- Fazer: ler o Ato Conjunto RFB/CGIBS 4/2026 no Diário Oficial ou no portal da Receita e anotar na seção 8 cada data com o link.
- Pronto quando: cada data da faixa e da página tem link oficial; data sem fonte oficial sai do texto.
- [x] feito
### T2 · Acrescentar as duas ofertas aos rótulos e ao conteúdo · N2
- Serve: CA1 · Depende de: bloqueada por P1 · Arquivos: `lib/offer-labels.ts:7-20`, `lib/site-content.ts:939-990`, testes de `lib/`
- Fazer: `whatsapp` e `fiscal` com os textos da seção 5, no formato das três atuais; testes primeiro.
- Verificar: `pnpm test -- lib/site-content lib/offer` → passa; `pnpm typecheck` aponta os lugares que faltam tratar.
- [x] feito
### T3 · Levar as ofertas ao formulário e ao WhatsApp · N2
- Serve: CA1 · Depende de: T2 · Arquivos: `app/components/contact-subject.ts`, `app/components/Contact.tsx`, testes de `Contact*`
- Fazer: as duas ofertas como tipo de projeto e como assunto da mensagem, no padrão das atuais.
- Verificar: `pnpm test -- Contact contact-subject` → passa.
- [x] feito
### T4 · Mostrar 4 cartões e a faixa na home · N3
- Serve: CA2, CA3 · Depende de: T2 · Arquivos: `app/components/WorkModalities.tsx:31,348-369`, `WorkModalities.paths.test.tsx`
- Fazer: grade 2×2 com site, sistema, WhatsApp com IA e revisão; faixa da nota fiscal acima, escondida depois de 2027-03-31; descrição da seção sem o número "três".
- Verificar: `pnpm test -- WorkModalities` → passa; capturas em 1440×900 e 390×844.
- [x] feito
### T5 · Criar as páginas das duas ofertas · N3
- Serve: CA1, CA3 · Depende de: T1, T2, bloqueada por P2 · Arquivos: `lib/site-content.ts` (`servicePages`), `app/servicos/[slug]/page.test.tsx`
- Fazer: uma página por oferta no formato das páginas de serviço, com entregáveis, primeiro passo e, na fiscal, as datas com link oficial.
- Verificar: `pnpm test -- servicos sitemap` → passa.
- [x] feito
### T6 · Atualizar a página `/ia` · N2
- Serve: CA4, CA5 · Depende de: nenhuma · Arquivos: `app/ia/page.tsx:124-170,428-455`, `app/ia/page.test.tsx`
- Fazer: dois itens em `INCIDENTS` (Lovable de abril de 2026, com a resposta da empresa; RedAccess de maio de 2026, com a contestação das plataformas); no cartão da LGPD, o prazo de 3 dias úteis (6 para pequeno porte) da Resolução CD/ANPD 15/2024 e a multa do TST de março de 2026, cada um com link.
- Verificar: `pnpm test -- app/ia` → passa.
- [x] feito
### CP1 · após T1–T6 · N0
- Verificar: CA5 linha a linha, suíte, build, e2e e as capturas da home.
## 8. Registro
2026-11-09 — rascunho escrito depois da aprovação dos três ganchos recomendados.
2026-11-09 — T1 adiantada (só leitura): Ato Conjunto RFB/CGIBS nº 4, de 30/07/2026, publicado no DOU de 31/07/2026 (edição 143, seção 1, página 25). Texto oficial: https://www.cgibs.gov.br/upload/arquivos/202607/31091735-20260730-16h30-ato-conjunto-rfb-cgibs-na-c2-ba-4-260731-090909.pdf . Nota da Receita: https://www.gov.br/receitafederal/pt-br/assuntos/noticias/2026/julho/receita-federal-e-comite-gestor-do-ibs-publicam-o-cronograma-de-implementacao-dos-documentos-fiscais-eletronicos-da-reforma-tributaria-do-consumo . Datas: 03/08/2026 (NF-e, NFC-e, CT-e, MDF-e e outros); 01/10/2026 (NFCom, DIR e NFS-e da maioria dos prestadores); 01/12/2026 (NFGas, NFAg, NF-e ABI e NFS-e de plataformas, locações e condomínios); 01/01/2027 (optantes do Simples Nacional, art. 1º, § 1º). Lido pelos trechos do PDF oficial devolvidos na busca; falta abrir o PDF inteiro antes de publicar.
2026-11-09 — aprovada pelo usuário ("prossiga com o que falta"): P1 = textos da seção 5; P2 = página própria para a nota fiscal; S1 e S2 aceitas.
2026-11-09 — T1: PDF oficial do Ato Conjunto lido por inteiro. As datas valem como anotado acima. O ato não fala de rejeição nem de tolerância para nota sem os campos: isso saiu de todos os textos.
2026-11-09 — correção de fato: o incidente da Lovable de 2026 durou de 3 de fevereiro a 20 de abril (relato da própria empresa), e expôs histórico de conversa e código-fonte de projetos públicos. O relatório dizia "48 dias" e "código e dados"; foi corrigido.
2026-11-09 — T6: `/ia` tem cinco episódios, o prazo de comunicação de incidente (Resolução CD/ANPD 15/2024, arts. 6º e 9º) e o cartão do TST. A fonte da RedAccess é o Security Boulevard, lido por inteiro; o Axios bloqueia leitura automática e entra como segundo link.
2026-11-09 — a faixa da nota fiscal na home cita só a data do Simples Nacional (01/01/2027), que é a que falta chegar.
2026-11-09 — T2 a T5: as duas ofertas estão nos rótulos, no conteúdo, no formulário, no assunto do WhatsApp, nos dados estruturados, em `/servicos`, no mapa do site e em duas páginas novas. A home tem a faixa (`FiscalNotice`, função pura de data) e 4 cartões em 2×2. As páginas novas não têm esquema no topo (nenhum dos existentes servia) e ganharam o bloco `official` (regras ou datas com link), mostrado por `ServiceOfficial.tsx`.
2026-11-09 — CA5, revisão linha a linha: o texto dos subitens da LC 116 estava errado (1.09 é conteúdo de áudio e vídeo pela internet, não hospedagem; hospedagem está no 1.03). Corrigido em `lib/site-content.ts` depois de conferir a lista da LC 116/2003. As demais frases batem com o Ato Conjunto, a política do WhatsApp e as fontes de `/ia`. Um teste falha se a página fiscal ganhar "rejeição", "multa" ou "tolerância".
2026-11-09 — o rodapé passou a listar 8 serviços.
2026-11-09 — CP1: `biome check`, `pnpm typecheck`, `pnpm test` (152 suítes, 1631 testes) e `pnpm build` verdes. Na suíte e2e, 55 de 57 passaram de primeira; os dois de teclado falharam porque a home ganhou três paradas de Tab (o link da faixa, o botão e o link do cartão novo). O limite do caminho linear subiu de 37 para 40, com o inventário atualizado em `e2e/keyboard.spec.ts`, e os três testes de teclado passaram.
2026-11-09 — fechamento: CA1 passa (testes de oferta, formulário e serviços); CA2 passa (home vista em 1440×900 e 390×844, sem estouro); CA3 passa (`fiscal-notice.test.tsx`, datas antes e depois de 2027-03-31); CA4 passa (`app/ia`, 13 testes); CA5 passa depois da correção dos subitens; CA6 passa (biome, tipos, 152 suítes e 1631 testes, build, 57 testes e2e).
+58
View File
@@ -0,0 +1,58 @@
# Hero mais abrangente
**Autor:** Evandro · **Data:** 2026-11-10 · **Porte:** médio · **Status:** concluída
## 1. Objetivo
O título do hero fala de dois casos (pedido pelo WhatsApp e planilha). Quem não tem esses dois problemas entende que o trabalho se resume a isso. O hero passa a dizer o trabalho inteiro, e a peça de blocos passa a mostrar a amplitude sem depender da rolagem.
## 2. Escopo
Dentro: texto do hero, faixa de clientes, linha do primeiro passo, alinhamento e tamanho da peça, duração da entrada, ciclo da peça em repouso com legenda.
Fora: seções abaixo do hero, cartões de oferta (continuam específicos), o número "5 anos de experiência" (segue vindo do resumo do portfólio), região no rótulo.
## 3. Critérios de aceite
- CA1 (texto): o h1 é "Sites e sistemas sob medida para o jeito que a sua empresa trabalha.", com "sob medida" em lima; o apoio é "Do site institucional ao sistema que a sua equipe usa todo dia. Você fala com quem desenvolve."; nenhum outro lugar do site repete o título antigo → `grep` e `pnpm test`.
- CA2 (primeiro passo): abaixo dos botões há a linha "Começa com uma conversa para entender o seu caso. A proposta vem por escrito."; o hero continua cabendo no palco fixo em 1440×900, 1280×720, 1024×768, 390×844 e 360×640 → inspeção.
- CA3 (clientes): em tela de 640 px ou mais, cada cliente mostra o ramo abaixo do nome; os nomes continuam levando ao case → `pnpm test -- ClientStrip`.
- CA4 (composição): de 1024 px em diante o texto do hero começa na mesma coluna do conteúdo das outras seções; a peça fica 20% maior e mais perto do texto, sem cobri-lo → medida de `getBoundingClientRect` em 1440 e 1280.
- CA5 (entrada): texto, botões e faixa assentam em até 1,8 s → medida do fim da última animação do hero.
- CA6 (peça em repouso): no nível `full`, parada no topo, a peça passa sozinha por marca, tela, integrações e dados; qualquer rolagem devolve o comando à rolagem; abaixo da peça uma legenda digitada diz o que ela está montando; com movimento reduzido ou sem WebGL não há ciclo nem legenda → `pnpm test -- BlocosScene lib/blocos` e inspeção com GPU.
- CA7: Biome, typecheck, `pnpm test`, build e e2e verdes.
## 4. Contexto verificado
- Título e destaque: `app/components/Hero.tsx:41-52` (`ACCENT`, `TITLE`); apoio em `Hero.tsx:111-114`.
- Faixa de clientes: `app/components/ClientStrip.tsx` (nomes com link para `/cases/<id>`, anos na mesma linha); lista em `app/page.tsx` (`clientNames`).
- Hero em `max-w-[max(1400px,var(--shell))]` com `lg:px-sp-12` (`Hero.tsx:86`): em 1440 px o texto começa em x=68; as seções usam `.ds-section-shell` (`globals.css:257`), que começa em x=100.
- Peça: `.hero-piece` (`globals.css:796-815`), `width: min(46%, 680px)` de 1024 px em diante; a cena segue essa caixa (`HeroField.tsx`).
- Entrada: `.hero-word` 1,1 s com atraso de 0,12 s + 45 ms por palavra; `.hero-in` 1 s com `--d` de até 0,75 s (`globals.css:873-914`). O júri mediu 2,6 s até assentar.
- Estados da peça por rolagem: `stateAt` em `lib/blocos.ts`; no nível `lite` a troca já é automática (`BlocosScene.tsx:369-373`).
- Ramos: SEV Moda Íntima é "loja virtual" e Lavação 047 é "lavação automotiva" (`public/portfolio-data.json`); a AS Serviços "reúne engenharia física e software" (`lib/site-content.ts:954`); Julia Beauty é "estúdio de extensão de cílios" (`portfolio-data.json`).
- Legendas do mergulho já existentes: `app/components/hero-captions.ts` ("A tela", "As integrações", "Os dados").
## 5. Desenho
- Ramos, em um só lugar (ao lado de `clientNames`): sev-modaintima "loja virtual"; lavacao-047 "lavação automotiva"; sc-plus "aquicultura"; as-servicos "engenharia e software"; julia-beauty "estúdio de cílios".
- Faixa de clientes de 640 px em diante: cada cliente é um par empilhado, nome (15 px, link) sobre ramo (13 px, apagado). Abaixo disso, só os nomes, como hoje.
- Legenda da peça: marca sem legenda; tela "Sites e sistemas"; integrações "Integrações"; dados "Painéis e dados". Quadrado lima antes do texto, como no rótulo do hero; decorativa (`aria-hidden`).
- Ciclo em repouso (`full`): começa 3 s depois da entrada, com a página no topo; cada estado fica uns 3 s; a troca usa a transição que já existe; a rolagem assume na hora e o ciclo só volta depois de 3 s parado no topo.
## 6. Restrições, riscos, suposições
- Só `transform`, `opacity`, `clip-path`; sem biblioteca nova; o título continua sendo o LCP.
- Risco: a linha nova e o ramo aumentam a altura do hero; o título novo é mais curto, mas o palco fixo do celular é apertado. Se não couber em 360×640, a linha do primeiro passo fica só de 640 px em diante.
- Suposição: os ramos acima; o usuário viu a lista e mandou seguir, com a AS Serviços corrigida pelo texto do próprio site.
## 7. Microtasks
### T1 — texto, faixa, primeiro passo, alinhamento e entrada
- Serve: CA1 a CA5 · Arquivos: `Hero.tsx`, `ClientStrip.tsx`, `app/page.tsx`, parte do hero em `globals.css`, testes e e2e que citam o título
- [x] feito
### T2 — ciclo em repouso e legenda da peça
- Serve: CA6 · Arquivos: `BlocosScene.tsx`, `HeroField.tsx`, `lib/blocos.ts`, testes
- [x] feito
### CP1
- Verificar: CA7 e inspeção com GPU em 1440×900 e 390×844.
## 8. Registro
2026-11-10 — T1 entregue: título, apoio, linha do primeiro passo, faixa com ramo (`CLIENT_SECTORS` em `lib/proof.ts`), hero na coluna das seções (x=100 em 1440, 64 em 1280, 61 em 1024), peça em `min(55.2cqw, 816px)`, entrada assentando em 1,37 s no computador e 1,5 s no celular; título em 3 linhas no computador e 4 no celular. Abaixo de 1024 px o alinhamento não muda.
2026-11-10 — desvio: as descrições do `layout.tsx`, do `manifest.ts` e da imagem de compartilhamento repetiam o posicionamento antigo e foram trocadas pelo novo. O teste `home-positioning.test.ts` proibia "sob medida" (posicionamento de antes do WhatsApp e da planilha); passou a exigir o título novo, porque a escolha do usuário foi a opção com "sob medida".
2026-11-10 — T2 entregue: `cicloAt`, `seguir`, `caixaDosEstados` e `legendaDe` em `lib/blocos.ts`; ciclo de 3 s por estado no `full`, parado no topo; legenda digitada só de 1024 px em diante (em 390 e 360 px encostava no título). No ciclo, tela, integrações e dados ocupam 78% da caixa da peça, porque na caixa inteira encostavam no título. Tempo de quadro medido com GPU: média de 0,17 ms. Pendência anotada: em tela de computador com menos de 700 px de altura não há mergulho e o ciclo roda com a página rolada enquanto o hero está à vista.
2026-11-10 — ajustes depois da revisão visual: "sob medida" não quebra mais entre as duas palavras; para o título seguir em 3 linhas no computador, o tamanho passou a acompanhar a largura da coluna (54 px em 1440, 39 px em 1024). Em 1440 os dois botões e o link do WhatsApp ficam na mesma linha, a 33 px da peça; em 1280 o link continua na linha de baixo, porque ficaria a 3 px do desenho.
2026-11-10 — a legenda entrava no começo da troca, por cima dos blocos da marca; agora só entra com a peça assentada e sai quando a troca seguinte começa. Medido com GPU em 1440×900 e 1280×720: nenhum pixel da cena atrás da legenda nos três estados.
2026-11-10 — LCP depois da mudança, no build de produção: o h1, em 228 ms no computador com GPU e 88 ms em 390 px sem limite de rede; CLS 0.
2026-11-10 — CP1: Biome e typecheck sem erro, 154 suítes e 1681 testes, build sem erro, e2e 60 de 60 sobre `pnpm start -p 3001` (servidor encerrado depois). Não houve novo júri nem teste em celular real.
+106
View File
@@ -0,0 +1,106 @@
# Melhorias de design depois da avaliação
**Autor:** Evandro · **Data:** 2026-11-10 · **Porte:** grande · **Status:** concluída
## 1. Objetivo
Aplicar as melhorias que a crítica do Impeccable (29/40, Consistência 2) e o crítico cego do jury (6,4) apontaram, para o conceito de blocos valer no site inteiro e os botões responderem como o resto. Aprovado pelo usuário em 2026-11-10 ("incluir as melhorias propostas").
Leitura (frontend-orchestrator): refino · Persuade · web · `3d`, `premium` → impeccable, design-rules, threejs-shaders e threejs-animation na cena; fecha com audit, diagnóstico rápido e jury.
## 2. Escopo
- Dentro: hover dos alvos principais; blocos nos topos das páginas internas; um destino só para "Falar sobre meu projeto"; botão do WhatsApp e aviso de cookies no celular; rótulo único para a página `/ia`; grades com sobra; menos rótulos em caixa alta; cena com vida em repouso e reação ao cursor; logo em blocos no cabeçalho; `rel="noopener"` nos links externos.
- Fora (não fazer): números de resultado nos cases (dependem de dados do usuário, P1); mudar ofertas ou textos de venda; biblioteca de animação; novo conceito visual.
## 3. Critérios de aceite
- CA1 (hover): todo botão verde, botão de contorno, link de texto, item da vitrine e botão de tocar vídeo muda de forma visível sob o ponteiro e no foco, com um tratamento por papel; com movimento reduzido a troca é instantânea → `noHoverChange` 0 e nenhum alvo primário em `colorOnly` no `shoot.mjs` do jury.
- CA2 (blocos nas páginas internas): `/cases`, `/curriculo`, `/privacidade` e as duas páginas de serviço sem capa mostram uma peça de blocos à direita do topo de 1024 px em diante, decorativa, sem estouro → inspeção em 1440×900 e `pnpm test:e2e -- routes`.
- CA3 (um destino): todo controle chamado "Falar sobre meu projeto" leva ao formulário; todo controle que abre o WhatsApp se chama "Chamar no WhatsApp" → `grep` dos rótulos e `pnpm test:e2e -- contact keyboard`.
- CA4 (celular): com o aviso de cookies aberto o botão flutuante não aparece; o aviso tem até 72 px de altura em 390 px; o link da política tem alvo de 24 px ou mais → `pnpm test -- GoogleAnalytics FloatingWhatsApp` e inspeção em 390×844.
- CA5 (rótulo): menu, trilha, rodapé e rótulo da página `/ia` dizem "Revisão de app com IA" → `pnpm test -- Header Footer app/ia`.
- CA6 (grades): nenhuma grade de cartões termina com célula vazia em 1440 px em `/cases` e `/servicos`; os cartões de `/ia` e do currículo não esticam com buraco interno → inspeção.
- CA7 (rótulos): `.ds-eyebrow` sem caixa alta; o rótulo do hero cabe em até 2 linhas em 390 px; as páginas internas com trilha não repetem rótulo sobre o título → inspeção e `pnpm test`.
- CA8 (cena viva): em repouso cada estado tem um movimento próprio e, no nível `full`, os blocos reagem ao cursor; o desenho continua sob demanda e para sem atividade → `pnpm test -- BlocosScene lib/blocos` e captura com GPU.
- CA9 (logo): o símbolo do cabeçalho responde ao ponteiro e ao foco com os quadrados verdes encaixando; em repouso o logo é igual ao atual (diferença média de pixel abaixo de 2 em 255) → medição do agente e inspeção.
- CA10: `pnpm lint && pnpm typecheck && pnpm test && pnpm build && pnpm test:e2e` verdes; novo jury com crítico novo, nota por categoria igual ou maior que a anterior.
## 4. Contexto verificado
- `app/components/button-styles.ts`: `primaryButton` só clareia no hover (`hover:bg-[#d9ff40]`); contornos só mudam a borda; links de texto só mudam a cor.
- `app/components/Hero.tsx:110-116`: o botão verde do hero abre o WhatsApp; `Header.tsx:86-91`: o do cabeçalho leva ao formulário.
- `lib/site-navigation.ts:10`: item `{ href: "/ia", label: "IA" }`.
- `app/globals.css` (`.ds-eyebrow, .ds-kicker`): 12 px, `text-transform: uppercase`, `letter-spacing: 0.08em`; `Section.tsx:47-58` põe um sobre cada título.
- `app/components/FloatingWhatsApp.tsx:61-66` e `GoogleAnalytics.tsx:224-236`: botão flutuante e aviso de cookies, ambos fixos na base no celular.
- `app/components/cinematic/BlocosScene.tsx` e `lib/blocos.ts`: cena de blocos, parada em repouso fora do balanço da marca.
- `app/components/Logo.tsx`: o logo é um `.webp` único (`/brand/wordmark-<tema>.webp`), sem partes animáveis; o símbolo existe em SVG (`public/brand/mark-dark.svg`, `mark-light.svg`).
- `3d/blocos.py` e `3d/pecas.py`: geram o pôster da marca e três loops; os estados `tela`, `integracoes` e `dados` só têm prévia em `3d/out/`.
## 5. Desenho
- Hover, um tratamento por papel, no verbo "encaixar" (entra pela esquerda, 280 ms, `--ease-out`, só `transform` e `opacity`):
- botão verde: um bloco cor de papel cobre o botão da esquerda para a direita; o texto continua escuro;
- botão de contorno: o mesmo bloco, translúcido no fundo escuro e escuro no fundo claro (o texto inverte);
- link de texto e item do menu: uma barra de 2 px cresce da esquerda sob o texto; o sublinhado fixo dos links de texto continua;
- item da vitrine: barra verde entra pela esquerda; tocar vídeo: o círculo cresce e fica verde.
- Um destino: "Falar sobre meu projeto" vai ao formulário em todo lugar. No hero entra um link discreto "Chamar no WhatsApp". Nas páginas de serviço, o botão que abre o WhatsApp com o assunto do serviço passa a se chamar "Chamar no WhatsApp".
- Peças das páginas internas: `/cases` usa o estado `tela`, `/curriculo` o `integracoes`, `/privacidade` o `dados`; atendimento usa o loop `peca-whatsapp` e nota fiscal um loop novo, `peca-fiscal`. Só de 1024 px em diante.
- Rótulos: sem caixa alta, 13 px, `letter-spacing` 0,02em; o efeito digitado continua. No celular o rótulo do hero mostra só "Engenharia de software".
- Cena: `marca` com os quatro quadrados verdes pulando em sequência; `tela` com o botão verde pulsando; `integracoes` com um pulso verde correndo do centro para os nós; `dados` com as barras respirando. Cursor: os blocos perto dele avançam para a frente.
- Logo: símbolo em SVG ao lado do texto recortado do `.webp` atual; se a montagem não bater com o original dentro da tolerância, fica o `.webp` com um tratamento de hover no logo inteiro.
- Descartado: esconder o botão flutuante do WhatsApp de vez no celular (é o atalho de contato mais usado).
Arquivos por frente (um dono cada): botões e celular em `button-styles.ts`, `globals.css`, `Hero.tsx`, `Header.tsx`, `FloatingWhatsApp.tsx`, `GoogleAnalytics.tsx`, vitrine e `CasePlayer.tsx`; páginas internas em `app/cases`, `app/servicos`, `app/curriculo`, `app/privacidade`, `app/ia`, `lib/site-navigation.ts`, `lib/site-content.ts`, `Footer.tsx`; Blender em `3d/` e `public/3d/`; cena em `BlocosScene.tsx` e `lib/blocos.ts`; logo em `Logo.tsx` e `public/brand/`.
## 6. Restrições, riscos, suposições
- Restrições: as mesmas das specs anteriores (Next 16, sem servidor extra além do `next dev` já aberto, sem commit).
- Riscos: o logo é a peça mais sensível da marca (por isso a tolerância e o plano B); mudar o destino do botão do hero mexe em medição de cliques e em testes de ponta a ponta.
- S1: as recomendações dos itens 3 e 5 da proposta estão aceitas como escritas.
- P1 (fora desta spec): números reais de resultado por cliente, para a página de cada case.
## 7. Microtasks
### T1 · Dar hover, destino único e celular limpo aos botões · N3
- Serve: CA1, CA3, CA4, CA7 · Depende de: nenhuma · Arquivos: os da frente "botões e celular"
- Pronto quando: os cinco papéis têm o tratamento da seção 5; o hero leva ao formulário e tem "Chamar no WhatsApp"; aviso de cookies e botão flutuante cumprem o CA4; rótulos sem caixa alta.
- Verificar: `pnpm test -- Hero Header FloatingWhatsApp GoogleAnalytics Section` e `e2e/contact.spec.ts`, `e2e/keyboard.spec.ts` contra a porta 3000.
- [x] feito
### T2 · Levar blocos, grades e rótulo único às páginas internas · N3 [P]
- Serve: CA2, CA3, CA5, CA6, CA7 · Depende de: nomes de arquivo do T3 · Arquivos: os da frente "páginas internas"
- Verificar: `pnpm test -- cases servicos curriculo privacidade app/ia Footer site-content`.
- [x] feito
### T3 · Renderizar os três estados e a peça da nota fiscal · N3 [P]
- Serve: CA2 · Arquivos: `3d/blocos.py`, `3d/pecas.py`, `public/3d/estado-*.webp`, `public/3d/peca-fiscal.*`
- Verificar: `blender -b --python 3d/blocos.py` → `OK`; `blender -b --python 3d/pecas.py` → `OK pecas=4`.
- [x] feito
### T4 · Dar vida à cena em repouso e reação ao cursor · N3 [P]
- Serve: CA8 · Arquivos: `BlocosScene.tsx`, `lib/blocos.ts` e testes
- Verificar: `pnpm test -- BlocosScene lib/blocos`; captura com GPU dos quatro estados em dois instantes.
- [x] feito
### T5 · Montar o logo do cabeçalho com o símbolo em blocos · N3 [P]
- Serve: CA9 · Arquivos: `Logo.tsx`, teste, arquivos novos em `public/brand/`
- Verificar: `pnpm test -- Logo Header Footer`; diferença média de pixel contra o logo atual abaixo de 2 em 255 em 180 e 240 px.
- [x] feito
### CP1 · após T1–T5 · N0
- Verificar: CA10 (suíte, build, e2e sobre `pnpm start -p 3001`, jury com crítico novo) e inspeção com GPU em 1440×900 e 390×844.
## 8. Registro
2026-11-10 — spec escrita a partir das nove melhorias propostas; o item 8 (resultado nos cases) ficou fora por depender de dados do usuário.
2026-11-10 — T3: `estado-tela.webp` (77 KB), `estado-integracoes.webp` (49 KB) e `estado-dados.webp` (70 KB), 1200×900 com alfa; `peca-fiscal` (webm 46 KB, mp4 54 KB, pôster 8 KB): nota de 11×16 blocos com as linhas como vãos e um selo verde de 7×7 com o "V" vazado. O pôster da marca não mudou de enquadramento. `blocos.json` com o mesmo md5.
2026-11-10 — T2: `PagePiece.tsx` novo, só de 1024 px em diante. `/cases` com o quinto cartão na linha toda; `/servicos` em 3 + 2; `/ia` e currículo com cartões alinhados pelo topo. Rótulo único `IA_PAGE_LABEL` ("Revisão de app com IA") no menu, na trilha, no rodapé e nos dados estruturados. Nenhuma página interna tinha "Falar sobre meu projeto" indo ao formulário além do currículo: os botões que abrem o WhatsApp viraram "Chamar no WhatsApp". No topo do case ficou só "Case de cliente" ou "Projeto de laboratório".
2026-11-10 — T5: logo montado de SVG (símbolo) mais `wordmark-text-<tema>.webp` (o original com o símbolo apagado, em 1600×330). Diferença média contra o logo anterior: 0,82 e 0,89 (escuro, 180 e 240 px), 0,34 e 0,39 (claro); caixa do link igual. As medidas do símbolo vieram do `.webp`, porque as do `mark-*.svg` não batem com ele.
2026-11-10 — T1: classes `cv-fill`, `cv-outline-dark`, `cv-outline-light` (bloco que entra pela esquerda) e `cv-bar` (barra de 2 px) em `globals.css`, só sob `@media (hover: hover)` e no foco. O botão do hero leva a `/#contact-form`; entrou o link "Chamar no WhatsApp", que é o que o botão flutuante observa. Fora da home, o botão do cabeçalho continua abrindo o WhatsApp, agora com o nome "Chamar no WhatsApp". Aviso de cookies com 67,7 px em 390 px e texto de 13 px abaixo de 640 px; o botão flutuante some com o aviso aberto. O espaço no fim da página já existia no rodapé. O limite de Tabs do caminho linear foi de 40 para 41 (o link novo do hero).
2026-11-10 — o contato sem JavaScript (`Contact.tsx`, `<noscript>`) também passou a dizer "Chamar no WhatsApp".
2026-11-10 — T4: `papeis()` em `lib/blocos.ts` tira do `blocos.json` os quatro quadrados da marca, o botão da tela, as seis linhas das integrações e as cinco barras dos dados. A vida em repouso e o cursor ficam no vertex shader; o peso vai a zero na troca de estado e na entrada; o relógio só anda quando há quadro. As barras respiram 15% (os 6% da proposta quase não se viam). O cursor empurra os blocos no eixo da própria peça, não na linha da câmera. Tempo de quadro medido com GPU: até 0,39 ms, um quadro a cada 33 ms.
2026-11-10 — segunda passada do Impeccable (`~/tmp/avaliacao/impeccable-2.md`): crítica 30/40, com Consistência de 2 para 3 e nenhuma heurística abaixo de 3; auditoria 15/20, sem mudança. O LCP com GPU ficou no título, em 356 ms.
2026-11-10 — problemas que as mudanças criaram, apontados na segunda passada e corrigidos: (1) o bloco cor de papel sumia nos botões verdes sobre cartão branco: em seção clara o bloco é escuro e o texto inverte; (2) sem caixa alta, o rótulo da vitrine mostrava "loja virtual · Em Produção": `projectKicker()` em `lib/project-showcase.ts` escreve "Loja virtual · em produção"; (3) rodapé, "Em movimento" e "Role para mergulhar" ainda estavam em caixa alta; (4) a imagem de reserva do 404 era pré-carregada em todas as páginas: ganhou `loading="lazy"`.
2026-11-10 — também da segunda passada: token `--whatsapp` (e `--whatsapp-hover`) no lugar das 9 cores soltas, e o botão de envio do formulário com o mesmo bloco dos outros.
2026-11-10 — o `next dev` parou de servir o CSS novo depois dos builds de produção feitos ao lado dele; foi reiniciado (pid novo) e voltou a servir.
2026-11-10 — ficam abertos, por serem decisão de produto ou anteriores a este trabalho: o botão do cabeçalho muda de nome e de destino entre a home e as outras páginas; o hero começa em x=68 e o resto em x=100; rótulos que repetem o título em três seções; o botão flutuante ainda cobre fim de linha no meio da rolagem; "Quatro pontos de partida" na home e "Cinco jeitos de começar" em `/servicos`.
2026-11-10 — suíte e2e sobre o build com as correções: 60 de 60.
2026-11-10 — pedido do usuário durante a execução: nos cartões da home, os dois balões soltos não se leem como conversa de WhatsApp, e a mídia da revisão (o trecho do relatório pequeno numa caixa escura) não sugere revisão. Mudanças: `peca-whatsapp` vira um celular de blocos com a conversa dentro, com um selo "WhatsApp" em HTML por cima; a revisão ganha a peça `peca-revisao` (uma lupa atravessa uma janela e assenta os blocos fora do lugar), com o trecho do relatório maior ao lado. O cartão da revisão deixa de ser exceção: três dos quatro cartões usam peça em loop.
2026-11-10 — peças entregues: `peca-whatsapp` refeita (celular de 9x16 blocos, três balões alternando os lados, pontos de "digitando") e `peca-revisao` nova (janela de 13 blocos de largura, lupa lima, três blocos que assentam, selo de conferido); `pecas.py` termina em `OK pecas=5`. No cartão: selo "WhatsApp" em HTML no canto de cima à esquerda; trecho do relatório ao lado da janela a partir de 29rem de quadro, embaixo do vídeo no celular e oculto entre 1024 e 1279 px (cartões lado a lado com quadro estreito). O vídeo dos cartões passou de `object-cover` a `object-contain`, porque no quadro 21:9 o celular e a janela eram cortados; o fundo do vídeo é o mesmo do quadro.
2026-11-10 — medido em dez larguras de 360 a 1920 px: o trecho nunca cobre a janela (folga mínima de 17 px, em 1280), o selo nunca cobre o celular (folga mínima de 7 px, em 360), os títulos dos cartões 3 e 4 ficam alinhados em duas colunas, sem rolagem horizontal.
2026-11-10 — verificação depois dessas mudanças: Biome sem erro, typecheck sem erro, 153 suítes e 1654 testes, build sem erro.
2026-11-10 — CP1: Biome, typecheck, 153 suítes e 1654 testes, build; e2e 60 de 60 sobre `pnpm start -p 3001` (servidor encerrado depois). Jury, rodada 2, com crítico novo (`~/tmp/.jury/127.0.0.1-3001/scorecard.md`): geral 7,0 contra 6,4; Design 6 → 7, Usabilidade 7 → 7, Criatividade 6 → 7, Conteúdo 7 → 7; nenhuma categoria caiu (CA10). Audit: 4 FAIL e 17 WARN contra 4 e 32; os 4 FAIL são os mesmos (texto de h2 que a ferramenta conta duas vezes e um link só de mouse sem nome).
2026-11-10 — depois da captura do jury: os botões do aviso de cookies ganharam o bloco no hover (eram só troca de cor em todas as páginas) e o link de e-mail de `/privacidade` ganhou alvo de 24 px. Conferido com os testes unitários e com `keyboard`, `contact` e `navigation` do e2e contra o `next dev` (28 de 28); não houve novo build de produção nem novo jury depois disso.
2026-11-10 — ficam abertos, da lista do crítico: o espaço depois de f e t no texto do celular (visto só na captura headless, não reproduzido em aparelho), o triângulo padrão dos `<details>` nas páginas de case e de serviço, espaço morto em cartões de `/servicos`, as requisições abortadas do vídeo da vitrine, e os dois itens que dependem do dono: resultado medido nos cases e a conta dos "5 anos de experiência" contra o currículo que começa em junho de 2022.