(null);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => { if (entry.isIntersecting) setIsVisible(true); },
{ threshold: 0.2 }
);
if (sectionRef.current) observer.observe(sectionRef.current);
return () => observer.disconnect();
}, []);
return (
```
**After (Motion):**
```typescript
const shouldReduceMotion = useReducedMotion();
return (
```
## Validation Checklist
When implementing animations:
- [ ] Uses `useReducedMotion()` for accessibility
- [ ] Passes `undefined` to variants/initial/transition when reduced motion preferred
- [ ] Uses shared variants from `variants.ts` where applicable
- [ ] Uses `viewport={{ once: true }}` for scroll-triggered animations
- [ ] Keeps hover states as CSS transitions (not Motion)
- [ ] Uses CSS keyframes for infinite/background animations
## Related Files
- `apps/web/CLAUDE.md` - Web app conventions
- `apps/web/src/app/about/_components/variants.ts` - Animation variants
- `apps/web/src/components/animated-number.tsx` - Number animation component