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.
30 KiB
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 (
anyemaskable, 192 e 512) eid: "/"; no maskable a marca cabe na zona segura (caixa ≤ 57% do lado, centrada ±2%) →pnpm test -- app/manifest.test.tsenode scripts/build-icons.mjs --check. - CA2 (ativos):
blender -b --python 3d/blocos.pyrecria do zeropublic/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 comOKe 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.glbnem ao pacote do Three.js →pnpm test:e2e -- hero-3d. - CA4 (computador): em aparelho
fullos blocos montam a marca em até 2,6 s e a rolagem leva marca → tela → integrações → dados dentro dos trechos dehero-captions.ts→pnpm test -- lib/blocos.test.tse captura local com GPU nos progressos 0, 0,35, 0,62 e 0,85. - CA5 (celular): em aparelho
litea 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 BlocosScenee 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.mjsdo awwwards (mobile.vitals) sobrepnpm 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,opacityeclip-pathanimam →pnpm test -- Heroe 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 keyboardverdes 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 routesverdes 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/3depnpm test:e2e. - CA11 (fechamento):
pnpm lint && pnpm typecheck && pnpm test && pnpm build && pnpm test:e2everdes; 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óthree0.182 elenis; 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,201elib/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) eHeroWork(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), sempurposenemid. 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:29eapp/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, addonblender_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 em3d/out/(ignorado no git). Descartado:.blendmodelado à mão e versionado, binário que ninguém revisa nem reconstrói. - Tempo real com uma
InstancedMeshe 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). GLTFLoaderdo própriothree, 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-stylecomallow-discretepara saída. - Peças secundárias pré-renderizadas no Cycles (vídeo curto com pôster), nunca WebGL por cartão.
Interfaces:
// 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 namain; 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(branchchore/security-updates-2026-11-09) nasceu do mesmo commit velho e vai conflitar com omainreal. - 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
HeroWorksaem 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.mdeCLAUDE.mdnã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→4671837ou mais novo;pnpm testverde. - 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.pnge/brand/pwa-512.png(purpose: "any"),/brand/pwa-maskable-192.pnge/brand/pwa-maskable-512.png(purpose: "maskable"), todosimage/png, e que cada arquivo existe empublic/. - Verificar:
pnpm test -- app/manifest.test.ts→ falha (ainda não implementado). - feito
T2 · Gerar os ícones a partir do símbolo · N2
- Serve: CA1 · Depende de: T1, bloqueada por P1 · Arquivos: criar
scripts/build-icons.mjse os 4 PNG empublic/brand/ - Fazer: com
sharp, comporpublic/brand/mark-dark.svgsobre fundorgb(8,10,9).any: marca com 77% do lado.maskable: marca com 56% do lado, centrada.--checkrelê 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. - feito
T3 · Apontar o manifesto para os ícones novos · N2
- Serve: CA1 · Depende de: T2 · Arquivos: modificar
app/manifest.ts:17-28; removerpublic/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. - 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 contextlê o arquivo sem aviso. - feito
T5 · Montar o kit e os estados no Blender · N3
- Serve: CA2 · Depende de: T0 · Arquivos: criar
3d/blocos.py;.gitignorecom3d/out/ - Fazer: bloco chanfrado (2 segmentos); os 4 estados como mapas de blocos no próprio script, com
marcaderivado dos retângulos depublic/brand/mark-dark.svg; exportarbloco.glbeblocos.jsonno 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. - feito
T6 · Acertar material, luz e pôster · N4
- Serve: CA2, CA3 · Depende de: T5 · Arquivos: modificar
3d/blocos.py - Fazer: porcelana fosca
#f4f3efe esmalte#d5fa00com emissão leve, luz de estúdio, Cycles com OptiX; renderizar os 2 matcaps (256 px) ehero-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.svge 4 cubos verdes; arquivos dentro do CA2. - 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-*), criare2e/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. - 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():staticcom movimento reduzido, economia de dados, ≤ 4 núcleos ou memória informada < 4; senãolitecom ponteirocoarse(exige ≥ 6 núcleos quando informado); senãofullpela regra atual. RemoverisLowPowerDevicee trocar a chamada emHeroField.tsx:195. Testes primeiro. - Verificar:
pnpm test -- lib/device.test.ts→ passa;grep -rn isLowPowerDevice app lib→ vazio. - 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 dehero-captions.ts(teste primeiro);InstancedMeshcom os estados em atributos por instância e mistura no vertex shader; entrada em 2,6 s;fullsegueprogressRef,litetroca de estado a cada 4 s à vista; orçamento de 28 ms nos 60 primeiros quadros chamaonErrore grava na sessão. - Verificar:
pnpm test -- lib/blocos.test.ts BlocosScene→ passa. - 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
BlocosSceneeprepareBlocosno lugar deHeroFieldSceneeprepareField;staticnão carrega nada;litesemtrackDive; o canvas cobre o pôster com fade quandodata-ready. - Verificar:
pnpm test -- HeroFieldepnpm test:e2e -- hero-3d scroll→ passam. - 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.tsxeHeroFieldScene*.test.tsx; ajustar o comentário dehero-captions.ts - Verificar:
grep -rn HeroFieldScene app lib→ vazio;pnpm typecheck && pnpm test→ passam. - 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 emchcomsteps(var(--n)), cursor de bloco verde que pisca 3 vezes) nos rótulos mono; marca-texto do trecho verde do H1 porscaleXnum pseudo-elemento; tudo dentro deprefers-reduced-motion: no-preference. - Verificar:
pnpm test -- Hero Section→ passa; inspeção com movimento reduzido: texto inteiro, sem cursor. - 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-stylecomtransition-behavior: allow-discreteno menu do celular, no<dialog>e no::backdrop, no aviso de cookies e no botão do WhatsApp;interpolate-size: allow-keywordscom::details-contentnas perguntas frequentes; 180 ms para sair, 280 ms para entrar. - Verificar:
pnpm test:e2e -- mobile-menu keyboard contact→ passam. - 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-outepage-inpor uma máscara em grade que fecha e abre na diagonal emsteps(), 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. - 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 CaseCoverepnpm test:e2e -- navigation→ passam. - 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) e404(marca com um bloco caindo); loop de 3 s a 24 quadros por segundo no Cycles, 640 px, fundo--ink;ffmpeggera.webme.mp4mais pôster.webp; o script afirma os limites do CA10. - Verificar:
blender -b --python 3d/pecas.py→OK. - 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. - 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 coresslate, e o loop404no lugar do íconeCode2; manter título e links. - Verificar:
pnpm test -- not-found→ passa. - 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.mde~/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 porvisibility; 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.