Documentação do processo de código, decisão a decisão, para quem mantiver o site. Iniciado em 01/09/2026. Contratos seguidos:
06-wireframes.md(W2) ·07-design-system.md·tokens.css(v0.1+v0.2) ·10-design-avancado.md(critérios v1) ·11-build-assets.md.
Estrutura criada#
site/
build/build.mjs ← gerador (Node puro, ZERO dependências — decisão 34)
build/lib/*.mjs ← parser das transcrições etc.
src/css/site.css ← estilos da página (tokens.css continua em docs/, é copiado)
src/js/entrevista.js ← player + sincronização (orçamento: <30 KB, meta ~15 KB)
src/i18n/{pt,en}.json ← strings de UI
public/fonts/ ← woff2 variáveis self-hosted (commitados)
public/media/ ← derivados PEQUENOS do vídeo (commitados: poster, sprites,
waveform.json, legendas VTT) — o MP4 nunca entra no git
dist/ ← saída do build (gitignored; regenerar: node build/build.mjs)
Registro cronológico#
01/09 · Etapa 1 — decisão de stack + parser#
- Decisão 34 registrada (03): gerador próprio em Node, zero dependências. O motivo decisivo foi a perenidade (09):
node build/build.mjsprecisa funcionar igual em 2050. - Parser das transcrições (
lib/parse-transcricao.mjs): lê os DOIS arquivos no formato**[MM:SS] FALANTE** — texto, extrai as 5 fitas com ranges, subseções da fita 5, o fecho*(fim da entrevista)*. Resultado: 487 turnos verbatim / 408 revisada, zero problemas. - Verificações embutidas (contrato 11 §12): timestamps não-decrescentes; falantes de conjunto conhecido (inclui "VOZ FEMININA (fora de quadro)" em [117:33] — aprendizado: rótulo com parênteses, o regex de falante não pode ser só maiúsculas); cada marcação
[?]do fonte presente na saída (comparada turno a turno — comparar com o arquivo inteiro dava falso positivo: a seção "Notas" também contém[?]); sha256 dos fontes gravado no build. - Transformações inline (sobre texto já escapado, nesta ordem): âncoras internas
**[134:47]**→ botão de seek;[x?]→.z-unsure(pontilhado + tooltip "não foi chutado");[palavra]→.z-insert(inserção editorial, tooltip);*(texto)*→.z-stage. Nenhum caractere do texto dele é alterado.
01/09 · Etapa 2 — fontes e derivados de mídia#
- Fontes variáveis self-hosted baixadas do fontsource (jsdelivr) para
public/fonts/: Source Serif 4 (normal+italic, latin+latin-ext) e Source Sans 3 (normal, latin+latin-ext), 6 woff2, ~277 KB no total; preload só nos 2 latin principais. Itálico da sans: dispensado (o itálico é reservado à voz dele — serif). - Derivados do vídeo (
build/gera-media.sh, contrato 11 §3–4), commitados empublic/media/(~3,5 MB — o MP4 continua fora do git):legendas-pt.vtt(conversão do SRT) ·poster.jpg(frame de 01:30, validado a olho: ele na poltrona, plano estável) ·sprite-00..08.jpg(824 miniaturas 160×120, 10×10 por página) ·thumbnails.vtt·waveform.json(2.000 picos) ·og.jpg(1200×630, recorte do poster). - Aprendizado ffmpeg: no tile,
-frames:v 100limita a SAÍDA (não a entrada) — o certo é-vf tile=10x10 -frames:v 1 -update 1por página, com-start_numberavançando de 100 em 100. - OG de momento (
?t=) com template tipográfico (11 §7) fica para o v2 — oog.jpgdo recorte é honesto e suficiente para o v1 (o título vem doog:title).
01/09 · Etapa 3 — gerador + página + player#
build/build.mjs: compila as duas trilhas para AMBOS os idiomas (tooltips localizados), gera/(PT),/en/,404.htmlbilíngue, favicon "Z" (11 §6), robots, sitemap com hreflang,build-info.json(data + sha256 dos fontes + contagens). CSS = tokens.css + site.css concatenados sem comentários. Verificações que falham o build: problemas do parser, contagem de turnos < esperado, JS > 30 KB gzip, denylist ("pedidos-prontos") no dist.- Números: index.html 715 KB bruto / 94 KB gzip (é a entrevista INTEIRA, duas trilhas, 895 turnos server-rendered — legível sem JS) · CSS 19,7 KB · JS 14,5 KB (5,2 KB gzip) — orçamento de 30 KB com folga 6×.
build/serve.mjs(novo): servidor de dev com HTTP Range — descoberta importante:python3 -m http.serverNÃO suporta Range, então o seek no MP4 de 1,25 GB congela (video.seekable = [0,0]). Na VPS o nginx faz Range nativo; no dev, usar SEMPREnode site/build/serve.mjs. Documentado também no README do site.- Player: waveform como barra de seek em linha própria (era estreita demais entre os botões — 143px; agora 100% da largura), preview de sprite no hover/arrasto (cálculo direto de página/célula, sem parsear o VTT no cliente), ±10s, CC ligado por padrão, fullscreen, atalhos (espaço; setas na waveform como slider ARIA), posição salva a cada 5s.
01/09 · Etapa 4 — verificação no navegador (todos os fluxos)#
| Teste | Resultado |
|---|---|
| Clique em âncora interna [55:37] (passagem de Majdanek) | seek + play + legenda certa + FITA 2 no "agora" + chip aceso |
| Sincronização vídeo→turno (binary search + follow) | rola a página acompanhando; gesto do usuário suspende; botão religa |
| Busca "Majdanek" | 9 turnos/2 fitas, Custom Highlight API ativa, singular/plural corrigido |
| Aba Verbatim↔Revisada | URL ?trilha=, turno atual mapeado entre trilhas |
Deep link /en/?trilha=verbatim#t=72m33s | abre na frase-título, turno destacado, vídeo posicionado sem tocar |
| hashchange (mudar só o hash) | salta e toca; .is-target agora migra para o novo turno |
| Tema ◐→→ | persiste entre páginas (script inline anti-FOUC) |
| EN completo · 404 com alma · mobile claro/escuro | (tagline escondida no header mobile — transbordava) |
| Retomar de onde parou | chip [MM:SS] com continuar/recomeçar |
| Copiar link do momento | código ok; clipboard negado só no ambiente de teste sintético — re-testar num clique real |
- Formato do vídeo: 4:3 real (768×576) — o wireframe dizia 16:9; obedecido o vídeo.
- Pendências conscientes do v1: OG por momento (v2, 11 §7) · apple-touch-icon PNG (precisa de rasterizador; SVG cobre os browsers modernos) · Lighthouse CI entra com o pipeline de deploy.