- Introduced DrawLine component for drawing animations. - Added LazyRevealImage component for lazy loading images with reveal animations. - Implemented TextDiffuseReveal for animated text reveals with staggered effects. - Created TopProgressBar for a dynamic loading indicator. - Added PointCloud mock for testing animations. - Developed useReducedMotion hook to handle user preferences for reduced motion. - Centralized animation configurations in config.ts for easier management. - Defined animation variants in variants.ts for consistent animation effects. - Established AnimationProvider to supply global animation settings. - Created tests for AnimationProvider and useReducedMotion hook. - Added SVG assets for branding and UI elements. - Documented frontend review plan and hero circuit board animation plan.
14 KiB
Design Document: Premium Animations
Overview
Este documento detalha o design técnico para implementar animações premium no portfolio evandro.dev.br, inspiradas no estilo suave e elegante do site on.energy. O sistema de animações será construído sobre Framer Motion e Lenis, mantendo a identidade visual existente (cores, tipografia Monument Extended, paleta --slash #ccff00) enquanto adiciona transições fluidas, scroll-triggered animations, parallax sutil e animações de hover sofisticadas.
Objetivos Principais
- Elevar a experiência visual com animações premium e fluidas
- Manter performance consistente (60fps) em todos os dispositivos
- Garantir acessibilidade completa com suporte a prefers-reduced-motion
- Criar um sistema de animações reutilizável e escalável
- Integrar smooth scroll global com Lenis
- Implementar interações sofisticadas (magnetic buttons, custom cursor)
Princípios de Design
- Performance First: Usar apenas propriedades GPU-accelerated (transform, opacity)
- Accessibility First: Respeitar prefers-reduced-motion em todas as animações
- Progressive Enhancement: Funcionalidade básica sem JavaScript, animações como enhancement
- Consistency: Usar curvas de easing e durações consistentes em todo o sistema
- Subtlety: Animações devem ser elegantes e discretas, não distrativas
Architecture
System Overview
O sistema de animações será organizado em camadas modulares:
Animation System
├── Core Layer (Hooks & Utilities)
│ ├── useScrollTrigger - Detecta elementos no viewport
│ ├── useParallax - Aplica efeitos parallax
│ ├── useReducedMotion - Detecta preferências de acessibilidade
│ ├── useMagneticEffect - Implementa efeito magnético
│ └── animationConfig - Configurações centralizadas
│
├── Component Layer (Wrappers Reutilizáveis)
│ ├── ScrollReveal - Wrapper para scroll-triggered animations
│ ├── StaggerContainer - Container para stagger animations
│ ├── ParallaxElement - Wrapper para parallax effects
│ ├── PageTransition - Transições entre páginas
│ ├── ImageReveal - Reveal animado para imagens
│ ├── TextReveal - Reveal animado para textos
│ └── CustomCursor - Cursor customizado
│
├── Integration Layer (Providers & Context)
│ ├── LenisProvider - Smooth scroll global
│ ├── AnimationProvider - Context para configurações
│ └── CursorProvider - Context para cursor customizado
│
└── Enhancement Layer (Micro-interactions)
├── Card hover effects
├── Button hover effects
├── Form field interactions
└── Loading states
Technology Stack
- Framer Motion 12.23.12: Biblioteca principal de animações
- Lenis 1.3.18: Smooth scroll library
- React 19.1.0: Framework base
- Next.js 15.5.9: App Router para routing
- TypeScript: Type safety
- Tailwind CSS 4: Styling utilities
Integration Points
- App Layout: LenisProvider e AnimationProvider no root layout
- Page Components: PageTransition wrapper em cada página
- Section Components: ScrollReveal wrapper em seções principais
- Card Components: Hover effects e stagger animations
- Form Components: Micro-interactions em campos
- Hero Component: Remoção da fase logo, manutenção de typing animation
Components and Interfaces
Core Hooks
useScrollTrigger
Hook para detectar quando elementos entram no viewport e disparar animações.
interface UseScrollTriggerOptions {
threshold?: number; // Porcentagem do elemento visível (default: 0.2)
once?: boolean; // Executar apenas uma vez (default: true)
margin?: string; // Margin do viewport (default: "0px")
}
interface UseScrollTriggerReturn {
ref: RefObject<HTMLElement>;
isInView: boolean;
entry: IntersectionObserverEntry | null;
}
function useScrollTrigger(options?: UseScrollTriggerOptions): UseScrollTriggerReturn
Implementação: Wrapper sobre useInView do Framer Motion com configurações otimizadas.
useParallax
Hook para aplicar efeitos parallax em elementos durante o scroll.
interface UseParallaxOptions {
speed?: number; // Velocidade do parallax (default: 0.5 = 50% mais lento)
direction?: "vertical" | "horizontal"; // Direção do movimento (default: "vertical")
disabled?: boolean; // Desabilitar parallax (usado com reduced motion)
}
interface UseParallaxReturn {
ref: RefObject<HTMLElement>;
style: MotionStyle;
}
function useParallax(options?: UseParallaxOptions): UseParallaxReturn
Implementação: Usa useScroll e useTransform do Framer Motion para calcular posição baseada no scroll.
useReducedMotion
Hook para detectar preferência de movimento reduzido do usuário.
function useReducedMotion(): boolean
Implementação: Wrapper sobre useReducedMotion do Framer Motion, retorna true se prefers-reduced-motion: reduce está ativo.
useMagneticEffect
Hook para implementar efeito magnético em botões.
interface UseMagneticEffectOptions {
radius?: number; // Raio de influência em pixels (default: 80)
strength?: number; // Força do efeito, max movement em pixels (default: 12)
disabled?: boolean; // Desabilitar efeito (usado com reduced motion)
}
interface UseMagneticEffectReturn {
ref: RefObject<HTMLElement>;
style: MotionStyle;
}
function useMagneticEffect(options?: UseMagneticEffectOptions): UseMagneticEffectReturn
Implementação: Rastreia posição do mouse, calcula distância ao elemento, aplica transform proporcional com spring physics.
Component Wrappers
ScrollReveal
Componente wrapper para aplicar scroll-triggered animations.
interface ScrollRevealProps {
children: React.ReactNode;
direction?: "up" | "down" | "left" | "right"; // Direção do slide (default: "up")
distance?: number; // Distância do movimento em pixels (default: 24)
duration?: number; // Duração da animação em segundos (default: 0.6)
delay?: number; // Delay antes da animação (default: 0)
threshold?: number; // Threshold do viewport (default: 0.2)
once?: boolean; // Executar apenas uma vez (default: true)
className?: string;
}
function ScrollReveal(props: ScrollRevealProps): JSX.Element
Comportamento:
- Normal: fade in + slide na direção especificada com blur inicial
- Reduced Motion: apenas fade in simples, sem movimento ou blur
StaggerContainer
Container para aplicar stagger animations em elementos filhos.
interface StaggerContainerProps {
children: React.ReactNode;
staggerDelay?: number; // Delay entre elementos em segundos (default: 0.08)
initialDelay?: number; // Delay inicial antes do primeiro elemento (default: 0)
className?: string;
}
function StaggerContainer(props: StaggerContainerProps): JSX.Element
Implementação: Usa motion.div com variants para aplicar stagger automático aos filhos.
ParallaxElement
Wrapper para aplicar parallax effect em elementos.
interface ParallaxElementProps {
children: React.ReactNode;
speed?: number; // Velocidade do parallax (default: 0.5)
direction?: "vertical" | "horizontal";
className?: string;
}
function ParallaxElement(props: ParallaxElementProps): JSX.Element
Comportamento:
- Normal: movimento parallax suave
- Reduced Motion: sem movimento, elemento estático
PageTransition
Wrapper para transições entre páginas.
interface PageTransitionProps {
children: React.ReactNode;
}
function PageTransition(props: PageTransitionProps): JSX.Element
Comportamento:
- Normal: fade in de 0.4s com ease-out
- Reduced Motion: fade in de 0.15s
ImageReveal
Componente para reveal animado de imagens.
interface ImageRevealProps {
src: string;
alt: string;
width?: number;
height?: number;
className?: string;
priority?: boolean;
}
function ImageReveal(props: ImageRevealProps): JSX.Element
Comportamento:
- Normal: clip-path reveal + fade in + scale (0.98 → 1) + blur (8px → 0px), duração 0.9s
- Reduced Motion: apenas fade in simples
TextReveal
Componente para reveal animado de textos por palavra.
interface TextRevealProps {
text: string;
as?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "p" | "span"; // Tag HTML (default: "p")
staggerDelay?: number; // Delay entre palavras (default: 0.04)
className?: string;
}
function TextReveal(props: TextRevealProps): JSX.Element
Comportamento:
- Normal: cada palavra com blur (6px → 0px) + scale (0.95 → 1) + clip-path slide horizontal
- Reduced Motion: texto completo com fade in simples
CustomCursor
Componente para cursor customizado.
interface CustomCursorProps {
size?: number; // Tamanho base do cursor (default: 40)
expandedSize?: number; // Tamanho expandido (default: 60)
color?: string; // Cor do cursor (default: "var(--slash)")
}
function CustomCursor(props: CustomCursorProps): JSX.Element
Comportamento:
- Desktop: cursor customizado com smooth delay, expande sobre interativos
- Touch devices: cursor padrão do sistema
- Reduced Motion: cursor padrão do sistema
Providers
LenisProvider
Provider para smooth scroll global.
interface LenisProviderProps {
children: React.ReactNode;
options?: {
duration?: number; // Duração do smooth scroll (default: 1.2)
easing?: number[]; // Curva de easing (default: [0.25, 0.0, 0.35, 1.0])
smoothWheel?: boolean; // Smooth wheel scroll (default: true)
smoothTouch?: boolean; // Smooth touch scroll (default: false)
};
}
function LenisProvider(props: LenisProviderProps): JSX.Element
Implementação: Inicializa Lenis, sincroniza com RAF, fornece instância via context.
AnimationProvider
Provider para configurações globais de animação.
interface AnimationConfig {
reducedMotion: boolean;
easings: {
easeOut: string;
easeIn: string;
spring: { stiffness: number; damping: number };
};
durations: {
fast: number;
normal: number;
slow: number;
};
}
interface AnimationProviderProps {
children: React.ReactNode;
}
function AnimationProvider(props: AnimationProviderProps): JSX.Element
function useAnimationConfig(): AnimationConfig
Implementação: Detecta reduced motion, fornece configurações centralizadas via context.
CursorProvider
Provider para cursor customizado.
interface CursorProviderProps {
children: React.ReactNode;
}
function CursorProvider(props: CursorProviderProps): JSX.Element
function useCursor(): {
setExpanded: (expanded: boolean) => void;
setColor: (color: string) => void;
}
Implementação: Gerencia estado do cursor, fornece métodos para controle via context.
Data Models
Animation Configuration
Configurações centralizadas para todas as animações.
export const animationConfig = {
// Easing curves
easings: {
easeOut: "cubic-bezier(0.16, 1, 0.3, 1)",
easeIn: "cubic-bezier(0.4, 0, 1, 1)",
spring: {
default: { stiffness: 200, damping: 20 },
hover: { stiffness: 260, damping: 20 },
magnetic: { stiffness: 150, damping: 15 },
gentle: { stiffness: 60, damping: 20 },
},
},
// Durations (in seconds)
durations: {
instant: 0.15,
fast: 0.3,
normal: 0.6,
slow: 0.9,
verySlow: 1.2,
},
// Scroll trigger
scrollTrigger: {
threshold: 0.2,
once: true,
margin: "0px",
},
// Stagger
stagger: {
default: 0.08,
fast: 0.04,
slow: 0.12,
reducedMotion: 0.03,
},
// Parallax
parallax: {
speed: 0.5, // 50% mais lento que scroll normal
maxDistance: 100, // pixels
},
// Magnetic effect
magnetic: {
radius: 80, // pixels
strength: 12, // max movement in pixels
},
// Cursor
cursor: {
size: 40,
expandedSize: 60,
followDelay: 0.1,
},
// Reduced motion overrides
reducedMotion: {
duration: 0.15,
stagger: 0.03,
disableParallax: true,
disableMagnetic: true,
disableCursor: true,
},
} as const;
Variant Presets
Presets de variants do Framer Motion para animações comuns.
export const variants = {
// Fade in
fadeIn: {
initial: { opacity: 0 },
animate: { opacity: 1 },
exit: { opacity: 0 },
},
// Fade in + slide up
fadeInUp: {
initial: { opacity: 0, y: 24, filter: "blur(8px)" },
animate: { opacity: 1, y: 0, filter: "blur(0px)" },
exit: { opacity: 0, y: -24, filter: "blur(8px)" },
},
// Fade in + slide down
fadeInDown: {
initial: { opacity: 0, y: -24, filter: "blur(8px)" },
animate: { opacity: 1, y: 0, filter: "blur(0px)" },
exit: { opacity: 0, y: 24, filter: "blur(8px)" },
},
// Fade in + slide left
fadeInLeft: {
initial: { opacity: 0, x: 24, filter: "blur(8px)" },
animate: { opacity: 1, x: 0, filter: "blur(0px)" },
exit: { opacity: 0, x: -24, filter: "blur(8px)" },
},
// Fade in + slide right
fadeInRight: {
initial: { opacity: 0, x: -24, filter: "blur(8px)" },
animate: { opacity: 1, x: 0, filter: "blur(0px)" },
exit: { opacity: 0, x: 24, filter: "blur(8px)" },
},
// Scale in
scaleIn: {
initial: { opacity: 0, scale: 0.95 },
animate: { opacity: 1, scale: 1 },
exit: { opacity: 0, scale: 0.95 },
},
// Image reveal
imageReveal: {
initial: {
opacity: 0,
scale: 0.98,
filter: "blur(8px)",
clipPath: "inset(0 100% 0 0)",
},
animate: {
opacity: 1,
scale: 1,
filter: "blur(0px)",
clipPath: "inset(0 0% 0 0)",
},
},
// Text reveal (per word)
textReveal: {
initial: {
opacity: 0,
scale: 0.95,
filter: "blur(6px)",
clipPath: "inset(0 100% 0 0)",
},
animate: {
opacity: 1,
scale: 1,
filter: "blur(0px)",
clipPath: "inset(0 0% 0 0)",
},
},
// Stagger container
staggerContainer: {
animate: {
transition: {
staggerChildren: 0.08,
},
},
},
// Reduced motion variants
reducedMotion: {
fadeIn: {
initial: { opacity: 0 },
animate: { opacity: 1 },
exit: { opacity: 0 },
},
},
} as const;