Stop_smoking_website_ver2/src/components/CelebrationAnimation.tsx
Avery Felts 54b7a294f5 Add achievements, health timeline, savings tracker, and reminders features
- Achievements system with 6 badges and confetti celebration animation
- Health recovery timeline showing 9 milestones from 20min to 1 year
- Money savings tracker with cost configuration and goal progress
- Daily reminder notifications with browser permission handling
- New Prisma models: Achievement, ReminderSettings, SavingsConfig
- API routes for all new features
- Full dashboard integration with staggered animations

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 11:38:46 -07:00

119 lines
3.3 KiB
TypeScript

'use client';
import { useEffect, useState } from 'react';
import { BadgeDefinition } from '@/lib/storage';
import {
Trophy,
Footprints,
Flame,
Shield,
Swords,
Crown,
Sparkles,
} from 'lucide-react';
interface CelebrationAnimationProps {
badge: BadgeDefinition;
onComplete: () => void;
}
const iconMap: Record<string, React.ElementType> = {
Footprints,
Flame,
Shield,
Swords,
Crown,
Trophy,
};
export function CelebrationAnimation({
badge,
onComplete,
}: CelebrationAnimationProps) {
const [particles, setParticles] = useState<
Array<{ id: number; x: number; y: number; color: string; delay: number }>
>([]);
useEffect(() => {
// Generate confetti particles
const newParticles = Array.from({ length: 50 }, (_, i) => ({
id: i,
x: Math.random() * 100,
y: Math.random() * 100,
color: ['#fbbf24', '#a855f7', '#22c55e', '#3b82f6', '#ef4444'][
Math.floor(Math.random() * 5)
],
delay: Math.random() * 0.5,
}));
setParticles(newParticles);
// Auto dismiss after 3 seconds
const timer = setTimeout(() => {
onComplete();
}, 3000);
return () => clearTimeout(timer);
}, [onComplete]);
const Icon = iconMap[badge.icon] || Trophy;
return (
<div
className="fixed inset-0 z-[100] flex items-center justify-center bg-black/60 backdrop-blur-sm"
onClick={onComplete}
>
{/* Confetti particles */}
{particles.map((particle) => (
<div
key={particle.id}
className="absolute w-2 h-2 rounded-full animate-confetti"
style={{
left: `${particle.x}%`,
top: '-10px',
backgroundColor: particle.color,
animationDelay: `${particle.delay}s`,
}}
/>
))}
{/* Badge reveal */}
<div className="relative animate-scale-in">
{/* Glow effect */}
<div className="absolute inset-0 bg-yellow-500/30 rounded-full blur-3xl scale-150 animate-pulse-subtle" />
{/* Main content */}
<div className="relative bg-gradient-to-br from-purple-600 to-indigo-700 p-8 rounded-2xl border border-yellow-500/50 shadow-2xl">
<div className="flex flex-col items-center gap-4">
{/* Sparkles */}
<div className="absolute -top-4 -right-4">
<Sparkles className="h-8 w-8 text-yellow-400 animate-float" />
</div>
<div className="absolute -bottom-4 -left-4">
<Sparkles className="h-6 w-6 text-yellow-400 animate-float delay-300" />
</div>
{/* Badge icon */}
<div className="p-4 bg-gradient-to-br from-yellow-400 to-amber-500 rounded-full shadow-lg">
<Icon className="h-12 w-12 text-white" />
</div>
{/* Text */}
<div className="text-center">
<p className="text-yellow-400 text-sm font-medium uppercase tracking-wider mb-1">
Achievement Unlocked!
</p>
<h2 className="text-2xl font-bold text-white mb-1">{badge.name}</h2>
<p className="text-white/70 text-sm">{badge.description}</p>
</div>
</div>
</div>
</div>
{/* Tap to dismiss hint */}
<p className="absolute bottom-8 text-white/50 text-sm">
Tap anywhere to dismiss
</p>
</div>
);
}