docs: spec notebook story interativo
This commit is contained in:
1 parent
a3d080d4d0
commit
7bbdfd9daf
1 file changed
+616
@@ -0,0 +1,616 @@
|
||||
# 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.
|
||||
Reference in new issue
Block a user