Files
portifolio-evandrodevbr/docs/superpowers/specs/2026-08-30-notebook-story-design.md
T

27 KiB
Raw Blame History

NotebookStory — design e especificação de implementação

O portfólio deixa de mostrar telas como uma faixa que passa diante do visitante e passa a revelar sistemas como uma sessão de trabalho: abrir, navegar, trocar de projeto, fechar.

Status

  • Estado: direção aprovada para implementação.
  • Escopo: substituir o palco 3D do celular e o PrintReel horizontal por uma única experiência de notebook.
  • Interação: L3 — pin, scrub reversível, objeto 3D, cursor coreografado e tabs acessíveis.
  • Implementação: ainda não iniciada.

Decisões fechadas

  1. O bloco de história terá um único dispositivo e um único Canvas WebGL. A hero atual não ganha outro renderer por causa desta mudança.
  2. O notebook usará um modelo 3D CC0, com abertura controlada por rig próprio.
  3. A tela não usará iframe nem dependerá de rede para renderizar a narrativa.
  4. A interface da tela será determinística, usando screenshots reais dos projetos já presentes no repositório.
  5. O cursor será coreografado pelo scroll; as tabs e o CTA continuarão sendo controles DOM reais.
  6. O PrintReel será removido depois da migração, sem alias ou caminho paralelo.
  7. O scroll continuará sendo o controle de play e rewind. Não será introduzido Lenis.

Objetivo e não escopo

Objetivo

Criar uma seção de prova visual dos projetos em que o visitante percebe um notebook físico abrindo, vê uma interface de navegador, acompanha um cursor trocando de aba entre projetos reais e observa o dispositivo fechar antes de continuar para a seção About.

A experiência deve comunicar três coisas sem depender de texto longo:

  • os projetos são sistemas reais, não apenas imagens de portfólio;
  • cada projeto tem uma interface e um contexto próprios;
  • a engenharia é apresentada como uma sequência de decisões operacionais.

Não escopo

  • Navegar sites reais dentro do notebook.
  • Reproduzir autenticação, formulários ou estados internos dos deployments.
  • Criar um desktop completo ou um sistema operacional fictício.
  • Adicionar áudio, vídeo, partículas pesadas ou um cursor global novo.
  • Manter o celular como uma segunda narrativa.
  • Redesenhar as seções About, Skills, Experience, Projects ou Contact.

1. Visual Theme & Atmosphere

Style: Dark Editorial / Systems Workbench.

Keywords: bancada escura, metal fosco, browser chrome, prova operacional, cursor preciso, verde ácido, tipografia técnica, silêncio entre ações.

Tone: cinematográfico e técnico, mas sóbrio — não gaming, não cyberpunk, não demonstração de template.

Feel: uma câmera observa uma máquina de trabalho real sendo preparada, usada e fechada no fim do expediente.

Interaction Tier: L3 — experiência imersiva com uma cena pinada, timeline scrubbed, WebGL e interação DOM.

Dependencies: React/R3F/Three/Drei já existentes + GSAP/ScrollTrigger já existentes. Não adicionar dependências.

Direção de composição

  • Fundo: var(--ink) com grade discreta e halo radial em var(--slash-dim).
  • Texto narrativo: lado esquerdo em desktop, alinhado à mesma margem do restante da home.
  • Notebook: lado direito em desktop, ocupando o foco visual sem encobrir o texto.
  • Tela: navegador escuro, borda fina, barra de abas legível e screenshot com contraste suficiente.
  • Acento: var(--slash) somente em estado ativo, progresso, cursor e CTA.
  • O modelo não deve parecer um produto anunciado. Deve parecer uma ferramenta em uso.

Estrutura narrativa

  1. Preparar: notebook fechado repousa sobre a bancada.
  2. Abrir: a tampa sobe pela dobradiça; o usuário entende imediatamente a mecânica.
  3. Ligar: o browser chrome aparece e a tela acende.
  4. Explorar: o cursor entra, clica nas tabs e troca o screenshot do projeto.
  5. Concluir: a última tela recebe uma pausa para leitura e um CTA real.
  6. Encerrar: cursor sai, tela reduz a luminosidade, notebook fecha e a seção libera o fluxo normal.

2. Color Palette & Roles

A implementação reutiliza os tokens existentes em app/globals.css. Os nomes abaixo são o contrato visual do novo palco; novos hexadecimais não devem ser introduzidos em componentes.

:root {
  /* Backgrounds */
  --notebook-bg: var(--ink);
  --notebook-surface: #111214;
  --notebook-surface-alt: rgba(255, 255, 255, 0.04);
  --notebook-surface-hover: rgba(255, 255, 255, 0.08);

  /* Borders */
  --notebook-border: rgba(255, 255, 255, 0.12);
  --notebook-border-hover: rgba(204, 255, 0, 0.62);

  /* Text */
  --notebook-text: var(--paper);
  --notebook-text-secondary: rgba(244, 242, 237, 0.72);
  --notebook-text-tertiary: rgba(244, 242, 237, 0.48);

  /* Accent */
  --notebook-accent: var(--slash);
  --notebook-accent-hover: var(--white);

  /* RGB variants */
  --notebook-bg-rgb: 10 10 10;
  --notebook-paper-rgb: 244 242 237;
  --notebook-accent-rgb: 204 255 0;

  /* Semantic */
  --notebook-success: var(--slash);
  --notebook-error: #ff6b6b;
  --notebook-warning: #f5c451;
}

Regras de cor

  • Todo CSS novo usa tokens; nenhum componente recebe hex solto.
  • O verde ácido marca apenas ação, estado ativo, foco, progresso ou informação de produção.
  • Texto de descrição não usa o verde como cor principal.
  • Estados de erro e indisponibilidade não serão simulados dentro da narrativa; projetos não aprovados são removidos da tablist.
  • Materiais R3F usam um mapa de cores central que replica os tokens acima; o modelo não cria uma paleta independente.
  • Gradientes ficam restritos ao fundo atmosférico e não são aplicados dentro da screenshot para não mascarar a interface real.

3. Typography Rules

O palco mantém as fontes locais e os tokens tipográficos da home; não haverá importação adicional de fonte.

Papel Fonte Tamanho Peso Altura Tracking
Título da narrativa var(--font-display) clamp(2.4rem, 6vw, 5.2rem) 800 0.98 -0.03em
Título do projeto ativo var(--font-display) clamp(1.25rem, 2.4vw, 2rem) 800 1.0 -0.025em
Corpo var(--font-mono) clamp(0.82rem, 1.2vw, 1rem) 400 1.7 normal
Tab/browser chrome var(--font-mono) 10–11px 600 1.2 0.06–0.12em
CTA var(--font-mono) 11px 700 1.0 0.10em
Metadados var(--font-mono) 9–10px 600 1.2 0.12–0.16em

Regras tipográficas

  • Títulos permanecem em caixa alta apenas quando já seguem a convenção da home.
  • Texto de tab usa nomes reais dos projetos; não abrevia nomes de forma ambígua.
  • O screenshot pode conter a tipografia própria do projeto. Não aplicar filtro ou blend que prejudique a leitura.
  • Para este estilo editorial, títulos não usam gradiente nem text-shadow; a profundidade vem do modelo, da luz e do contraste.
  • Não usar fontes externas, emoji ou ícones improvisados.

4. Component Stylings

NotebookStory shell

Default: fundo var(--notebook-bg), min-height: 100dvh, overflow horizontal bloqueado, conteúdo centrado em max-width: 1240px.

Active: o palco recebe data-phase e data-project-id; apenas o projeto corrente usa o acento visual.

Focus: o foco nunca fica no Canvas; controles DOM recebem outline: 2px solid var(--notebook-accent) e outline-offset: 3px.

Disabled: controles de projeto indisponíveis não aparecem na tablist; o estado não será apenas visualmente desabilitado.

Browser chrome

  • Janela com raio discreto (var(--r-md)), borda de 1px e fundo quase preto.
  • Barra superior separa tabs, endereço e controles decorativos.
  • A barra permanece estável; trocar de projeto muda o conteúdo e o estado ativo, não move o frame inteiro.
  • Ícones de janela são decorativos e aria-hidden; não usar símbolos de texto como controles.

Project tabs

As tabs são buttons reais dentro de role="tablist".

.notebook-tab {
  min-height: 44px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--notebook-text-tertiary);
  transition:
    color 180ms var(--ease-out),
    background-color 180ms var(--ease-out),
    border-color 180ms var(--ease-out),
    transform 180ms var(--ease-out);
}

.notebook-tab:hover {
  border-color: var(--notebook-border);
  background: var(--notebook-surface-hover);
  color: var(--notebook-text);
}

.notebook-tab[aria-selected="true"] {
  border-color: var(--notebook-accent);
  background: rgb(var(--notebook-accent-rgb) / 0.10);
  color: var(--notebook-accent);
}

.notebook-tab:active {
  transform: scale(0.98);
}

.notebook-tab:focus-visible {
  outline: 2px solid var(--notebook-accent);
  outline-offset: 3px;
}

.notebook-tab:disabled {
  cursor: not-allowed;
  opacity: 0.42;
}

Cursor visual

  • Elemento decorativo com aria-hidden="true" e pointer-events: none.
  • Desenhado em CSS/SVG simples, sem seguir o cursor global do site.
  • Estado normal: seta clara pequena.
  • Estado de hover: halo de var(--notebook-accent) com escala limitada.
  • Estado de click: anel de 1 pulso, sem filter: blur() em movimento.
  • O cursor nunca será a única indicação da tab ativa.

CTA Abrir projeto

  • Default: bloco preenchido com var(--notebook-accent) e texto var(--ink).
  • Hover: fundo var(--notebook-accent-hover), texto var(--ink).
  • Active: escala 0.98.
  • Focus: anel de acento visível.
  • Disabled: não renderizar se o projeto não possuir demoUrl aprovado.
  • A ação abre o deployment em nova aba com target="_blank" e rel="noopener noreferrer".

5. Layout Principles

Container

  • Max width: 1240px, igual às demais seções.
  • Padding horizontal: 24px em mobile/tablet e 64px em desktop amplo.
  • Texto narrativo: máximo de 36ch para preservar hierarquia.
  • Palco 3D: área mínima de min(58vw, 760px) em desktop.

Desktop

.notebook-story-grid {
  display: grid;
  grid-template-columns: minmax(260px, 0.72fr) minmax(520px, 1.28fr);
  align-items: center;
  gap: clamp(24px, 5vw, 80px);
}
  • O notebook ocupa a coluna visual maior.
  • O browser chrome não pode tocar as bordas do viewport.
  • O texto não deve ser colocado sobre a área mais clara da tela.
  • A indicação de progresso fica próxima ao rodapé, seguindo o HUD atual do ScrollStory.

Scroll runway

  • Runway inicial: 520vh, recalculável conforme o número de tabs.
  • Palco: position: sticky; top: 0; height: 100dvh.
  • O sticky libera no final da fase de fechamento; não usar position: fixed global.
  • O próximo elemento no DOM é About; não inserir uma faixa horizontal intermediária.

Espaçamento

  • Entre kicker e título: var(--sp-3).
  • Entre texto e metadata do projeto: var(--sp-8).
  • Entre tabs: 4px a 8px conforme largura.
  • CTA e tabs devem ter área de toque mínima de 44px.

6. Depth & Elevation

Nível Tratamento Uso
Flat var(--ink) + borda sutil Fundo e áreas sem foco
Surface plano quase preto + borda var(--notebook-border) Browser chrome e tablist
Elevated sombra curta, opacidade baixa Browser sobre a tela do notebook
Device luz direcional + metalness moderado Corpo e tampa do GLB
Focus linha/halo de var(--notebook-accent) Tab ativa, progresso e cursor

Regras:

  • Não usar sombra gigantesca para esconder recortes do modelo.
  • Não animar box-shadow em todos os frames.
  • Reflexos do metal devem ser discretos e vir da iluminação R3F.
  • O conteúdo da tela deve continuar mais legível que os efeitos de profundidade.

7. Animation & Interaction

Filosofia de movimento

O objeto deve se comportar como um objeto com massa: aproxima, articula na dobradiça, estabiliza, recebe input e fecha. Cada movimento tem função narrativa; não haverá rotação contínua gratuita.

Contrato de progresso

Um único valor progress ∈ [0, 1] dirige:

  • transformação do notebook;
  • intensidade da tela;
  • posição do cursor visual;
  • projeto ativo;
  • barra de progresso;
  • entrada e saída do texto contextual.

O scroll para cima deve recalcular exatamente os mesmos valores em sentido inverso. Não usar timers para alterar o estado narrativo.

Timeline

Faixa Estado Animação principal Conteúdo
0.00–0.10 approach notebook entra, escala estabiliza, luz de bancada acende kicker geral
0.10–0.25 open tampa gira de fechada para aproximadamente 105° título da narrativa
0.25–0.34 boot tela ganha brilho; browser chrome revela texto curto de contexto
0.34–0.44 cursor-in cursor entra pela lateral e aponta para a primeira tab tab 01 preparada
0.44–0.78 browse quatro ciclos de mover, clicar, trocar screenshot e manter leitura uma tab ativa por vez
0.78–0.87 hold última tela permanece; metadata e CTA ganham foco projeto corrente
0.87–1.00 close cursor sai, brilho reduz, tampa fecha, palco perde opacidade próximo conteúdo

Cada ciclo de tab terá uma janela de leitura maior que a janela de clique. A troca não deve parecer um carrossel: a moldura permanece fixa e somente o conteúdo da viewport muda com crossfade curto.

Rig 3D

  • base permanece estável e recebe leve deslocamento de entrada.
  • lid é o único elemento que rotaciona na abertura/fechamento.
  • O plano de tela acompanha a transformação local da tampa.
  • A câmera mantém perspectiva 3/4 frontal; mudanças de FOV são proibidas durante a troca de tabs.
  • A rotação livre do celular existente será removida; não reaproveitar motionRig como turntable.
  • O efeito de aproximação da câmera será limitado a uma pequena variação de posição, sem zoom agressivo.

Tela e cursor

A tela será um componente DOM 3D com Html transform do Drei, alinhado ao plano interno do modelo. Isso permite que o browser chrome e as tabs sejam reais, acessíveis e clicáveis.

  • Screenshot: <img> local com object-fit: cover e recorte definido por projeto.
  • Browser chrome: DOM, não desenhado em Canvas.
  • Cursor: elemento DOM dentro do mesmo plano da tela, movido por timeline.
  • Clique coreografado: altera aria-selected, activeProject e screenshot somente na janela de troca.
  • O componente deve pré-carregar a imagem atual e a próxima; as demais entram sob demanda.

Interação do visitante

  • O visitante pode rolar e rebobinar a sequência normalmente.
  • Foco de teclado não altera o scroll automaticamente.
  • ArrowLeft/ArrowRight mudam a tab selecionada; Home/End saltam para a primeira/última.
  • Clique numa tab usa scroll suave nativo até a janela de progresso daquela tab; depois a timeline continua sendo a fonte de verdade.
  • Clique em Abrir projeto é a única ação que sai do portfólio e abre um deployment.
  • A coreografia pode ser interrompida pelo input sem deixar dois cursores ou duas tabs ativas.
  • O cursor visual não acompanha pointermove global; o cursor global existente continua com seu contrato atual.

ScrollTrigger

Usar o padrão existente:

const timeline = gsap.timeline({
  defaults: { ease: "none" },
  scrollTrigger: {
    trigger: section,
    start: "top top",
    end: "bottom bottom",
    scrub: 0.85,
    invalidateOnRefresh: true,
  },
});
  • Não criar um segundo listener global de scroll.
  • Atualizações de texto e aria-selected devem ser quantizadas por mudança de tab, não por frame.
  • A animação deve ser destruída via gsap.context().revert() no unmount.
  • O Canvas deve pausar quando o palco estiver fora da viewport.

Reduced motion e fallback

@media (prefers-reduced-motion: reduce) {
  .notebook-story-stage {
    min-height: auto;
  }

  .notebook-story-sticky {
    position: static;
    height: auto;
  }

  .notebook-cursor {
    display: none;
  }
}

Com prefers-reduced-motion: reduce:

  • não inicializar GSAP, ScrollTrigger ou WebGL;
  • renderizar notebook aberto como mockup estático ou imagem do modelo;
  • mostrar uma tab selecionada e a tablist completa;
  • manter título, descrição, screenshot e CTA em fluxo normal;
  • permitir troca manual por teclado e clique.

8. Do's and Don'ts

Do

  • Usar screenshots reais já versionados em public/assets/prints.
  • Fazer o browser chrome permanecer fixo enquanto as tabs trocam.
  • Dar tempo suficiente para o visitante entender cada projeto.
  • Manter o notebook como protagonista visual e o texto como orientação.
  • Usar uma única variável de progresso para toda a cena.
  • Parar renderização quando o palco estiver invisível.
  • Manter os controles DOM acessíveis mesmo quando o modelo 3D falhar.
  • Atualizar CREDITS.md com o modelo e sua licença.

Don't

  • Não usar iframe ou rede externa para montar a tela durante o scroll.
  • Não transformar screenshots em uma faixa horizontal novamente.
  • Não criar um segundo Canvas WebGL para o notebook.
  • Não rotacionar o notebook inteiro como se fosse um celular.
  • Não animar filter: blur() em elementos que se movem a cada frame.
  • Não esconder tabs clicáveis dentro de um Canvas sem equivalente DOM.
  • Não usar o cursor global como substituto do estado ativo da tab.
  • Não deixar o usuário preso no sticky depois que a tampa fechou.
  • Não mostrar projetos fora do snapshot aprovado de saúde.
  • Não iniciar timers independentes que quebrem o rewind do scroll.

9. Responsive Behavior

Breakpoint Layout Comportamento
Desktop >= 1025px, hover disponível duas colunas GLB CC0, Html transform, sticky + scrub completo
Tablet 768–1024px uma coluna compacta 3D somente com hover disponível e espaço suficiente; caso contrário, fallback 2D
Mobile < 768px ou pointer coarse fluxo normal sem WebGL; mockup 2D aberto, tablist em 2 colunas ou wrap, screenshot única
Qualquer largura + reduced motion fluxo normal fallback estático acessível, sem pin, cursor automático ou scrub

Mobile

  • Nunca exigir que o visitante acompanhe uma tampa 3D em uma tela pequena.
  • O mockup 2D deve manter proporção de notebook, mas priorizar screenshot, tabs e CTA.
  • Tabs podem quebrar em duas linhas; não usar white-space: nowrap em uma faixa que cause overflow.
  • O screenshot ativo ocupa a largura disponível com aspect-ratio estável.
  • O CTA fica depois do screenshot, com largura confortável para toque.
  • A seção não deve criar overflow horizontal em viewport de 320px.

Arquitetura e integração com o repositório

Composição da home

CinematicHome.tsx deve passar projects para o novo palco e ficar nesta sequência:

<Hero />
<marquee />
<NotebookStory projects={projects} />
<About />
<SectionDivider />
<Skills />
...

O <PrintReel /> será removido. O conteúdo de contexto atualmente presente no ScrollStory será adaptado para o notebook; não haverá duas introduções concorrentes.

Componentes propostos

  • app/components/story/NotebookStory.tsx: orquestra runway, ScrollTrigger, capítulos, estado de tab e fallback.
  • app/components/story/NotebookStage3D.tsx: carrega o GLB, monta o rig, câmera, luzes e boundary de falha.
  • app/components/story/NotebookBrowser.tsx: browser chrome, tabs, screenshot, cursor visual e CTA.
  • app/components/story/notebookProjects.ts: mapa entre IDs de projeto e assets de screenshot, sem chamada de rede.
  • app/components/cinematic/CinematicHome.tsx: integração e remoção do PrintReel.
  • public/models/laptop-kenney.glb: asset CC0 aprovado após validação de nós e escala.
  • CREDITS.md: fonte, autor, licença, URL e modificações do modelo.

Depois da migração, remover os caminhos sem consumidores:

  • PrintReel.tsx;
  • StoryStage3D.tsx;
  • phoneScreenTexture.ts;
  • testes específicos desses componentes, substituídos por testes do notebook.

ReelDirector.tsx continua responsável pelas animações declarativas das seções restantes.

Dados, assets e licenciamento

Projetos

A fonte continua sendo PortfolioProject[] carregada no servidor. O mapa visual deve usar:

  • id para escolher o screenshot local;
  • title e subtitle para metadata;
  • technologies para a linha técnica;
  • demoUrl para o CTA;
  • a lista já filtrada pelo snapshot de saúde para decidir quais tabs existem.

A ordem inicial é a já usada no filmstrip atual:

  1. nutriscan — /assets/prints/print-nutriscan.png;
  2. sc-plus — /assets/prints/print-scplus.png;
  3. sev-modaintima — /assets/prints/print-sev.png;
  4. as-servicos — /assets/prints/print-asservicos.png.

Se um projeto live não tiver screenshot mapeado, ele não entra na narrativa até o asset ser incluído; não renderizar imagem quebrada ou placeholder colorido.

Modelo 3D

Candidato aprovado para investigação: Laptop — Kenney / Poly Pizza, listado como OBJ/GLTF e Public Domain (CC0). A licença pública também está referenciada em CC0 1.0.

Antes de integrar:

  • baixar uma cópia local, não carregar o GLB por URL remota;
  • inspecionar o Outliner e confirmar objetos separados para base, tampa, tela e teclado;
  • confirmar origem da tampa próxima à dobradiça;
  • corrigir escala, normais, materiais e pivô offline;
  • registrar fonte e modificações em CREDITS.md;
  • se o arquivo não permitir separar a tampa, escolher outro asset CC0 ou aplicar o fallback procedural; não animar o modelo inteiro como substituto.

Tela

A tela usa os PNGs existentes, sem captura em runtime. Não haverá requisição externa durante renderização, nem dependência de disponibilidade dos deployments para a animação aparecer.

Acessibilidade

  • A seção recebe aria-label="Projetos em produção no notebook".
  • A tablist usa role="tablist"; cada tab usa role="tab", aria-selected e aria-controls.
  • O painel ativo usa role="tabpanel", aria-labelledby e nome do projeto.
  • Mudança de projeto atualiza uma região aria-live="polite" curta, sem narrar cada frame.
  • O Canvas e o cursor coreografado são decorativos para tecnologia assistiva.
  • O CTA real permanece fora da textura e é alcançável por teclado.
  • A ordem de tabulação segue texto → tabs → CTA, sem focar controles decorativos.
  • O fallback sem JavaScript continua apresentando título, tabs, screenshot e links no fluxo normal.
  • Foco visível, contraste e áreas de toque seguem os tokens globais já existentes.

Performance e limites

  • Um único Canvas WebGL para o bloco NotebookStory; não criar um segundo contexto para browser chrome, tabs ou fallback.
  • Renderers 3D existentes da hero devem ser pausados quando estiverem fora da viewport, evitando duas cenas animando ao mesmo tempo.
  • WebGL somente em desktop com hover disponível.
  • Canvas pausado fora da viewport por IntersectionObserver ou equivalente do palco.
  • dpr limitado a [1, 1.5] no notebook.
  • GLB local otimizado para no máximo 2 MB transferidos; texturas externas não são permitidas.
  • Uma imagem atual e uma próxima pré-carregadas; demais lazy.
  • Screenshot não deve ser redesenhada por frame; apenas a troca de src e o movimento de cursor acompanham a timeline.
  • Nenhum fetch de deployment dentro de NotebookStory.
  • Nenhuma nova biblioteca de scroll ou de cursor.
  • Alvo de verificação: pelo menos 50 FPS em desktop intermediário durante scrub contínuo; se o modelo não atingir isso, reduzir luzes/geometria antes de adicionar efeitos.
  • Mobile e reduced motion não criam Canvas WebGL.

Critérios de aceite

Comportamento

  1. A seção inicia com notebook fechado.
  2. A tampa abre pela dobradiça, sem girar o corpo inteiro.
  3. O browser chrome aparece antes da primeira troca de tab.
  4. O cursor visual move-se até a primeira tab, clica e troca o conteúdo.
  5. As quatro tabs aprovadas aparecem na ordem definida quando os quatro projetos estão live.
  6. Cada tab mostra screenshot, nome, metadata e CTA correspondente.
  7. A tela permanece no mesmo enquadramento; não existe deslocamento horizontal de cards.
  8. O notebook fecha depois da última leitura.
  9. Ao terminar o fechamento, About aparece no fluxo normal sem salto ou seção presa.
  10. Ao rolar para cima, todas as fases rebobinam sem duplicar tabs, cursores ou listeners.

Acessibilidade e fallback

  1. Tablist, tabs, painel e CTA são encontrados no accessibility tree.
  2. Teclado consegue trocar tabs sem depender do cursor animado.
  3. prefers-reduced-motion desativa pin, scrub, cursor automático e WebGL.
  4. Pointer coarse/mobile não recebe Canvas WebGL nem overflow horizontal.
  5. Falha do GLB mantém screenshot, tabs e CTA no mockup fallback.
  6. Um projeto sem health check aprovado não aparece na narrativa.

Qualidade técnica

  1. Não há iframe ou request remoto no componente.
  2. O bloco NotebookStory mantém somente um Canvas WebGL; controls e fallback não criam canvases adicionais.
  3. O unmount remove ScrollTrigger, GSAP context e observers.
  4. PrintReel, o rig do celular e a textura antiga são removidos depois de não haver referências.
  5. CREDITS.md contém atribuição e licença do notebook.
  6. O build de produção continua passando.

Plano de testes e verificação

Testes unitários/componentes

  • mapa de projeto: filtra IDs live e ignora screenshot ausente;
  • função de timeline: cada faixa de progress produz a fase esperada;
  • seleção de tab: aria-selected, aria-controls e painel acompanham a mudança;
  • teclado: setas, Home, End, Enter e Space;
  • CTA: URL, target e rel corretos;
  • reduced motion: fallback sem inicializar GSAP/WebGL;
  • falha do GLB: boundary renderiza mockup sem perder controles.

Smoke browser

Em Chromium real, verificar:

  1. carregar a home sem erro de console;
  2. confirmar notebook fechado no início da seção;
  3. avançar o scroll até abertura, boot e cada tab;
  4. voltar o scroll e confirmar rewind visual;
  5. clicar numa tab e confirmar seek para sua janela;
  6. usar teclado no tablist;
  7. chegar ao fim e confirmar que About segue normalmente;
  8. medir ausência de overflow horizontal em desktop e 320px;
  9. repetir com reduced motion e confirmar fluxo estático;
  10. confirmar que o CTA abre somente quando acionado pelo visitante.

Verificações de entrega

pnpm exec jest --runInBand
pnpm exec tsc --noEmit
pnpm exec biome check app/components/story app/components/cinematic/CinematicHome.tsx
pnpm run build

Ordem de implementação

  1. Validar, otimizar e creditar o GLB CC0.
  2. Criar o rig base/lid e o fallback procedural no palco existente.
  3. Criar NotebookBrowser com DOM 3D, tabs, screenshot e CTA.
  4. Migrar a timeline do ScrollStory para o contrato de fases do notebook.
  5. Passar projects de CinematicHome e filtrar a lista visual pelos dados aprovados.
  6. Remover PrintReel, celular e textura antiga após atualizar todos os consumidores.
  7. Implementar reduced motion, mobile e falhas do modelo.
  8. Executar testes, smoke browser, build e revisão visual desktop/mobile.

A implementação só começa depois da revisão deste spec; qualquer mudança no número de Canvas, origem dos screenshots, ordem de projetos ou comportamento de fallback deve ser registrada neste documento antes do código.