18 efeitos prontos. Cole e funciona.
Biblioteca de motion para sites e LPs Entrelaços. Cada cartão tem demo ao vivo + snippet HTML/CSS/JS copiável. Tudo respira na curva-assinatura cubic-bezier(0.22, 1, 0.36, 1) e suporta prefers-reduced-motion.
tokens.css (roxo/laranja/curvas). Se você não tem esses tokens, troque pelos seus hex/curvas.
O efeito mais usado em LPs Entrelaços. Elementos aparecem com opacity 0→1 + translateY 24px→0 ao entrar no viewport. IntersectionObserver dispara apenas uma vez por elemento.
<div class="fx-reveal" data-reveal>Conteúdo</div>
.fx-reveal { opacity: 0; transform: translateY(24px); transition: opacity 800ms cubic-bezier(0.16, 1, 0.3, 1), transform 800ms cubic-bezier(0.16, 1, 0.3, 1); } .fx-reveal.is-revealed { opacity: 1; transform: translateY(0); }
const io = new IntersectionObserver(entries => { entries.forEach(e => { if (e.isIntersecting) { e.target.classList.add('is-revealed'); io.unobserve(e.target); } }); }, { threshold: 0.18, rootMargin: '0px 0px -10% 0px' }); document.querySelectorAll('[data-reveal]').forEach(el => io.observe(el));
Cards filhos revelam em sequência com delay incremental. Use em grids de pilares, depoimentos, features. 90ms é o passo padrão Entrelaços — rítmico sem ficar lento.
<div data-stagger="90"> <div class="fx-stagger-item" data-stagger-item>A</div> <div class="fx-stagger-item" data-stagger-item>B</div> <div class="fx-stagger-item" data-stagger-item>C</div> </div>
.fx-stagger-item { opacity: 0; transform: translateY(18px); transition: opacity 600ms cubic-bezier(0.16, 1, 0.3, 1), transform 600ms cubic-bezier(0.16, 1, 0.3, 1); } .fx-stagger-item.is-revealed { opacity: 1; transform: translateY(0); }
document.querySelectorAll('[data-stagger]').forEach(group => { const kids = group.querySelectorAll('[data-stagger-item]'); const step = parseInt(group.dataset.stagger || '90'); const io = new IntersectionObserver(entries => { entries.forEach(e => { if (e.isIntersecting) { kids.forEach((c, i) => setTimeout(() => c.classList.add('is-revealed'), i * step)); io.unobserve(group); } }); }, { threshold: 0.18 }); io.observe(group); });
Stats que animam quando entram na tela. Curva cubic-out em 1800ms. Aceita decimais e sufixos (%, +, x).
<div data-count="120" data-suffix="+">0</div> <div data-count="98.5" data-suffix="%">0</div>
const easeOut = t => 1 - Math.pow(1 - t, 3); const animate = (el) => { const target = parseFloat(el.dataset.count); const suffix = el.dataset.suffix || ''; const decimals = (el.dataset.count.split('.')[1] || '').length; const start = performance.now(); const tick = (now) => { const t = Math.min(1, (now - start) / 1800); el.textContent = (target * easeOut(t)).toFixed(decimals) + suffix; if (t < 1) requestAnimationFrame(tick); }; requestAnimationFrame(tick); }; const io = new IntersectionObserver(entries => { entries.forEach(e => { if (e.isIntersecting) { animate(e.target); io.unobserve(e.target); } }); }, { threshold: 0.5 }); document.querySelectorAll('[data-count]').forEach(el => io.observe(el));
Esteira horizontal contínua para trust strips, palavras-chave, logos. Pausa no hover. Mask gradiente nas bordas para fade-in/out elegante. Sem JS — duplica o conteúdo no HTML.
<div class="fx-marquee"> <div class="fx-marquee-track"> <!-- duplique a lista 2x para loop sem corte --> <span class="fx-marquee-item">Item A</span> <span class="fx-marquee-item">Item B</span> <span class="fx-marquee-item">Item A</span> <span class="fx-marquee-item">Item B</span> </div> </div>
.fx-marquee { overflow: hidden; mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent); } .fx-marquee-track { display: flex; gap: 48px; width: max-content; animation: fx-marquee-x 24s linear infinite; } .fx-marquee:hover .fx-marquee-track { animation-play-state: paused; } @keyframes fx-marquee-x { from { transform: translateX(0); } to { transform: translateX(-50%); } }
O hover canônico de cards Entrelaços. translateY(-4px) + sombra roxa colorida + borda assumindo o tom da marca. Nunca sombra preta.
Passe o mouse
Lift + glow roxo. Curva 0.22, 1, 0.36, 1.
<div class="fx-lift">...</div>
.fx-lift { transition: transform 300ms cubic-bezier(0.22, 1, 0.36, 1), border-color 300ms, box-shadow 300ms; } .fx-lift:hover { transform: translateY(-4px); border-color: #A878EC; box-shadow: 0 12px 30px rgba(116,39,212,0.30); }
CTAs que reagem fisicamente ao cursor. Movem-se ~35% da distância até o ponteiro. Use com parcimônia — só 1 botão por dobra. Excessivo cansa.
<a class="e-btn fx-magnetic-wrap" data-magnetic="0.35">Botão</a>
document.querySelectorAll('[data-magnetic]').forEach(btn => { const strength = parseFloat(btn.dataset.magnetic) || 0.35; btn.addEventListener('mousemove', e => { const r = btn.getBoundingClientRect(); const dx = (e.clientX - r.left - r.width / 2) * strength; const dy = (e.clientY - r.top - r.height / 2) * strength; btn.style.transform = `translate(${dx}px, ${dy}px)`; }); btn.addEventListener('mouseleave', () => { btn.style.transform = 'translate(0,0)'; }); });
A "luz roxa" que segue o cursor dentro do card. CSS controla o gradiente via custom-properties; o JS só atualiza --mx e --my.
Mova o mouse aqui
Spotlight roxo segue o cursor. Aparece só no hover. Performance ótima — só atualiza CSS vars.
<div class="fx-spot" data-spotlight>...</div>
.fx-spot { position: relative; overflow: hidden; } .fx-spot::before { content: ''; position: absolute; inset: 0; background: radial-gradient(circle at var(--mx, 50%) var(--my, 50%), rgba(168,120,236,0.18) 0%, transparent 40%); opacity: 0; transition: opacity 300ms; pointer-events: none; } .fx-spot:hover::before { opacity: 1; }
document.querySelectorAll('[data-spotlight]').forEach(el => { el.addEventListener('mousemove', e => { const r = el.getBoundingClientRect(); el.style.setProperty('--mx', ((e.clientX - r.left) / r.width * 100) + '%'); el.style.setProperty('--my', ((e.clientY - r.top) / r.height * 100) + '%'); }); });
Texto com gradiente animado em loop infinito. Use em palavras-chave do hero, manifesto, ou frases-âncora. Nunca em parágrafos longos — vira distração.
<span class="fx-shimmer">Texto brilhante</span>
.fx-shimmer { background: linear-gradient(90deg, #C9A7F4 0%, #fff 25%, #FFB87A 50%, #fff 75%, #C9A7F4 100%); background-size: 200% 100%; -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; animation: fx-shimmer-x 6s linear infinite; } @keyframes fx-shimmer-x { from { background-position: 200% 0; } to { background-position: -200% 0; } }
Sublinhado que entra da direita e sai pela esquerda — sensação de direção, não de aparecer. Padrão Entrelaços para links inline e nav-items.
<a class="fx-underline">Link</a>
.fx-underline { position: relative; display: inline-block; padding-bottom: 4px; } .fx-underline::after { content: ''; position: absolute; left: 0; bottom: 0; width: 100%; height: 1px; background: #A878EC; transform: scaleX(0); transform-origin: right; transition: transform 450ms cubic-bezier(0.22, 1, 0.36, 1); } .fx-underline:hover::after { transform: scaleX(1); transform-origin: left; }
Feedback tátil em CTAs. scale(0.96) em 150ms com curva spring. Faltar isso é o que separa um botão real de um botão SaaS.
.fx-press { transition: transform 150ms cubic-bezier(0.34, 1.56, 0.64, 1); } .fx-press:active { transform: scale(0.96); }
Card inclina-se no eixo X/Y conforme o mouse. Máximo 8° — mais que isso vira gimmick. Use em cards-destaque (membro, manifesto, oferta), nunca em grids inteiros.
Travessia
Mova o mouse sobre este cartão para ver o efeito 3D Entrelaços.
<div class="fx-tilt" data-tilt="8">...</div>
.fx-tilt { transition: transform 200ms cubic-bezier(0.22, 1, 0.36, 1); transform-style: preserve-3d; will-change: transform; }
document.querySelectorAll('[data-tilt]').forEach(card => { const max = parseFloat(card.dataset.tilt) || 8; card.addEventListener('mousemove', e => { const r = card.getBoundingClientRect(); const px = (e.clientX - r.left) / r.width - 0.5; const py = (e.clientY - r.top) / r.height - 0.5; card.style.transform = `perspective(900px) rotateY(${px*max}deg) rotateX(${-py*max}deg)`; }); card.addEventListener('mouseleave', () => { card.style.transform = 'perspective(900px) rotateY(0) rotateX(0)'; }); });
Onda que nasce no ponto de clique e se expande. Material Design reaproveitado com tokens Entrelaços. Bom em CTAs principais — reforça "algo aconteceu".
<button class="fx-ripple-host" data-ripple>Clique</button>
.fx-ripple-host { position: relative; overflow: hidden; isolation: isolate; } .fx-ripple-wave { position: absolute; border-radius: 50%; background: rgba(255,255,255,0.35); transform: scale(0); animation: fx-ripple 700ms cubic-bezier(0.22, 1, 0.36, 1) forwards; pointer-events: none; } @keyframes fx-ripple { to { transform: scale(1); opacity: 0; } }
document.querySelectorAll('[data-ripple]').forEach(btn => { btn.addEventListener('click', e => { const r = btn.getBoundingClientRect(); const ripple = document.createElement('span'); ripple.className = 'fx-ripple-wave'; const size = Math.max(r.width, r.height) * 2; ripple.style.width = ripple.style.height = size + 'px'; ripple.style.left = (e.clientX - r.left - size/2) + 'px'; ripple.style.top = (e.clientY - r.top - size/2) + 'px'; btn.appendChild(ripple); setTimeout(() => ripple.remove(), 700); }); });
Headline revela letra por letra. Use uma vez por página — no hero ou no manifesto. Repetir tira o impacto.
A travessia começa aqui.
<h2 data-split>A travessia começa aqui.</h2>
.fx-split-char { display: inline-block; opacity: 0; transform: translateY(0.4em); transition: opacity 600ms cubic-bezier(0.16, 1, 0.3, 1), transform 600ms cubic-bezier(0.16, 1, 0.3, 1); } [data-split].is-revealed .fx-split-char { opacity: 1; transform: translateY(0); }
document.querySelectorAll('[data-split]').forEach(el => { const text = el.textContent; el.textContent = ''; [...text].forEach((ch, i) => { const span = document.createElement('span'); span.className = 'fx-split-char'; span.textContent = ch === ' ' ? '\u00A0' : ch; span.style.transitionDelay = (i * 25) + 'ms'; el.appendChild(span); }); const io = new IntersectionObserver(entries => { entries.forEach(e => { if (e.isIntersecting) { e.target.classList.add('is-revealed'); io.unobserve(e.target); } }); }, { threshold: 0.5 }); io.observe(el); });
Imagem cresce lentamente no hover dentro de um container com overflow: hidden. 1200ms é proposital — sensação de cinema, não de SaaS.
<div class="fx-zoom"> <img class="fx-zoom-img" src="foto.jpg"> <span class="fx-zoom-cap">Legenda</span> </div>
.fx-zoom { overflow: hidden; position: relative; } .fx-zoom-img { transition: transform 1200ms cubic-bezier(0.22, 1, 0.36, 1); } .fx-zoom:hover .fx-zoom-img { transform: scale(1.12); }
Barra fina (3px) no topo da tela mostrando progresso da leitura. Útil em LPs longas e artigos. Demo abaixo mostra a barra com valor sintético — em produção é fixed top-0.
<div class="fx-progress"> <div class="fx-progress-bar" data-scroll-progress></div> </div>
.fx-progress { position: fixed; top: 0; left: 0; right: 0; height: 3px; background: rgba(180,140,255,0.10); z-index: 200; } .fx-progress-bar { height: 100%; background: linear-gradient(90deg, #7427d4, #ff8a1f); transform-origin: left; transform: scaleX(0); transition: transform 90ms linear; }
const bar = document.querySelector('[data-scroll-progress]'); const update = () => { const h = document.documentElement; const p = h.scrollTop / (h.scrollHeight - h.clientHeight); bar.style.transform = `scaleX(${p})`; }; document.addEventListener('scroll', update, { passive: true }); update();
Elementos se movem em velocidades diferentes durante o scroll. Use em backgrounds decorativos (uma palavra serif gigante atrás, por exemplo) — nunca em texto-âncora.
<div data-parallax="0.4">Camada lenta</div>
const els = document.querySelectorAll('[data-parallax]'); const update = () => { els.forEach(el => { const speed = parseFloat(el.dataset.parallax) || 0.3; const r = el.getBoundingClientRect(); const mid = window.innerHeight / 2; const offset = (r.top + r.height/2 - mid) * -speed; el.style.transform = `translateY(${offset}px)`; }); }; document.addEventListener('scroll', update, { passive: true }); update();
Ponto pulsante para indicar status ao vivo, vagas abertas, urgência. Dois ::before/::after defasados criam ondas concêntricas.
<span class="fx-pulse"></span> <span>Ao vivo</span>
.fx-pulse { display: inline-block; width: 18px; height: 18px; border-radius: 50%; background: #7427d4; position: relative; } .fx-pulse::before, .fx-pulse::after { content: ''; position: absolute; inset: 0; border-radius: 50%; border: 1px solid #A878EC; animation: fx-pulse-out 2.4s cubic-bezier(0.16, 1, 0.3, 1) infinite; } .fx-pulse::after { animation-delay: 1.2s; } @keyframes fx-pulse-out { 0% { transform: scale(1); opacity: 1; } 100% { transform: scale(2.6); opacity: 0; } }
As 4 bordas se desenham em sequência no hover, dando sensação de "construindo". Caprichoso para CTAs ghost ou cards-âncora especiais. Não use mais que 1 ou 2 por página.
<button class="fx-border-draw"> Texto do botão <span class="fx-bd-r"></span> <span class="fx-bd-b"></span> </button>
.fx-border-draw { position: relative; padding: 14px 24px; background: transparent; border: 0; cursor: pointer; } .fx-border-draw::before { content: ''; position: absolute; left: 0; right: 0; top: 0; height: 1px; background: #A878EC; transform: scaleX(0); transform-origin: left; transition: transform 450ms cubic-bezier(0.22, 1, 0.36, 1); } .fx-bd-r { right: 0; top: 0; width: 1px; height: 100%; transform: scaleY(0); transform-origin: top; } .fx-bd-b { left: 0; bottom: 0; right: 0; height: 1px; transform: scaleX(0); transform-origin: right; } /* + ::after na borda left, com cascata de transition-delay */ .fx-border-draw:hover::before { transform: scaleX(1); } .fx-border-draw:hover .fx-bd-r { transform: scaleY(1); transition-delay: 250ms; } .fx-border-draw:hover .fx-bd-b { transform: scaleX(1); transition-delay: 500ms; transform-origin: left; }
Princípios de uso
- Um efeito-protagonista por dobra. Reveal e stagger são "respiros" — podem se repetir. Magnetic, tilt, split text, border-draw são protagonistas — só um por dobra.
- Curva única. Tudo respira em
cubic-bezier(0.22, 1, 0.36, 1)(default) ou(0.16, 1, 0.3, 1)(reveals). Misturar curvas quebra a coerência sensorial. - Cor da depth. Sombras sempre roxas/laranjas — nunca preto sólido.
rgba(116,39,212,0.30)é o ponto de partida. - prefers-reduced-motion sempre respeitado. Marquee, parallax, shimmer e pulse devem desativar. O CSS no fim de
motion-fx.cssmostra como. - Performance. Use
transformeopacity— evite animarwidth,left,background-color.