As regras duras, e só elas. O porquê de cada uma, os casos que custaram
uma noite e os fluxos passo a passo estão no MANUAL.md — leia
antes de construir, não antes de consultar.
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 |
Google Fonts em produção |
Alvo: um snippet que cola em três lugares sem adaptação — página estática,
.astro, ou bloco HTML do Elementor.
GSAP só com timeline encadeada ou ScrollTrigger com scrub. Reveal,
parallax simples e entrada de seção já estão no core/motion.js.
Fontes: woff2 + woff para fonte estática; woff2 sozinho quando for
variável — woff não carrega fonte variável, e todo navegador que entende
uma entende a outra. Nunca declare @font-face para arquivo que não está
no repositório — 404 de fonte não cai no fallback de graça (MANUAL §1).
2. Idioma
- Pastas, documentação, comentários e commits: português.
- Classes CSS, ids, variáveis JS e tokens: inglês, minúsculo, kebab-case.
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.
| O que | Regra |
|---|---|
Token global (--accent, --sec-gap, --ease…) |
criar um é decisão de arquitetura — discuta antes |
Custom property do próprio componente (--cine-dur, --wheel-radius) |
livre, desde que prefixada como as classes |
A segunda linha é o que permite parametrizar sem inflar o vocabulário global: o número que só aquele componente entende mora dentro dele.
Globais (em core/tokens.css)
--display --sans famílias
--pad --max --nav-h coluna e menu
--sec-gap respiro entre dobras
--ease curva única do projeto
--radius-pill --radius-media
--accent --accent-2 marca
--veil --z-overlay véu e degrau de sobreposição
--alta --baixa estado: alta e baixa
--alta/--baixa são o par de mercado, e o nome fala do papel, não do
pigmento — um tema pode trocá-los por âmbar e azul sem que o componente fique
mentindo no próprio código. Os valores são redefinidos por escopo de tom,
porque nenhum verde único alcança 4,5:1 contra #0a0a0a e contra #ffffff ao
mesmo tempo. Cor nunca é o único portador da direção: quem usa o par diz a
direção também por forma (seta, sinal) e por texto para o leitor de tela.
De escopo de tom (definidos pela classe da seção)
--bg --fg --fg-2 --fg-muted --rule --rule-2 --accent-ink
--alta --baixa (calibrados por escopo)
--accent-ink é a tinta do realce, e existe porque --accent é cor de
marca, não cor de texto. Em escopo escuro ela é o próprio --accent; em
escopo claro é o accent misturado com a tinta do escopo, porque o accent puro
não fecha contraste sobre fundo claro — medido 1.36:1 com o sage da Liberta,
contra um mínimo de 3:1. Quem escreve realce lê --accent-ink, nunca
--accent (MANUAL §3).
Escopos: .sec-dark, .sec-deep, .sec-paper, .sec-light.
A seção declara, os componentes dentro herdam.
Componente não faz override de --fg por conta própria — com uma exceção
nomeada: escopo de tom no markup. Um bloco pode declarar .sec-dark ou
.sec-deep na própria marcação para inverter o tom dentro de uma dobra de
tom oposto. Ele está entrando num escopo, não sobrescrevendo o de fora — a
diferença é que continua lendo os mesmos nomes, sem redefinir nenhum.
Precedente: conteudo/bento-recursos-expansivel.
Tom vizinho: fundos escuros adjacentes têm de ser quase idênticos
(#0a0a0a / #131313). Contraste real se faz com uma seção .sec-paper
inteira, e a seguinte volta 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. Para sangrar até a borda, o elemento sai do .wrap — não ganha
margem negativa.
Ritmo vertical: .sec-pad (padding: var(--sec-gap) 0). Não invente clamp
solto para separar seções.
5. Movimento
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 blur indo a zero.
data-rv e transform próprio não moram no mesmo nó. O core termina o
reveal com [data-rv].in{transform:none}, seletor que vence sua classe por
especificidade e apaga o transform em silêncio quando a dobra entra. A
saída é sempre a mesma: o data-rv vai num invólucro, e o elemento que se
move fica por dentro. Precedente: efeitos/inclinacao-3d.
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. CSS por componente
- Um prefixo, sempre. Toda classe começa pelo prefixo declarado no
componente.json. Nada de.n,.full,.veilsoltos. - Zero
!important. Se precisou, o token está errado. - Sem seletor de elemento solto. Um
h3do componente vira.fan-title. Permitido como descendente do próprio prefixo, em conteúdo de prosa que o autor do texto escreve livre —.fan-body p,.fan-body li,.fan-body strong. Fora daí, classe. - Nenhuma dependência de ordem de carregamento. O CSS funciona colado em qualquer posição da folha.
- Quem dá
displaya algo que às vezes é[hidden]desliga à mão:.meu-bloco[hidden]{display:none}. Qualquer regra de autor comdisplayvence o[hidden]do navegador. Já foi preciso em quinze componentes. - Media queries de largura no fim do próprio bloco, nunca em arquivo
separado.
@media(prefers-reduced-motion)é exceção e fica colada à regra que desliga.
Quem confere é python bin/conformidade.py. Um alerta pode ter explicação
legítima — quando tem, ela vira comentário no próprio arquivo.
Primitivas do core — não recopie
.wrap · .sec-pad · .eyebrow · .btn/.btn-primary/.btn-ghost ·
.grain · .glass/.glass-hover · h2.sec-title/.sec-lead · .sec-head ·
.pill/.pill-accent · .display-xl
Regra que aparece em três ou mais componentes é candidata a primitiva. Promova ao core antes de recopiar pela terceira vez.
Três regras de forma
- Véu sobre imagem:
backdrop-filtersempre acompanhado de máscara em gradiente — caixa reta produz borda dura e lê "chapado". - Texto sobre imagem: legenda ou título de card de mídia vai dentro da imagem, ancorado embaixo. Não vira parágrafo abaixo da foto.
- Hover não mexe em nada. Sob o ponteiro, imagem, card e botão não
escalam, não sobem, não deslizam e não mudam de tamanho — nem abrem por
passagem do mouse. O retorno de hover mora em cor, brilho, borda e
opacidade. Exceção única: o ícone de seta que anda 2–4px dentro do botão e
o filete que cresce sob o link (
::afteremscaleX) — são tipografia, não peça. Componente cujo assunto é o ponteiro (efeitos/*) declara isso na ficha. Oconformidade.pyacusa (MANUAL §6).
7. JS por componente
(function () {
var raizes = document.querySelectorAll('[data-meu]');
if (!raizes.length) return; // guard
[].forEach.call(raizes, function (raiz) { // cada instância
/* tudo parte de `raiz`; scroll da página via LIB.onScroll */
});
})();
- IIFE fechada. Nada no escopo global.
- Guard clause. Ausência do elemento é saída silenciosa, nunca erro.
passive: trueemscrolleresize.requestAnimationFramepara qualquer coisa presa ao scroll.- Respeita
reduce. Leia a flag do core antes de animar. - Parta da raiz do componente, nunca do documento — ele pode repetir na página.
8. Anatomia
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 GERADO do markup.html — nunca escreva à mão
preview.html importa o core/ por caminho relativo, usa os placeholders de
showroom/demo/ e tem de abrir com duplo-clique, sem servidor.
destaque no componente.json decide a ordem dentro da categoria:
3 vitrine · 2 forte · 1 normal (padrão) · 0 bastidor. É julgamento
seu, não métrica — a complexidade entra só como desempate. Quem decide o
bloco é a ordem de bin/categorias.json.
Cinco arquivos opcionais controlam o preview (preview-antes.html,
preview-extra.html, preview-tema.txt, preview-css.txt,
preview-js.txt) — o que cada um faz está no MANUAL §8.
9. Os quatro comandos
python bin/indice.py # catalogo.json + tabelas + cards do showroom + fichas 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 caminho relativo: href, src e url() de CSS
E um quinto, que lê o navegador em vez do código — obrigatório depois
de mexer no core/, porque uma primitiva atinge dezenas de peças de uma vez:
node bin/varredura.js # os 80 previews no Chrome: console, 404, estouro, texto sem JS
catalogo.json, as tabelas do CATALOGO.md, os cards do showroom e os
preview.html são gerados. Editar qualquer um à mão é trabalho perdido na
próxima execução — a fonte é o componente.json e o markup.html.
Fluxo completo de catalogação, passo a passo: MANUAL §9.
10. Consumir num site novo
- Escolha as seções no
CATALOGO.md. - Copie
core/tokens.css,base.css,motion.css,motion.jse a pastacore/temas/fontes/biblioteca/. - 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 e caminhos de entrada
| Status | Quer dizer |
|---|---|
estavel |
sem pendência aberta — copie |
revisar |
funciona, mas a ficha tem pendência aberta |
experimental |
a forma ainda pode mudar entre versões |
O status fala do código, não do histórico: a biblioteca não registra o que
já rodou em produção. Cada projeto pede uma peça diferente, e "já foi usado"
não prevê se serve no próximo. Em variacoes[], experimental marca a
variante que ainda não foi exercitada.
Três caminhos de entrada, detalhados no MANUAL §11:
| Caminho | Registro consolidado | origem[].projeto |
|---|---|---|
| Clone de referência — um site no ar, reescrito do zero | projetos/referencias-externas.md |
"referência: <site>" |
| Origem externa de código — autor nomeado, licença explícita | idem, seção própria | nome do registro |
| Autoral — padrão comum, sem referência única | projetos/autorais.md |
"autoral" |
Componente extraído de site nosso: projetos/<projeto>.md.
Duas regras que não se negociam:
- Não se copia — se reescreve. Licença de site aleatório nunca é clara, e o original quase nunca serviria: vem em Tailwind, GSAP ou React.
- Sem licença clara, não catalogue. Reescreva do zero ou deixe passar. Havendo autor e licença, o crédito é obrigatório na ficha — de onde veio, quem escreveu, sob qual licença — mesmo que a licença dispense.
Não atravessam a tradução, mesmo que o original tenha: fonte de terceiro, ícone proprietário, logo, foto e qualquer asset com direito de imagem.