Leia este arquivo inteiro antes de extrair, alterar ou consumir qualquer componente. Ele existe para que todo site novo saia com a mesma lógica de HTML, CSS e JS. Regra de ouro: antes de inventar um componente, procure no
catalogo.json.
1. Stack fixa
| Camada | Decisão | Não use |
|---|---|---|
| Markup | HTML semântico estático | template engine, JSX |
| Estilo | CSS puro, custom properties | Tailwind, SCSS, CSS-in-JS |
| Script | JS vanilla, ES5-safe, dentro de IIFE | jQuery, bundler, imports |
| Libs | nenhuma por padrão | GSAP só quando a ficha declarar |
| Fontes | @font-face local, woff2 + woff |
Google Fonts em produção |
O alvo é um snippet que cola em três lugares sem adaptação: página estática, .astro, ou bloco HTML do Elementor. Qualquer coisa que quebre isso está fora do contrato.
GSAP só entra quando a interação depende de timeline encadeada ou de ScrollTrigger com scrub. Reveal, parallax simples e entrada de seção são IntersectionObserver + requestAnimationFrame — já resolvidos no core/motion.js, não reimplemente.
2. Idioma
- Pastas, documentação, comentários e commits: português.
- Classes CSS, ids, nomes de variáveis JS e tokens: inglês, minúsculo, kebab-case.
Motivo: o código já roda assim nos sites existentes (Tarvos, Liberta Wealth). Trocar agora quebraria a portabilidade nos dois sentidos.
3. Tokens — a interface do componente
Componente nunca escreve cor, fonte, espaçamento de seção ou curva de easing na mão. Só lê token. Isso é o que permite o mesmo hero rodar preto-e-sage na Liberta e claro-e-glow em outro cliente.
Tokens globais (definidos em core/tokens.css)
--display família de títulos
--sans família de texto
--pad respiro lateral da coluna
--max largura máxima da coluna
--nav-h altura do menu fixo
--sec-gap respiro vertical entre dobras
--ease curva única do projeto
--radius-pill raio de botão (1000px)
--radius-media raio de imagem/card (0 em projeto flat)
--accent cor de marca
--accent-2 cor de marca secundária
Tokens de escopo de tom (definidos pela classe da seção)
--bg fundo da seção
--fg texto principal
--fg-2 texto secundário
--fg-muted texto de apoio / legenda
--rule linha divisória fraca
--rule-2 linha divisória forte
Escopos disponíveis: .sec-dark, .sec-deep, .sec-paper, .sec-light.
A seção declara o escopo; os componentes dentro dela herdam. Um componente jamais faz override de --fg por conta própria.
Regra do tom vizinho
Fundos escuros adjacentes têm de ser quase idênticos (#0a0a0a / #131313). Salto forte de tom entre seções suja a página. Se precisar de contraste real, use uma seção .sec-paper inteira como respiro, e a seguinte volta direto ao escuro.
4. Grid
.wrap é a única regra que controla a coluna:
.wrap{width:100%;max-width:var(--max);margin-inline:auto;padding-inline:var(--pad)}
Nenhum componente mexe em padding-inline, max-width de página ou margin lateral. Precisa sangrar até a borda? O elemento sai de dentro do .wrap, não ganha margin negativa.
Ritmo vertical: .sec-pad (padding: var(--sec-gap) 0). Não invente clamp solto para separar seções.
5. Movimento
Três ganchos globais, servidos por core/motion.js. Componente usa esses — não escreve observer próprio para entrada de elemento.
| Gancho | Uso | Efeito |
|---|---|---|
data-rv |
qualquer bloco | fade + blur + subida |
data-d="1..3" |
junto do data-rv |
escalona o delay |
data-split |
frase de destaque | revela palavra a palavra |
.ttl |
título de seção | reveal próprio, com blur |
Nada entra seco. Toda entrada carrega filter: blur(14px) indo a zero. Translate puro não é aceito.
prefers-reduced-motion: reduce desliga tudo — já tratado no core. ?motion=on na URL força a animação para gravar vídeo ou apresentar.
6. Regras de CSS por componente
- Um prefixo, sempre. Todas as classes do componente começam pelo prefixo declarado no
componente.json(hero-,fan-,marquee-). Nada de.n,.full,.veilsoltos — colidem no primeiro projeto que juntar dois componentes. - Zero
!important. Se precisou, o token está errado. - Zero seletor de elemento global. Um
h3dentro do componente vira.fan-title. - Nenhuma dependência de ordem de carregamento. O CSS do componente funciona colado em qualquer posição da folha.
- Mobile no fim. Media queries de largura ficam no fim do próprio bloco, nunca em arquivo separado.
@media(prefers-reduced-motion)é exceção e fica colado na regra que desliga — ali a proximidade é o que torna a intenção legível.
Quem confere isso é o bin/conformidade.py, que audita cada componente contra este arquivo: prefixo, !important, seletor de elemento, cor crua, ordem das media queries, IIFE, passive, movimento reduzido. Rode antes de dar um componente por pronto:
python bin/conformidade.py
Um alerta pode ter explicação legítima — e quando tem, ela vira comentário no próprio arquivo, para o próximo a passar por ali não precisar redescobrir.
Primitivas do core
O componente pode assumir que estas existem e não as recopia:
| Classe | O quê |
|---|---|
.wrap |
a coluna |
.sec-pad |
respiro vertical da seção |
.eyebrow |
olho com traço |
.btn / .btn-primary / .btn-ghost |
botão pílula |
.grain |
ruído SVG sobre mídia escura |
.glass / .glass-hover |
card de vidro (borda tênue, brilho radial, blur) |
h2.sec-title / .sec-lead |
título e apoio de seção |
.sec-head |
cabeçalho de dobra centralizado; largura por --head-max |
.pill / .pill-accent |
rótulo pequeno em pílula de contorno |
Uma regra que aparece em três ou mais componentes é candidata a primitiva. Antes de recopiar pela terceira vez, promova ao core e ajuste os componentes.
Véu sobre imagem
backdrop-filter em caixa reta produz borda dura e lê "chapado". Sempre acompanhado de máscara em gradiente:
mask-image:linear-gradient(to top,#000 0%,rgba(0,0,0,.9) 44%,transparent 100%);
Texto sobre imagem
Legenda ou título de card de mídia vai dentro da imagem, ancorado embaixo, com gradiente mais blur mascarado. Não vira parágrafo abaixo da foto.
7. Regras de JS por componente
(function () {
var root = document.querySelector('.meu-componente');
if (!root) return; // guard
addEventListener('scroll', onScroll, { passive: true }); // sempre passive
})();
- IIFE fechada. Nada no escopo global.
- Guard clause. Ausência do elemento é saída silenciosa, nunca erro no console.
passive: trueemscrolleresize.requestAnimationFramepara qualquer coisa presa ao scroll — nunca cálculo direto no listener.- Respeita
reduce. Leia a flag do core antes de animar. - Sem busca global no documento quando o componente pode repetir na página: parta da raiz do próprio componente.
8. Anatomia de um componente
componentes/<categoria>/<nome-kebab>/
├── componente.json metadados lidos pelo índice
├── ficha.md o que é, quando usar, padrão editorial, histórico, gotchas
├── markup.html só o bloco da seção, sem <html> nem <head>
├── estilo.css só as classes do prefixo
├── script.js opcional — ausente se for CSS puro
└── preview.html página autônoma que roda o componente sozinho
preview.html importa o core/ por caminho relativo e usa os placeholders de showroom/demo/. Ele tem de abrir com duplo-clique, sem servidor.
O preview.html é gerado a partir do markup.html — nunca escreva o markup duas vezes à mão:
bash bin/previews.sh # todos
bash bin/previews.sh hero/full-bleed-media # um só
TEMA=<projeto> bash bin/previews.sh # com outro tema
Precisa de mais que o markup no preview? Três arquivos opcionais na pasta do componente, todos respeitados pelo gerador:
| Arquivo | Para quê |
|---|---|
preview-extra.html |
markup depois do componente — uma seção falsa para o parallax ter para onde rolar, um botão que dispara o estado |
preview-css.txt |
folhas extras, um caminho por linha — para componente que compõe outro |
preview-js.txt |
scripts extras, um por linha, inseridos antes do próprio |
9. Fluxo para catalogar um componente novo
- Copie
_modelo/paracomponentes/<categoria>/<nome>/. - Extraia o markup do projeto de origem e normalize os prefixos de classe.
- Troque toda cor crua por token. Se faltar token, discuta antes de criar — token novo é decisão de arquitetura.
- Se a regra já apareceu em três componentes, promova ao core como primitiva antes de recopiar.
- Preencha
componente.jsone aficha.mdinteira, inclusive o padrão editorial (que conteúdo entra ali) e o histórico. - Registre em
projetos/<projeto>.mdde onde veio. - Rode os dois geradores:
python bin/indice.py # catalogo.json + tabelas do CATALOGO.md + cards do showroom + fichas em HTML
bash bin/previews.sh # preview.html de cada componente, a partir do markup.html
python bin/conformidade.py # audita o componente contra este contrato
python bin/valida.py # confere que nenhum caminho relativo aponta para o vazio
catalogo.json, as tabelas do CATALOGO.md, os cards do showroom e os preview.html são gerados. Editar qualquer um deles à mão é trabalho perdido na próxima execução — a fonte é o componente.json e o markup.html.
10. Fluxo para consumir num site novo
- Leia o
CATALOGO.mde escolha as seções. - Copie
core/tokens.css,base.css,motion.cssemotion.js. - Duplique
core/temas/_modelo.csspara o tema do cliente e preencha só os valores. - Cole
markup.html,estilo.cssescript.jsde cada componente escolhido. - Nunca edite o componente para caber no cliente — edite o tema. Se o tema não resolve, o componente precisa de uma variação nova na ficha, não de um hack local.
11. Status possíveis
estavel— rodou em produção, pode copiar sem medo.revisar— funciona, mas tem gotcha aberto na ficha.experimental— só rodou uma vez, pode mudar.externo— nunca rodou em produção. Veio de fora ou foi construído aqui, e ainda não passou por um site de verdade. Normalizado ao contrato e testado no preview, mas sem quilometragem: espere ajuste no primeiro uso real.
Um externo que roda num site vira estavel (ou revisar, se deixar pendência) — e a ficha registra em qual projeto isso aconteceu. Essa promoção é o único caminho de saída do externo.
Componente de origem externa
Componente que veio de fora entra sempre como externo, e a ficha tem de trazer, no histórico: de onde veio, quem escreveu, sob qual licença. Mesmo licença permissiva costuma exigir crédito — e crédito não custa nada. Se a licença não estiver clara, não catalogue: reescreva do zero ou deixe passar.