Bastidores · Guia técnico

Como o KINETIC foi feito.

Uma campanha de tipografia em movimento — Swiss/editorial, sem 3D e sem bibliotecas pesadas. Só HTML, CSS e JavaScript puro. Hero premium, carrossel da unidade, seções de modalidade, escassez, avaliações do Google, mapa, FAQ e um marquee "VEM QUEIMAR" contínuo. Aqui está cada técnica, para você replicar.

00 A ideia

O conceito é tipografia cinética: a hierarquia, o ritmo e a energia da página vêm das letras — não de imagens flutuantes ou WebGL. Headlines gigantes em Montserrat Black, marquees em velocidades diferentes, texto que se revela caractere a caractere no scroll, um contador de escassez em numeral gigante e um cursor magnético. Tudo com uma única regra de easing que dá a sensação "designed".

Stack: HTML estático + CSS + JS vanilla. Zero build, zero dependência. Fonte via Google Fonts. Deploy estático na Vercel.

01 Split-text no scroll

O efeito de cada palavra "subindo" de dentro de uma máscara é feito quebrando o texto em palavras → caracteres e animando um transform: translateY dentro de um contêiner com overflow:hidden.

Em vez de escrever o markup manualmente, um pequeno script percorre os nós de texto (preservando tags como <em> e <br>) e embrulha cada palavra:

// cada palavra vira <span class="word"><i>texto</i></span>
.word   { display:inline-block; overflow:hidden }
.word i { transform:translateY(110%); transition:transform .9s var(--ease) }
.reveal.in .word i { transform:translateY(0) }

Quando o bloco entra na viewport (via IntersectionObserver), adicionamos a classe .in e damos a cada letra um transition-delay incremental — é o stagger que cria a cascata.

items.forEach((i,ix) => i.style.transitionDelay = (ix*0.045)+'s');

02 Marquees em rAF

Os textos que deslizam na horizontal não usam CSS keyframes — usam requestAnimationFrame. Motivo: controle fino de velocidade e direção por faixa, e um loop perfeito sem "salto".

Cada faixa duplica o conteúdo. A cada frame movemos o translateX e, quando o deslocamento passa da largura de um bloco, somamos a largura de volta — o olho nunca percebe o reset:

m.x -= m.speed * m.dir;
if (m.dir>0 && -m.x >= m.w) m.x += m.w;  // loop infinito
m.el.style.transform = 'translateX('+m.x+'px)';
  • Faixa 1 desliza devagar (speed 0.6), laranja preenchida.
  • Faixa 2 vai ao contrário (dir -1) e mais rápida, em outline vazado.
  • A diferença de velocidade e direção cria profundidade — o "parallax" tipográfico.

Sem overflow horizontal: as faixas ficam em contêineres com overflow:hidden e white-space:nowrap. Elas se movem, mas nunca empurram a largura da página — a barra de rolagem horizontal jamais aparece.

03 Contador de escassez

O numeral gigante "73 / 100" anima de 0 ao alvo quando entra na tela. A contagem usa rAF com um ease-out cúbico (1 − (1−p)³) para desacelerar no fim — muito mais vivo que um incremento linear. A barra de progresso preenche em paralelo.

var e = 1 - Math.pow(1-p, 3);   // ease-out
cEl.textContent = Math.round(e*target);

O tamanho tipográfico faz o trabalho pesado: clamp(120px, 26vw, 340px) com font-variant-numeric: tabular-nums para os dígitos não "dançarem" durante a contagem.

04 Cursor magnético

Dois elementos seguem o mouse: um ponto preciso (segue direto) e um anel que persegue com interpolação (lerp), criando o atraso elástico. Ambos com mix-blend-mode: difference para inverter sobre qualquer fundo.

cx += (mx - cx) * 0.16;   // lerp: anel "corre atrás"
cy += (my - cy) * 0.16;

Nos botões marcados com [data-mag], o próprio elemento é puxado na direção do cursor — o efeito "ímã" que convida ao clique:

var x = (e.clientX - centroX) * 0.28;
el.style.transform = 'translate('+x+'px,'+y+'px)';

Em telas de toque (pointer:coarse) o cursor é escondido — nada de comportamento estranho no mobile.

05 Easing & ritmo

Duas curvas fazem toda a "assinatura" de movimento:

--ease
cubic-bezier(.16,1,.3,1) — desaceleração suave para reveals e imagens.
--overshoot
cubic-bezier(.34,1.56,.64,1) — passa do ponto e volta; usado em botões e no sucesso do form.

O overshoot é o detalhe que faz parecer "produzido": botões que crescem além do 100% e assentam, o cartão de sucesso que dá um leve "pop". Aplicado com parcimônia — só onde há interação.

06 Grid editorial & cores

  • Escassez como pico visual: o bloco de contador é centralizado e hero-grade — numeral gigante 73/100 com glow, barra de progresso, linha de urgência e um único botão grande de CTA sobre um fundo com radial-gradient laranja.
  • Outline vs. fill: palavras vazadas (-webkit-text-stroke, cor transparente) contrastam com blocos sólidos — puro Swiss.
  • Laranja como acento, não fundo: #FF4D00 aparece em itálico, marcadores e no numeral. O preto domina; o laranja pontua.
  • Textura: um SVG de ruído (feTurbulence) em data: URI a 5% de opacidade tira o "digital liso" da tela.

07 Nova unidade & seções de modalidade

A seção da nova unidade é empilhada: uma foto horizontal quase full-bleed do studio no topo (larga, spanning o contêiner) e o bloco narrativo abaixo dela — não mais lado a lado. Sob o texto, a galeria "por dentro da unidade" é um marquee automático e contínuo (zero biblioteca): o track é duplicado e traduzido em requestAnimationFrame, resetando exatamente no offsetLeft do primeiro card duplicado — loop perfeito, sem emenda. Pausa no hover, máscara de fade nas bordas, e congela em prefers-reduced-motion.

Cada modalidade (Bike → Hot Yoga → Hot Pilates) é uma seção editorial própria em que a foto ocupa a altura inteira da faixa, borda a borda de topo a base: grid com align-items:stretch e min-height, a coluna da imagem em height:100% com object-fit:cover, e o texto centralizado verticalmente ao lado. O lado alterna (.mod.rev inverte colunas e ordem). A foto vira um painel de altura total, dominante, com leve scale no hover.

08 "VEM QUEIMAR" — marquee contínuo

O momento assinatura antes do rodapé: um loop horizontal infinito e sem emenda de "VEM QUEIMAR ✳", alternando preenchido e vazado em laranja. Usa o mesmo motor de requestAnimationFrame dos marquees — a faixa é duplicada e, quando o deslocamento passa a largura de um bloco, a largura é somada de volta. Sem reset perceptível, sem scroll horizontal (o contêiner é overflow:hidden).

.w-fill { color:var(--accent) }              // preenchido
.w-out  { -webkit-text-stroke:1.6px var(--accent); color:transparent }  // vazado

A faixa inteira é um link único — toda conversão sai por um link externo trocável (href="https://justburnclub.com.br" target="_blank", marcado com um comentário TODO ao lado de cada CTA). Não há mais formulário na página: todo botão de "retirar as 2 aulas" aponta para esse link, fácil de trocar pelo WhatsApp/checkout real.

09 FAQ, avaliações & mapa

  • FAQ sem JS: acordeão com <details>/<summary> nativos — acessível por teclado de graça. O ícone +/− é puro CSS (dois pseudo-elementos) e gira com [open].
  • Avaliações do Google: logo do Google em SVG inline (4 paths coloridos, zero request), nota 4,8, estrelas e 3 depoimentos reais em cards com avatar de inicial.
  • Mapa: iframe do Google Maps com output=embed, num contêiner com aspect-ratio e overflow:hidden para nunca vazar largura. Um leve grayscale integra o mapa à paleta escura.

10 Acessível & responsivo

  • prefers-reduced-motion: um único media query desliga marquees, reveals, contador animado e cursor — o conteúdo aparece estático e completo.
  • Tipografia fluida: tudo em clamp(), então a hierarquia escala do mobile 390px ao desktop sem breakpoints frágeis.
  • Conversão por link externo: sem formulário — cada CTA é um <a target="_blank" rel="noopener"> para um link trocável, então a captura acontece onde a marca já opera (WhatsApp/checkout).
  • Zero scroll horizontal: overflow-x:hidden no body + marquees isolados.

11 Ferramentas

Montserrat
Google Fonts, pesos 300–900 + itálico. A base tipográfica.
IntersectionObserver
Dispara reveals e o contador só quando visíveis.
requestAnimationFrame
Marquees, contador e cursor — 60fps sem libs.
Web APIs puras
Nenhum framework. ~1 arquivo HTML, self-contained.
Vercel
Deploy estático, cleanUrls para a rota /guide.