← Biblioteca componentes/prova/stats-contador-regua/ficha.md Abrir previewmarkupestiloscript
prova/stats-contador-regua

Régua de números com contagem

🔵 experimental complexidade média prefixo stat- prova-social numeros contador metricas resultados count-up intersection-observer

Uma faixa com números gigantes lado a lado, separados por filete vertical, cada um com um rótulo curto em caixa alta embaixo. Quando a faixa entra na tela, os números sobem de zero até o alvo em 1,8 segundos e param — uma vez só, nunca de novo. No mobile a régua vira pilha e o filete troca de eixo.

A contagem é IntersectionObserver + requestAnimationFrame, ~30 linhas. Nenhuma lib.


Quando usar

Quando não usar


Padrão editorial

Slot Regra
Olho (.eyebrow) 1 a 3 palavras. "Em números", "Resultados", "Desde 2014". Opcional.
Título (.sec-title) 1 linha, uma palavra em <em>. Nomeia a medida, não repete os números: "O trabalho, medido".
Apoio (.sec-lead) 1 parágrafo, 15 a 30 palavras. Diz de onde vêm os números e de quando são. Opcional, mas é aqui que a régua ganha credibilidade.
Número (.stat-num) Até 5 glifos contando afixo: 12+, 240+, 98,5%, R$ 40mi. Acima disso transborda a célula de propósito.
Rótulo (.stat-label) 2 a 4 palavras, no máximo duas linhas. Substantivo, sem verbo: "Projetos entregues", não "Já entregamos projetos". Minúsculas no HTML — a caixa alta é do CSS.
Pílula do rodapé (.pill) A data da apuração. "Apurado em 01/2026". Número sem data é promessa.
Botão (.btn) 1, no máximo. Leva ao lugar onde o número se comprova (cases, relatório). Opcional.

Três é o padrão. Quatro cabe baixando --stat-size. Dois funciona, mas com dois itens o filete central vira eixo de simetria e a faixa fica parecendo comparação — cuidado se os números não forem comparáveis.

Todo número precisa ser verificável. A régua é o lugar da página onde uma afirmação vira dado; se ninguém consegue conferir, escreva prosa e não coloque em 92px.


Como usar

<link rel="stylesheet" href="core/tokens.css">
<link rel="stylesheet" href="core/temas/<projeto>.css">
<link rel="stylesheet" href="core/base.css">
<link rel="stylesheet" href="core/motion.css">
<link rel="stylesheet" href="componentes/prova/stats-contador-regua/estilo.css">
...
<script src="core/motion.js"></script>
<script src="componentes/prova/stats-contador-regua/script.js"></script>

Cole o markup.html, troque os números e os rótulos. Depende do core e de mais nada.

Os quatro atributos

Toda a parametrização mora no markup. O script não conhece nenhum número da sua página.

Atributo Onde O quê
data-stats na faixa (.stat-band) a raiz. O script aceita várias na mesma página
data-count em cada .stat-num o alvo. Sempre com ponto: 98.5, nunca 98,5
data-suffix opcional vem depois: +, %, mi, /5
data-prefix opcional vem antes: R$, +, ~
data-dec opcional separador decimal na tela. Padrão ,; use . em projeto em inglês
<p class="stat-num" data-count="98.5" data-suffix="%">98,5%</p>
<p class="stat-num" data-prefix="R$ " data-count="40" data-suffix=" mi">R$ 40 mi</p>

O texto escrito no HTML

O .stat-num traz o valor final escrito, não o zero. Isso é decisão, não descuido:

Ou seja: o texto do markup é fallback; a fonte da verdade é o data-count. Os dois não conseguem divergir na tela, porque o script sempre reescreve.

Variáveis locais

No .stat-band:

Variável Padrão O quê
--stat-cols 3 quantidade de colunas no desktop
--stat-gap clamp(16px,2.5vw,44px) respiro interno de cada célula
--stat-size clamp(52px,7.5vw,92px) corpo do número

Variações

Variação Como
4 números --stat-cols:4 e --stat-size:clamp(38px,5.4vw,64px)
Números na cor de marca .stat-band--accent
Régua em card de vidro class="glass stat-band stat-band--card" — compõe a primitiva do core
Faixa nua apague .sec-head e .stat-foot. Foi a forma de origem: uma faixa entre duas dobras, sem título
Filete horizontal fechando a régua border-block:1px solid var(--rule);padding-block:clamp(26px,4vh,40px) no tema. Experimente sem antes: os verticais já dão a estrutura, e os quatro juntos leem tabela
Sem contagem não carregue o script.js. O markup já está pronto e correto

A régua daqui e a régua do hero

O hero/full-bleed-media já embute uma régua de quatro números (.hero-stats / .hero-k / .hero-v). São coisas diferentes e devem continuar separadas.

hero/full-bleed-media prova/stats-contador-regua
Pergunta que responde por que confiar quanto
Conteúdo dos slots chaves, nem sempre numéricas: 100%, SP · POA, Global, CVM quantidades, sempre numéricas
Corpo 19px, cor --accent 52–92px, cor --fg
Separação um filete horizontal em cima, nada entre os itens filete vertical entre os itens
Itens 4 fixos 3 (2 a 4 por --stat-cols)
Entrada cascata do hero, delay .95s [data-rv] do core, e a contagem em cima
Movimento nenhum depois de entrar conta 1,8s
Onde vive dentro do hero, acima da dobra seção própria, abaixo da dobra

O hero deveria passar a compor este componente? Não, e por quatro motivos concretos:

  1. Metade dos slots do hero não é número. CVM e SP · POA não têm alvo. Numa régua onde dois itens contam e dois ficam parados, o resultado lê como falha, não como decisão.
  2. A contagem não cabe acima da dobra. A régua do hero entra em .95s, no fim de uma cascata que já tem headline em duas linhas, apoio e CTA. Somar 1,8s de contagem empurra o "a página assentou" para depois dos 2,7s e põe movimento competindo com a headline no momento em que ela é a única coisa que importa.
  3. 19px e 92px não são o mesmo objeto tipográfico. Fundir os dois exigiria transformar o corpo em variável e o componente perderia identidade: um "número gigante" de 19px não é este componente, é outro.
  4. A régua do hero são seis linhas de CSS. Trocá-las por um componente com --stat-cols, filetes, empilhamento e observer é mais código para fazer menos.

O que efetivamente compõe é o script, não o CSS. O script.js daqui procura [data-count] dentro de [data-stats] e troca textContent — ele não conhece uma única classe stat-. Um projeto que queira o hero contando adiciona data-stats no .hero-stats e data-count nos .hero-k numéricos, carrega este script, e pronto: zero CSS, zero alteração no hero. Essa costura existe de propósito e está documentada aqui para não ser redescoberta. A recomendação continua sendo não usar (motivo 2), mas a porta está aberta e é de duas linhas.

Se um dia um terceiro componente pedir a mesma dupla "chave grande + rótulo pequeno separados por filete", aí sim vale promover ao core como primitiva, conforme o §6 do contrato. Com dois, ainda não.


Gotchas


Histórico

Projeto Onde Data Diferenças
Gabriel Rocha Studio gabriel-rocha-studio-2.html, section.stats 2026-08 Origem. Site autoral, autocontido, com GSAP e ScrollTrigger carregados para a página inteira.

O que mudou na travessia:

No original Aqui Por quê
gsap.to({val:0}, …) como tweener e ScrollTrigger para a hora IntersectionObserver + requestAnimationFrame, easeOutCubic no lugar de power3.out Contrato §1: GSAP só entra em timeline encadeada ou ScrollTrigger com scrub. Isto não é nenhum dos dois — é um número subindo
parseFloat + Math.round casas derivadas do próprio data-count + toFixed 4.9 chegava na tela como 5. Agora 98.5 conta com uma casa e mostra 98,5
0 escrito no HTML valor final escrito no HTML, zerado pelo script antes da primeira pintura Sem JS, a régua inteira afirmava zero
.stat-item{border-right:0!important} no mobile mesmo seletor :not(:last-child), mesma especificidade, mais adiante na folha Contrato §6: zero !important. Especificidade igual + ordem resolve
var(--mono) no rótulo var(--sans) A biblioteca não tem token de fonte mono. Criar um é decisão de arquitetura (§3) e ficou em aberto — veja abaixo
Um observer global disparando por .stats um observer por [data-stats], partindo da raiz Contrato §7.6: o componente pode repetir na página
Cores cruas var(--line), var(--muted) do site --rule, --fg-muted, --fg Tokens da biblioteca, para rodar em qualquer tema
Sem tratamento de movimento reduzido valor final escrito, sem contagem, sem observer Exigência do contrato
Faixa nua entre duas seções ganhou .sec-head e .stat-foot opcionais A forma nua continua disponível apagando os dois blocos
Números afirmando fato sobre pessoa real conteúdo genérico de exemplo Regra da biblioteca: markup de componente é dimensionamento, não conteúdo

Em aberto — token de fonte mono

O site de origem usa --mono (DM Mono) em todo rótulo pequeno: olho, legenda de card, número de acordeão, ticker, rodapé. A biblioteca não tem esse token e este componente resolveu com var(--sans), que é o que .eyebrow e .marquee-label já fazem. Funciona e está consistente.

Mas se mais componentes desse site entrarem, a decisão volta: ou todos convertem mono em --sans (e a voz de rótulo do projeto se perde na travessia), ou a biblioteca ganha um --mono em core/tokens.css com fallback monospace. É decisão de arquitetura, fica registrada aqui e não foi tomada.

Em aberto — primitiva de rótulo

11px / caixa alta / tracking largo / --fg-muted já aparece em .eyebrow (com traço), .marquee-label, .fold-cap, .pill e agora .stat-label. Pelo §6 do contrato isso passou de três e é candidato a primitiva — um .label sem traço, do qual .eyebrow seria a variação com traço. Não promovi: mexer no core está fora do escopo desta extração, e a mudança toca cinco componentes. Fica registrado para quem for fazer a próxima varredura.

Status experimental: rodou uma vez, no site de origem, e nesta forma normalizada nunca. Espere ajuste no primeiro uso real — provavelmente no corpo do número com quatro colunas e na fonte do rótulo.