Documentation Index Fetch the complete documentation index at: https://mintlify.com/ThalysonRibeiro/my-portfolio-fullstack/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Thalyson’s Portfolio uses Framer Motion (v12.17.0) to create smooth, performant animations throughout the interface. The animation system includes:
Page entrance animations with stagger effects
Scroll-triggered animations via whileInView
Hover and interaction animations for enhanced UX
Spring physics for natural motion
Performance optimizations with will-change and reduced motion support
All animated components are client components ("use client") since Framer Motion requires browser APIs.
Installation
{
"dependencies" : {
"framer-motion" : "^12.17.0"
}
}
Import in components:
import { motion } from "framer-motion" ;
Core Animation Patterns
1. Container + Item Stagger
The most common pattern: parent container staggers child animations.
Hero Component (src/components/hero.tsx)
About Component (src/components/about.tsx)
"use client" ;
import { motion } from "framer-motion" ;
const ANIMATION_CONFIG = {
container: {
hidden: { opacity: 0 },
visible: {
opacity: 1 ,
transition: {
staggerChildren: 0.2 , // 200ms delay between children
delayChildren: 0.1 // Start after 100ms
}
}
},
item: {
hidden: { opacity: 0 , y: 50 },
visible: {
opacity: 1 ,
y: 0 ,
transition: {
type: "spring" ,
damping: 20 , // Less bouncy
stiffness: 100 , // Spring speed
duration: 0.8
}
}
}
};
export function Hero () {
return (
< motion.div
variants = { ANIMATION_CONFIG . container }
initial = "hidden"
animate = "visible"
>
< motion.h1 variants = { ANIMATION_CONFIG . item } >
Title
</ motion.h1 >
< motion.p variants = { ANIMATION_CONFIG . item } >
Description
</ motion.p >
< motion.button variants = { ANIMATION_CONFIG . item } >
CTA
</ motion.button >
</ motion.div >
);
}
Key properties:
staggerChildren: Delay between each child animation (in seconds)
delayChildren: Initial delay before children start animating
variants: Reusable animation states shared between parent and children
Animate elements when they enter the viewport using whileInView:
< motion.div
initial = "hidden"
whileInView = "visible"
viewport = { {
once: true , // Animate only once (don't re-trigger)
margin: "-100px" // Trigger 100px before element enters viewport
} }
variants = { fadeUp }
>
Content animates when scrolled into view
</ motion.div >
Viewport options:
once: true - Prevents re-animation on every scroll
margin: "-100px" - Adjusts trigger point (negative = earlier, positive = later)
amount: 0.5 - Trigger when 50% of element is visible
3. Hover Animations
Interactive hover effects for cards and buttons:
< motion.div
variants = { fadeUp }
whileHover = { {
y: - 4 , // Lift 4px
scale: 1.02 // Slight scale increase
} }
className = "bg-card p-6 cursor-pointer"
>
Hover me!
</ motion.div >
Common hover patterns:
Lift Effect
Scale Effect
Rotate Effect
< motion.div whileHover = { { y: - 8 , scale: 1.02 } } >
Card
</ motion.div >
4. Spring Physics
Spring animations create natural, physics-based motion:
const springConfig = {
type: "spring" ,
damping: 20 , // Higher = less bouncy (0-100)
stiffness: 100 , // Higher = faster (0-500)
mass: 1 // Higher = heavier, slower (default: 1)
};
< motion.div
initial = { { scale: 0 } }
animate = { { scale: 1 } }
transition = { springConfig }
>
Bouncy element
</ motion.div >
Spring parameters:
damping : Controls bounciness (20-30 for subtle, 10 for bouncy)
stiffness : Controls speed (100 for smooth, 300 for snappy)
mass : Controls inertia (rarely needs adjustment)
5. Title Scale Animation
Special effect for hero titles:
const title = {
hidden: { opacity: 0 , scale: 0.8 },
visible: {
opacity: 1 ,
scale: 1 ,
transition: {
type: "spring" ,
damping: 15 ,
stiffness: 100 ,
duration: 1
}
}
};
< motion.h1 variants = { title } >
Scaling Title
</ motion.h1 >
Animation Recipes
Fade Up (Most Common)
const fadeUp = {
hidden: { opacity: 0 , y: 20 },
visible: {
opacity: 1 ,
y: 0 ,
transition: { duration: 0.5 , ease: "easeOut" }
}
};
Use for: Text blocks, cards, sections
Fade In (Simple)
const fadeIn = {
hidden: { opacity: 0 },
visible: {
opacity: 1 ,
transition: { duration: 0.4 }
}
};
Use for: Images, overlays, backgrounds
Scale In
const scaleIn = {
hidden: { opacity: 0 , scale: 0.95 },
visible: {
opacity: 1 ,
scale: 1 ,
transition: { duration: 0.5 , ease: "easeOut" }
}
};
Use for: Modal dialogs, profile images, featured content
Slide In (Horizontal)
const slideInLeft = {
hidden: { opacity: 0 , x: - 50 },
visible: {
opacity: 1 ,
x: 0 ,
transition: { type: "spring" , damping: 20 , stiffness: 100 }
}
};
Use for: Sidebar navigation, slide-out panels
Stagger List Items
const list = {
visible: { transition: { staggerChildren: 0.1 } }
};
const item = {
hidden: { opacity: 0 , y: 10 },
visible: { opacity: 1 , y: 0 }
};
< motion.ul variants = { list } initial = "hidden" animate = "visible" >
{ items . map ( item => (
< motion.li key = { item . id } variants = { item } >
{ item . text }
</ motion.li >
)) }
</ motion.ul >
CSS Animations (Non-Framer)
Some animations use pure CSS for better performance:
Gradient Animation
@layer utilities {
.animate-gradient {
animation : gradient 8 s linear infinite ;
}
@keyframes gradient {
0% { background-position : 0 % 50 % ; }
50% { background-position : 100 % 50 % ; }
100% { background-position : 0 % 50 % ; }
}
}
< h1 className = "bg-gradient-to-r from-white via-primary to-white bg-[length:200%_auto] bg-clip-text text-transparent animate-gradient" >
Animated Gradient Text
</ h1 >
Shine Effect (Hover)
@layer utilities {
.animate-shine {
animation : shine 1.5 s ease-in-out infinite ;
}
@keyframes shine {
from { transform : translateX ( -200 % ); }
to { transform : translateX ( 200 % ); }
}
}
< button className = "relative group overflow-hidden" >
< div className = "absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent -translate-x-[200%] group-hover:animate-shine pointer-events-none" />
< span > Hover for Shine </ span >
</ button >
Animated Number Counter
src/components/animated-number.tsx
"use client" ;
import { useEffect , useState } from "react" ;
export function AnimatedNumber ({ value , simbol = "+" }) {
const [ count , setCount ] = useState ( 0 );
useEffect (() => {
let start = 0 ;
const end = value ;
const duration = 2000 ; // 2 seconds
const increment = end / ( duration / 16 ); // 60fps
const timer = setInterval (() => {
start += increment ;
if ( start >= end ) {
setCount ( end );
clearInterval ( timer );
} else {
setCount ( Math . floor ( start ));
}
}, 16 );
return () => clearInterval ( timer );
}, [ value ]);
return < span className = "text-2xl font-bold" > { count }{ simbol } </ span > ;
}
Usage:
< AnimatedNumber value = { 5000 } simbol = "+" />
SVG Path Animations
The animated background uses SVG animateMotion:
src/components/animatedBackground.tsx
< svg viewBox = "0 0 602 602" >
< ellipse cx = "295" cy = "193" rx = "1.07" ry = "1.07" fill = "#ef4444" >
< animateMotion dur = "10s" repeatCount = "indefinite" rotate = "auto" >
< mpath xlinkHref = "#path_2" />
</ animateMotion >
</ ellipse >
< path d = "M294.685 193.474L268.932 219.258" stroke = "url(#paint3_linear)" >
< animateMotion dur = "10s" repeatCount = "indefinite" rotate = "auto" >
< mpath xlinkHref = "#path_2" />
</ animateMotion >
</ path >
</ svg >
Key attributes:
dur: Animation duration
repeatCount="indefinite": Loops forever
rotate="auto": Element rotates to follow path
<mpath xlinkHref="#path_2" />: References path to follow
Use will-change Sparingly
Framer Motion automatically applies will-change for animated properties. Avoid manually adding it:
/* Avoid overuse */
.element {
will-change : transform, opacity; /* Can hurt performance */
}
These properties are GPU-accelerated and performant:
// ✅ Performant
< motion.div animate = { { x: 100 , opacity: 0.5 } } />
// ❌ Avoid (causes reflows)
< motion.div animate = { { width: 300 , height: 200 } } />
Use layout Prop for Layout Animations
When animating size or position changes:
< motion.div layout >
Content that changes size smoothly
</ motion.div >
Framer Motion uses FLIP technique for performant layout animations.
Reduce Motion for Accessibility
Respect user preferences for reduced motion:
import { useReducedMotion } from "framer-motion" ;
function Component () {
const shouldReduceMotion = useReducedMotion ();
return (
< motion.div
initial = { { opacity: 0 , y: shouldReduceMotion ? 0 : 50 } }
animate = { { opacity: 1 , y: 0 } }
/>
);
}
Or use CSS:
@media (prefers-reduced-motion: reduce) {
* {
animation-duration : 0.01 ms !important ;
animation-iteration-count : 1 !important ;
transition-duration : 0.01 ms !important ;
}
}
Lazy Load Animations
For off-screen content, use whileInView with once: true:
< motion.div
initial = "hidden"
whileInView = "visible"
viewport = { { once: true } } // Only animate on first view
>
Content
</ motion.div >
Timing Functions (Easing)
Framer Motion supports multiple easing functions:
// Linear (constant speed)
transition = {{ ease : "linear" , duration : 1 }}
// Ease Out (fast start, slow end) - Best for entrances
transition = {{ ease : "easeOut" , duration : 0.5 }}
// Ease In (slow start, fast end) - Best for exits
transition = {{ ease : "easeIn" , duration : 0.3 }}
// Ease In-Out (slow start and end) - General purpose
transition = {{ ease : "easeInOut" , duration : 0.6 }}
// Custom cubic-bezier
transition = {{ ease : [ 0.43 , 0.13 , 0.23 , 0.96 ], duration : 0.8 }}
Recommended defaults:
Entrances: easeOut with 0.5s duration
Exits: easeIn with 0.3s duration
Interactions: easeInOut with 0.2s duration
Custom Animation Hooks
useInView Hook
Track when an element enters the viewport:
import { useInView } from "framer-motion" ;
import { useRef } from "react" ;
function Component () {
const ref = useRef ( null );
const isInView = useInView ( ref , { once: true , margin: "-100px" });
return (
< div ref = { ref } style = { { opacity: isInView ? 1 : 0 } } >
Content
</ div >
);
}
useAnimation Hook
Programmatic control over animations:
import { motion , useAnimation } from "framer-motion" ;
import { useEffect } from "react" ;
function Component () {
const controls = useAnimation ();
useEffect (() => {
controls . start ({
opacity: 1 ,
y: 0 ,
transition: { duration: 0.5 }
});
}, [ controls ]);
return (
< motion.div initial = { { opacity: 0 , y: 20 } } animate = { controls } >
Content
</ motion.div >
);
}
Animation Best Practices
Don’t animate on every render. Use initial and animate props instead of inline styles that change on state updates.
DO ✅
// Efficient: Framer Motion handles optimization
< motion.div
initial = { { opacity: 0 } }
animate = { { opacity: 1 } }
>
Content
</ motion.div >
DON’T ❌
// Inefficient: Triggers animation on every render
< motion.div style = { { opacity: isVisible ? 1 : 0 } } >
Content
</ motion.div >
Keep Animations Subtle
Duration: 0.3-0.6s for most interactions
Distance: Move 10-30px for fade-ups
Scale: 1.02-1.05 for hover effects
Stagger: 0.05-0.2s between items
Too aggressive:
// ❌ Over-the-top
< motion.div whileHover = { { scale: 1.5 , rotate: 360 } } >
Subtle and professional:
// ✅ Subtle enhancement
< motion.div whileHover = { { scale: 1.02 , y: - 4 } } >
Use Consistent Timing
Define animation constants at the top of files:
const ANIMATION_CONFIG = {
container: {
hidden: { opacity: 0 },
visible: {
opacity: 1 ,
transition: { staggerChildren: 0.2 , delayChildren: 0.1 }
}
},
item: {
hidden: { opacity: 0 , y: 50 },
visible: {
opacity: 1 ,
y: 0 ,
transition: { type: "spring" , damping: 20 , stiffness: 100 }
}
}
} as const ;
Benefits:
Easy to adjust all animations at once
TypeScript autocomplete for variant names
Reusable across components
Avoid Animating Width/Height
Animating dimensions causes layout recalculation:
// ❌ Causes reflow
< motion.div animate = { { width: 300 } } />
// ✅ Use scale instead
< motion.div animate = { { scaleX: 1.5 } } />
Optimize for Mobile
Mobile devices have less GPU power. Reduce complexity:
import { useMediaQuery } from "@/hooks/useMediaQuery" ;
function Component () {
const isMobile = useMediaQuery ( "(max-width: 768px)" );
return (
< motion.div
animate = { { y: isMobile ? 0 : 100 } } // Disable on mobile
transition = { { duration: isMobile ? 0 : 0.5 } }
>
Content
</ motion.div >
);
}
Debugging Animations
Visualize Animation State
Use React DevTools with Framer Motion:
Install Framer Motion DevTools
Add to app:
import { MotionDevTools } from "framer-motion-devtools" ;
function App () {
return (
<>
< MotionDevTools />
< Component />
</>
);
}
Log Animation Events
< motion.div
animate = { { opacity: 1 } }
onAnimationStart = { () => console . log ( "Animation started" ) }
onAnimationComplete = { () => console . log ( "Animation completed" ) }
>
Content
</ motion.div >
Common Issues
Animations Not Running
Cause: Using Framer Motion in Server Components
Fix: Add "use client" directive:
"use client" ;
import { motion } from "framer-motion" ;
Janky Animations
Cause: Animating expensive properties (width, height, top, left)
Fix: Use transform properties (x, y, scale):
// ❌ Janky
< motion.div animate = { { left: 100 } } />
// ✅ Smooth
< motion.div animate = { { x: 100 } } />
Cause: Missing viewport={{ once: true }}
Fix:
< motion.div
whileInView = "visible"
viewport = { { once: true } } // Add this
>
Next Steps
Styling System Customize colors and design tokens
Content Management Update portfolio content