Contrato de Arquitetura Frontend da Pryne
As decisões estruturais que sustentam os builds vanilla-JS da Pryne, por que elas existem e os invariantes que você não deve quebrar. Implementação de referência: design-demos/ no repositório Portfolio (test) (~/Documents/Coding Folder/Portfolio (test)), build ativo v4-d-hybrid.html + js/roll-*.js, a partir de 2026-07-22.
Jargão, definido uma vez: IIFE = Expressão de Função Imediatamente Invocada, (function () { ... })(); — uma closure por escopo de arquivo, então nada vaza exceto o que o arquivo coloca explicitamente no namespace compartilhado. Enhancement progressivo = a página é totalmente utilizável sem enhancement JS; camadas de comportamento mais ricas só são ativadas quando sua dependência realmente carrega. Develop-plate = a obra de arte em canvas deste projeto que "se desenvolve" (resolve a partir de ruído) conforme o usuário interage.
Quando NÃO usar este skill
- Teoria de animação, easing, coreografia → pryne-motion-reference
- Se uma alteração é permitida e como é bloqueada → pryne-change-control
- Algo está quebrado e você está diagnosticando → pryne-frontend-debugging
- Provar que uma alteração funciona (screenshots, Lighthouse, rubrica) → pryne-validation-and-qa
- Projeto React/Next.js ou qualquer bundler — este contrato é apenas para sites vanilla sem build.
O contrato do módulo
Uma preocupação por arquivo. Uma IIFE por arquivo. Sem bundler, sem imports.
Todo estado entre módulos vive em um único objeto namespace global — window.Roll aqui. roll-core.js o cria e carrega primeiro; cada outro módulo abre com const Roll = window.Roll; e lê/escreve propriedades nele (roll-core.js:1-7 afirma isso explicitamente). Módulos que não devem saber um do outro se comunicam via eventos DOM (exemplo: roll-scroll.js despacha roll:launch; roll-contact-sheet.js escuta — veja roll-scroll.js:35-37).
A ordem dos scripts no HTML é o grafo de dependências. De v4-d-hybrid.html:429-437:
roll-core.js → roll-shader.js → roll-field.js → roll-audio.js → roll-cursor.js
→ vendor/lenis.min.js → roll-scroll.js → roll-contact-sheet.js → roll-eggs.js
Invariante: nunca reordenar ou inserir um <script> sem primeiro grepar o que o módulo lê de Roll e confirmar que todo produtor carrega antes. Não há um sistema de imports para te salvar; uma reordenação falha silenciosamente ou lança erro no carregamento.
grep -n "Roll\.\w*" design-demos/js/<module>.js # o que ele lê/escreve
GSAP está deliberadamente FORA dessa cadeia — veja Invariante 2.
Mapa de módulos (a partir de 2026-07-22)
| Arquivo | Uma preocupação | Linhas |
|---|---|---|
roll-core.js | Namespace, estado compartilhado, mecanismo develop-plate, upgradeMotion | 192 |
roll-scroll.js | A espinha dorsal: entrada, coreografia de scroll, HUD, loop principal rAF (requestAnimationFrame), Lenis | 226 |
roll-field.js | Campo de canvas de fundo | 136 |
roll-shader.js | Shader WebGL de grão/luz + fallback canvas-2D | 133 |
roll-eggs.js | Gatilhos de personalidade ocultos (apenas nível sussurro) | 83 |
roll-audio.js | Som ambiente suave vinculado à velocidade do scroll | 79 |
roll-contact-sheet.js | Revelação de contact-sheet no lançamento | 55 |
roll-cursor.js | Cursor personalizado (apenas desktop, bloqueado pelo GSAP) | 36 |
Nova funcionalidade interativa? É um novo arquivo roll-*.js (ou uma seção claramente delimitada do módulo proprietário daquela preocupação), adicionado à cadeia de scripts na posição de dependência — não <script> inline no HTML.
Invariantes (verificados no código — mantenha-os verdadeiros)
- Movimento reduzido bloqueia todo módulo de movimento.
Roll.reduced = matchMedia('(prefers-reduced-motion: reduce)').matches(roll-core.js:9). Todo módulo o consulta: shader pula WebGL (roll-shader.js:90,124), field pula seu loop rAF (roll-field.js:125,135), contact-sheet retorna cedo (roll-contact-sheet.js:12), scroll pula a entrada hold-to-develop e Lenis (roll-scroll.js:39-40,152), e CSS desativa transições clip-path (v4-d-hybrid.html:293-295). Qualquer novo movimento deve verificarRoll.reduced(JS) ou estar sob a media query de movimento reduzido (CSS). - GSAP é um enhancement progressivo, nunca uma dependência. Ele carrega
asyncvia CDN com hash SRI (v4-d-hybrid.html:440); ao carregar, chamaRoll.upgradeMotion(), que alternaRoll.enhancede executa callbacks registrados viaRoll.onMotionReady(roll-core.js:10-15,186-191). Se GSAP nunca chegar, o site deve permanecer totalmente funcional — cursor, tipo herói e skew simplesmente ficam na linha de base (roll-cursor.js:34-35,roll-scroll.js:32-33,89,196). Registre código dependente de GSAP comRoll.onMotionReady(fn); nunca chamegsap.*incondicionalmente. - Assunções visuais arriscadas são protegidas defensivamente — no caminho recorrente, não apenas na configuração. O cursor personalizado é protegido em três camadas, cada uma com um catch restaurador que anula
Roll.cursorUpdatee remove.custom-cursor(que esconde o cursor do SO): configuração (roll-cursor.js:9-32, catch em 32), corpo do manipulador por evento (roll-cursor.js:17-20), e o local de chamadapointermove(roll-core.js:26-29). Uma falha em qualquer ponto — na configuração ou em qualquer mousemove posterior — restaura o cursor do SO (corrigido em 2026-07-22; contrato fail-safe no cabeçalho do arquivo,roll-cursor.js:1-5). Ao aplicar esse padrão a qualquer coisa que esconda/substitua um affordance nativo, proteja também o caminho recorrente, não apenas a configuração. - Revelações clip-path vivem em um wrapper interno, nunca no elemento que o IntersectionObserver observa (a "correção conceito-C" — nomeada a partir do conceito v4 C,
v4-c-ch001.html, onde se originou antes de ser levada ao v4-d). O IO observa.panel; o clip-path anima.panel-inner(v4-d-hybrid.html:121-122,185-186). Razão: recortar o elemento observado altera sua geometria de interseção e a revelação compete com o gatilho. - Visuais orientados por engajamento são bloqueados por sua própria visibilidade, não pelo carregamento da página. As placas de funcionalidades só se desenvolvem enquanto seu painel tiver
.in(roll-scroll.js:204-219); o comentário do código registra o bug original: placas terminavam de resolver antes de alguém rolar até elas, "silenciosamente derrotando a interação 'desenvolve conforme você interage'." O comentário principal dev4-d-hybrid.htmlchama a correção a de maior prioridade única da auditoria técnica. - WebGL é explícito e seguro com fallback. Alfa não-premultiplicado na criação do contexto (
roll-shader.js:17),gl.clearexplícito a cada frame (roll-shader.js:95), contexto 2D adquirido apenas depois que WebGL teve sua chance — um canvas só pode ter um tipo de contexto (roll-shader.js:9-12), ewebglcontextlostfalha silenciosamente em vez de deixar o loop de renderização lançar erro (roll-shader.js:129-132). Chamadores usam o ponto de entrada únicoRoll.shader.render(t)e nunca sabem se é GL ou 2D. - Scroll permanece nativo sob Lenis. Lenis é vendored (
js/vendor/lenis.min.js, não CDN), instanciado apenas quando!Roll.reduced, e mantémwindow.scrollYválido — é um upgrade de sensação, não um sequestro de scroll (roll-scroll.js:148-152,167). O loop de renderização lêscrollYcomo antes.
Filosofia de demonstração em arquivo único + tokens de design
Cada variante de design é um arquivo HTML autocontido (CSS inline, módulos js/ compartilhados) para que variantes possam ser diffadas, screenshotadas e A/B testadas como artefatos completos — veja pryne-design-direction-decision. Um comentário longo no cabeçalho de cada arquivo registra o que a variante é e quais descobertas de auditoria ela carrega (leia antes de editar; é o changelog local do arquivo).
Todas as cores, espaçamentos e easings são propriedades customizadas CSS em :root (v4-d-hybrid.html:50-63):
:root {
--bg: #0a0a0b; --accent: #d2232e; --ink: #f4f3f1; --dim: #86868c;
--pad-x: clamp(1.25rem, 6vw, 6rem);
--ease-out: cubic-bezier(0.22, 1, 0.36, 1);
--ease-snap: cubic-bezier(0.16, 1, 0.3, 1);
}
Pelas regras globais da Pryne: nunca codifique uma cor/tamanho onde exista um token; valores fluidos usam clamp(). Novo movimento deve reutilizar --ease-out/--ease-snap a menos que pryne-motion-reference diga o contrário — uma linguagem de easing por build.
Checklist: adicionar um módulo
- Novo
roll-*.js(ou módulo proprietário existente correto), IIFE de topo,const Roll = window.Roll; -
<script>inserido depois de tudo que lê deRoll, antes de qualquer coisa que o leia - Verifica
Roll.reduced; uso de GSAP apenas dentro deRoll.onMotionReady - Qualquer coisa que esconda um affordance nativo é envolvida em try/catch com catch restaurador
- Bloqueado por visibilidade se orientado por engajamento; alvo IO ≠ alvo clip-path
- Tokens, não valores codificados
- Depois validar conforme pryne-validation-and-qa
Pontos fracos conhecidos (abertos, declarados claramente)
- Sem etapa de build: sem minificação, sem empacotamento; bom para demonstrações, não resolvido para deploy de produção.
- Fontes carregam do CDN do Google Fonts (
v4-d-hybrid.html:47-48) apesar da intenção first-vendor do projeto (self-hosting é referenciado emdesign-demos/DECISIONS.mdapenas de passagem, dentro da entrada do Lenis; o registro de decisão em si predata o arquivo; ainda não feito). GSAP também ainda está em CDN (fixado + SRI). Veja pryne-change-control antes de "corrigir" qualquer um. - Nenhum teste automatizado — validação é apenas evidência baseada em navegador (pryne-validation-and-qa).
- Conteúdo placeholder: estudos de caso/bio não são finais; não refine o texto como se fosse.
Proveniência e manutenção
Execute scripts/verify.sh (no diretório deste skill) para verificar tudo abaixo de uma vez.
Fundamentado no repositório Portfolio (test) no commit 457868a, 2026-07-22. Re-verifique antes de confiar em fatos voláteis:
- Ordem dos scripts:
grep -n "<script" design-demos/v4-d-hybrid.html - Contagem de linhas / lista de módulos:
wc -l design-demos/js/*.js - Cobertura de movimento reduzido:
grep -rn "Roll.reduced\|prefers-reduced-motion" design-demos/ - GSAP ainda CDN+SRI, fontes ainda CDN:
grep -n "cdn.jsdelivr\|fonts.googleapis" design-demos/v4-d-hybrid.html - Lenis ainda vendored:
ls design-demos/js/vendor/ - Build ativo ainda é v4-d: verifique
design-demos/DECISIONS.mde o últimogit log --oneline