Motion
Animações que fazem seu site parecer vivo, não parado. Cada movimento guia o olho do visitante para onde importa. Aqui estão todas as regras.
5 princípios que guiam toda animação na TLX. Nada se mexe sem motivo. Cada transição existe para mostrar algo ou facilitar a navegação.
| # | Princípio | Descrição |
|---|---|---|
| 01 | Intencional | Toda animação tem propósito funcional ou comunicativo. Nenhum movimento é decorativo sem razão. |
| 02 | Rápido | Feedback imediato. Durações curtas (150-400ms) para manter a interface responsiva e profissional. |
| 03 | Suave | Curvas de easing naturais. Sem movimentos lineares ou abruptos. Tudo flui organicamente. |
| 04 | Coerente | Mesmos tokens em toda plataforma. Uma curva, uma duração, um padrão. Consistência absoluta. |
| 05 | Acessível | Respeita prefers-reduced-motion. Animações nunca são o único canal de informação. |
8 curvas de easing padronizadas (4 core + 4 Awwwards). Clique em cada card para ver a animação em ação.
3 níveis de duração. Clique para visualizar o tempo relativo de cada token.
Padrões de animação reutilizáveis. Composições das curvas e durações para cenários comuns.
Elemento entra de baixo com fade. Easing spring premium extraido do CERNE Studio.
// fadeInUp (upgraded — CERNE spring)
initial: { opacity: 0, y: 32 }
animate: { opacity: 1, y: 0 }
transition: {
duration: 0.8,
ease: [0.16, 1, 0.3, 1] // Spring
}Texto aparece palavra por palavra, subindo de tras de um mask. Extraido do CERNE Studio.
// clipReveal — text reveal word-by-word
<span className="overflow-hidden pb-[0.15em]">
<motion.span
initial={{ y: "120%" }}
animate={{ y: "0%" }}
transition={{
duration: 0.8,
delay: index * 0.06,
ease: [0.16, 1, 0.3, 1] // Spring
}}
>
{word}
</motion.span>
</span>Cards/items entram com delay escalonado. Delays extraidos do Koto (83ms, 133ms, 167ms).
// staggerChildren — Koto pattern
// CSS utility class:
.stagger-children > *:nth-child(1) { animation-delay: 0ms; }
.stagger-children > *:nth-child(2) { animation-delay: 83ms; }
.stagger-children > *:nth-child(3) { animation-delay: 133ms; }
.stagger-children > *:nth-child(4) { animation-delay: 167ms; }
.stagger-children > *:nth-child(5) { animation-delay: 200ms; }
// Framer Motion:
staggerContainer: {
visible: { transition: { staggerChildren: 0.083 } }
}Linha se desenha da esquerda para direita. Divider animado extraido do CERNE Studio.
// drawLine — CERNE pattern
@keyframes draw-line {
from { transform-origin: left; transform: scaleX(0); }
to { transform-origin: left; transform: scaleX(1); }
}
// Framer Motion:
initial: { scaleX: 0, transformOrigin: "left" }
animate: { scaleX: 1 }
transition: { duration: 1.2, ease: [0.16, 1, 0.3, 1] }Elemento sai para baixo com fade. Saida rapida extraida do Koto.
// fadeOutDown (upgraded — Koto exit)
animate: { opacity: 0, y: 16 }
transition: {
duration: 0.25,
ease: [0.7, 0, 1, 1] // Quick exit
}Card escala suavemente no hover. Easing spring de 900ms extraido do CERNE Studio.
// cardHover — CERNE pattern
.card-hover {
transition: transform 0.9s cubic-bezier(0.16, 1, 0.3, 1);
}
.card-hover:hover {
transform: scale(1.03);
}
.card-hover:hover img {
transform: scale(1.05);
}Underline desliza da esquerda no hover, sai pela direita. Extraido do Koto.
// underlineSlide — Koto pattern
.link-underline {
position: relative;
overflow: hidden;
}
.link-underline::after {
content: '';
position: absolute;
bottom: 0; left: 0;
width: 100%; height: 1px;
background: currentColor;
transform: translateX(-100%);
transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
.link-underline:hover::after {
transform: translateX(0);
}Elementos revelados ao entrar na viewport. Usa IntersectionObserver com once: true.
// Scroll Entrance (upgraded)
const ref = useRef(null);
const isInView = useInView(ref, {
once: true,
margin: "-10% 0px"
});
<motion.div
ref={ref}
initial={{ opacity: 0, y: 32 }}
animate={isInView
? { opacity: 1, y: 0 }
: { opacity: 0, y: 32 }
}
transition={{
duration: 0.8,
ease: [0.16, 1, 0.3, 1] // Spring
}}
/>Respeitar preferências do usuário. Toda animação deve ser desativável via prefers-reduced-motion.
/* CSS — Desativar animações para usuários que preferem */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/* Framer Motion — Hook */
import { useReducedMotion } from "framer-motion";
function Component() {
const shouldReduce = useReducedMotion();
return (
<motion.div
animate={{ opacity: 1, y: shouldReduce ? 0 : 16 }}
transition={{
duration: shouldReduce ? 0 : 0.4
}}
/>
);
}Checklist
- Animações decorativas desativadas com prefers-reduced-motion
- Transições de estado mantidas (opacity ok, transform removido)
- Nenhuma informação transmitida exclusivamente por animação
- Durações nunca excedem 400ms para feedback de interação
8 animações oficiais do logo TLX. Cada uma projetada para um contexto de uso específico.
Structural Reveal
1.2s
Logo se constrói letra por letra, simulando o processo de construção da marca. Stroke path animation com fill progressivo.
Teal Pulse
2.0s
Logo pulsa com glow teal suave em loop. Indica processamento ou carregamento ativo.
Data Stream
1.5s
Partículas de dados fluem através das letras do logo, representando o fluxo de informação e análise.
Blueprint Grid
1.8s
Logo aparece sobre grid blueprint que fade out, referenciando o aspecto projetual e construtivo da marca.
Layer Reveal
1.0s
Camadas do logo se empilham de baixo para cima com stagger. Comunica profundidade e construção em camadas.
Metric Counter
1.5s
Letras do logo se comportam como contadores numéricos antes de resolver na forma final. Conecta marca e métricas.
Stagger Letters
0.8s
Cada letra entra com stagger de 50ms usando curva Decelerate. Simples, elegante, rápido.
Convergence
1.2s
Letras convergem de posições dispersas para a posição final. Comunica unificação e resolução.
Tokens YAML completos do motion system. Source of truth para todas as plataformas.
motion:
easing:
smooth: "cubic-bezier(0.4, 0, 0.2, 1)" # Default
decelerate: "cubic-bezier(0, 0, 0.2, 1)" # Entrada
accelerate: "cubic-bezier(0.4, 0, 1, 1)" # Saida
emphasis: "cubic-bezier(0.34, 1.56, 0.64, 1)" # Bounce
spring: "cubic-bezier(0.16, 1, 0.3, 1)" # Premium (CERNE)
overshoot: "cubic-bezier(0.23, 1, 0.32, 1)" # Buttons (Digital Present)
in-decisive: "cubic-bezier(0, 0, 0, 1)" # Entrada decisiva (Koto)
out-quick: "cubic-bezier(0.7, 0, 1, 1)" # Saida rapida (Koto)
duration:
fast: "150ms"
normal: "300ms"
slow: "500ms"
reveal: "1000ms" # Scroll reveals, text animations
marquee: "30s" # Infinite ticker
patterns:
fadeInUp:
initial: { opacity: 0, y: 16 }
animate: { opacity: 1, y: 0 }
easing: decelerate
duration: slow
fadeOutDown:
animate: { opacity: 0, y: 16 }
easing: accelerate
duration: normal
stateChange:
easing: smooth
duration: fast
scrollEntrance:
initial: { opacity: 0, y: 16 }
animate: { opacity: 1, y: 0 }
easing: decelerate
duration: slow
trigger: viewport
once: true
logo:
structural-reveal: { duration: "1.2s", loop: false }
teal-pulse: { duration: "2.0s", loop: true }
data-stream: { duration: "1.5s", loop: false }
blueprint-grid: { duration: "1.8s", loop: false }
layer-reveal: { duration: "1.0s", loop: false }
metric-counter: { duration: "1.5s", loop: false }
stagger-letters: { duration: "0.8s", loop: false }
convergence: { duration: "1.2s", loop: false }
a11y:
reduced-motion: "remove transform, keep opacity"
max-feedback-duration: "400ms"
no-animation-only-info: trueReferência de implementação com Framer Motion. Padrões prontos para copiar e usar.
Framer Motion: Variantes base
import { motion, type Variants } from "framer-motion";
const fadeInUp: Variants = {
hidden: { opacity: 0, y: 16 },
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.4,
ease: [0, 0, 0.2, 1], // Decelerate
},
},
};
<motion.div
variants={fadeInUp}
initial="hidden"
animate="visible"
>
{children}
</motion.div>Framer Motion: Stagger children
const staggerContainer: Variants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.05,
delayChildren: 0.1,
},
},
};
const staggerItem: Variants = {
hidden: { opacity: 0, y: 12 },
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.4,
ease: [0, 0, 0.2, 1],
},
},
};
<motion.ul variants={staggerContainer} initial="hidden" animate="visible">
{items.map((item) => (
<motion.li key={item.id} variants={staggerItem}>
{item.content}
</motion.li>
))}
</motion.ul>CSS Custom Properties
:root {
/* Easing — base */
--ease-smooth: cubic-bezier(0.4, 0, 0.2, 1);
--ease-decel: cubic-bezier(0, 0, 0.2, 1);
--ease-accel: cubic-bezier(0.4, 0, 1, 1);
--ease-emphasis: cubic-bezier(0.34, 1.56, 0.64, 1);
/* Easing — Awwwards premium */
--ease-spring: cubic-bezier(0.16, 1, 0.3, 1); /* CERNE */
--ease-overshoot: cubic-bezier(0.23, 1, 0.32, 1); /* Digital Present */
--ease-in-decisive: cubic-bezier(0, 0, 0, 1); /* Koto */
--ease-out-quick: cubic-bezier(0.7, 0, 1, 1); /* Koto */
/* Duration */
--duration-fast: 150ms;
--duration-normal: 300ms;
--duration-slow: 500ms;
--duration-reveal: 1000ms;
/* Blur */
--blur-glass: 16px;
--blur-decorative: 50px;
}
/* Uso premium */
.card-hover {
transition: transform 0.9s var(--ease-spring);
}
.card-hover:hover { transform: scale(1.03); }Tailwind: Classes utilitárias
/* tailwind.config.js */
theme: {
extend: {
transitionTimingFunction: {
smooth: "cubic-bezier(0.4, 0, 0.2, 1)",
decelerate: "cubic-bezier(0, 0, 0.2, 1)",
accelerate: "cubic-bezier(0.4, 0, 1, 1)",
emphasis: "cubic-bezier(0.34, 1.56, 0.64, 1)",
},
transitionDuration: {
fast: "150ms",
normal: "250ms",
slow: "400ms",
},
},
}
/* Uso no JSX */
<button className="transition-all duration-fast ease-smooth">
Hover me
</button>