EntrelaçosPsi v1.0
Bíblia / Tokens / 09+ · Motion FX library
09+Tokens · Biblioteca

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.

Como usar: cada snippet é independente. Cole o CSS no seu <style>, o HTML no markup, e o JS no final do <body>. Tudo herda das variáveis do tokens.css (roxo/laranja/curvas). Se você não tem esses tokens, troque pelos seus hex/curvas.

01 Reveal on scroll · fade + translate CSS+JS

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.

Item 1 — role para revelar
Item 2 — fade + translateY
Item 3 — 800ms ease-out
<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));
02 Stagger children · cascata em série CSS+JS

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.

A
B
C
D
E
F
<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);
});
03 Counter animado · 0 ao alvo JS

Stats que animam quando entram na tela. Curva cubic-out em 1800ms. Aceita decimais e sufixos (%, +, x).

0
Mulheres
0
Retenção
0
Crescimento
0
Cohorts
<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));
04 Marquee infinito · logos / palavras CSS

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.

Confronto fértil Densidade emocional Travessia, não promessa Comunidade primeiro Confronto fértil Densidade emocional Travessia, não promessa Comunidade primeiro
<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%); }
}
05 Hover lift · o card respira CSS

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);
}
06 Magnetic button · segue o cursor CSS+JS

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)'; });
});
07 Cursor spotlight · radial seguindo o mouse CSS+JS

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) + '%');
  });
});
08 Gradient text shimmer · brilho circular CSS

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.

A travessia começa quando você para de fingir.
<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; }
}
09 Underline reveal · direção bidirecional CSS

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; }
10 Button press · o tato do clique CSS

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); }
11 Tilt 3D · cartão respondendo CSS+JS

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)';
  });
});
12 Ripple click · onda concêntrica CSS+JS

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);
  });
});
13 Split text · letra por letra CSS+JS

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);
});
14 Image zoom · slow-burn CSS

Imagem cresce lentamente no hover dentro de um container com overflow: hidden. 1200ms é proposital — sensação de cinema, não de SaaS.

Comunidade · 2024
<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); }
15 Scroll progress bar · o quanto falta CSS+JS

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();
16 Parallax simples · camadas profundas JS

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.

Travessia
Comunidade
<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();
17 Pulse / live indicator · "está acontecendo" CSS

Ponto pulsante para indicar status ao vivo, vagas abertas, urgência. Dois ::before/::after defasados criam ondas concêntricas.

Inscrições abertas
<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; }
}
18 Border draw · bordas se desenham CSS

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.css mostra como.
  • Performance. Use transform e opacity — evite animar width, left, background-color.
Anti-padrões: não anime tudo. Não use mais de 3 protagonistas em uma página inteira. Não animate cores roxa→laranja em loop infinito. Não combine parallax + tilt 3D + magnetic na mesma seção — vira parque de diversões.