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

617 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.
```css
: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 `button`s reais dentro de `role="tablist"`.
```css
.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
```css
.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:
```ts
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
```css
@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:
```text
<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](https://poly.pizza/m/GnbwSUiVty), listado como OBJ/GLTF e Public Domain (CC0). A licença pública também está referenciada em [CC0 1.0](https://creativecommons.org/publicdomain/zero/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
11. Tablist, tabs, painel e CTA são encontrados no accessibility tree.
12. Teclado consegue trocar tabs sem depender do cursor animado.
13. `prefers-reduced-motion` desativa pin, scrub, cursor automático e WebGL.
14. Pointer coarse/mobile não recebe Canvas WebGL nem overflow horizontal.
15. Falha do GLB mantém screenshot, tabs e CTA no mockup fallback.
16. Um projeto sem health check aprovado não aparece na narrativa.
### Qualidade técnica
17. Não há iframe ou request remoto no componente.
18. O bloco `NotebookStory` mantém somente um Canvas WebGL; controls e fallback não criam canvases adicionais.
19. O unmount remove ScrollTrigger, GSAP context e observers.
20. `PrintReel`, o rig do celular e a textura antiga são removidos depois de não haver referências.
21. `CREDITS.md` contém atribuição e licença do notebook.
22. 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
```bash
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.