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:
cubic-bezier(.16,1,.3,1) — desaceleração suave para reveals e imagens.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/100com glow, barra de progresso, linha de urgência e um único botão grande de CTA sobre um fundo comradial-gradientlaranja. - Outline vs. fill: palavras vazadas (
-webkit-text-stroke, cor transparente) contrastam com blocos sólidos — puro Swiss. - Laranja como acento, não fundo:
#FF4D00aparece em itálico, marcadores e no numeral. O preto domina; o laranja pontua. - Textura: um SVG de ruído (
feTurbulence) emdata: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:
iframedo Google Maps comoutput=embed, num contêiner comaspect-ratioeoverflow:hiddenpara nunca vazar largura. Um levegrayscaleintegra 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:hiddenno body + marquees isolados.
11 Ferramentas
cleanUrls para a rota /guide.