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. |
4 curvas de easing padronizadas. 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. Padrão para scroll entrance e montagem de página.
// fadeInUp
initial: { opacity: 0, y: 16 }
animate: { opacity: 1, y: 0 }
transition: {
duration: 0.4,
ease: [0, 0, 0.2, 1] // Decelerate
}Elemento sai para baixo com fade. Usado ao desmontar ou navegar.
// fadeOutDown
animate: { opacity: 0, y: 16 }
transition: {
duration: 0.25,
ease: [0.4, 0, 1, 1] // Accelerate
}Transição de estado (hover, active, disabled). Feedback imediato sem distração.
// State Change
transition: {
duration: 0.15,
ease: [0.4, 0, 0.2, 1] // Smooth
}
// CSS equivalent
.element {
transition: all 150ms cubic-bezier(0.4, 0, 0.2, 1);
}Elementos revelados ao entrar na viewport. Usa IntersectionObserver com once: true.
// Scroll Entrance (Framer Motion)
const ref = useRef(null);
const isInView = useInView(ref, {
once: true,
margin: "-20% 0px"
});
<motion.div
ref={ref}
initial={{ opacity: 0, y: 16 }}
animate={isInView
? { opacity: 1, y: 0 }
: { opacity: 0, y: 16 }
}
transition={{
duration: 0.4,
ease: [0, 0, 0.2, 1]
}}
/>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)"
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)"
duration:
fast: "150ms"
normal: "250ms"
slow: "400ms"
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 */
--ease-smooth: cubic-bezier(0.4, 0, 0.2, 1);
--ease-decelerate: cubic-bezier(0, 0, 0.2, 1);
--ease-accelerate: cubic-bezier(0.4, 0, 1, 1);
--ease-emphasis: cubic-bezier(0.34, 1.56, 0.64, 1);
/* Duration */
--duration-fast: 150ms;
--duration-normal: 250ms;
--duration-slow: 400ms;
}
/* Uso */
.button {
transition: all var(--duration-fast) var(--ease-smooth);
}
.modal {
transition: transform var(--duration-slow) var(--ease-decelerate),
opacity var(--duration-slow) var(--ease-decelerate);
}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>