229 lines
11 KiB
React
229 lines
11 KiB
React
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
||
|
|
import { createPortal } from 'react-dom';
|
||
|
|
import { Trophy, Star } from 'lucide-react';
|
||
|
|
|
||
|
|
export function WinnerModal({ isOpen, winner, onConfirm }) {
|
||
|
|
if (!isOpen) return null;
|
||
|
|
|
||
|
|
return createPortal(
|
||
|
|
<AnimatePresence>
|
||
|
|
<motion.div
|
||
|
|
initial={{ opacity: 0 }}
|
||
|
|
animate={{ opacity: 1 }}
|
||
|
|
exit={{ opacity: 0 }}
|
||
|
|
className="fixed inset-0 z-50 flex items-center justify-center p-4"
|
||
|
|
style={{
|
||
|
|
background: 'rgba(0, 0, 0, 0.92)',
|
||
|
|
backdropFilter: 'blur(8px)'
|
||
|
|
}}
|
||
|
|
onClick={onConfirm}
|
||
|
|
>
|
||
|
|
{/* Floating particles animation */}
|
||
|
|
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||
|
|
{[...Array(20)].map((_, i) => (
|
||
|
|
<motion.div
|
||
|
|
key={i}
|
||
|
|
className="absolute"
|
||
|
|
initial={{
|
||
|
|
x: Math.random() * window.innerWidth,
|
||
|
|
y: window.innerHeight + 100,
|
||
|
|
opacity: 0
|
||
|
|
}}
|
||
|
|
animate={{
|
||
|
|
y: -100,
|
||
|
|
opacity: [0, 1, 0],
|
||
|
|
scale: [0, 1, 0]
|
||
|
|
}}
|
||
|
|
transition={{
|
||
|
|
duration: 3 + Math.random() * 2,
|
||
|
|
repeat: Infinity,
|
||
|
|
delay: Math.random() * 3,
|
||
|
|
ease: "easeOut"
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<Star size={20} fill="#D4AF37" color="#D4AF37" />
|
||
|
|
</motion.div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<motion.div
|
||
|
|
initial={{ scale: 0.5, opacity: 0, rotateY: -180 }}
|
||
|
|
animate={{ scale: 1, opacity: 1, rotateY: 0 }}
|
||
|
|
exit={{ scale: 0.5, opacity: 0, rotateY: 180 }}
|
||
|
|
transition={{
|
||
|
|
type: 'spring',
|
||
|
|
duration: 0.8,
|
||
|
|
bounce: 0.3
|
||
|
|
}}
|
||
|
|
className="relative max-w-2xl w-full overflow-hidden rounded-[2rem] shadow-2xl"
|
||
|
|
style={{
|
||
|
|
background: 'linear-gradient(135deg, rgba(0, 0, 0, 0.95) 0%, rgba(20, 20, 20, 0.9) 100%)',
|
||
|
|
backdropFilter: 'blur(20px)',
|
||
|
|
}}
|
||
|
|
onClick={(e) => e.stopPropagation()}
|
||
|
|
>
|
||
|
|
{/* Metallic border */}
|
||
|
|
<div className="absolute inset-0 rounded-[2rem] pointer-events-none" style={{
|
||
|
|
background: 'linear-gradient(135deg, #bf953f 0%, #fcf6ba 25%, #b38728 50%, #fbf5b7 75%, #aa771c 100%)',
|
||
|
|
padding: '4px',
|
||
|
|
WebkitMask: 'linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)',
|
||
|
|
WebkitMaskComposite: 'xor',
|
||
|
|
maskComposite: 'exclude',
|
||
|
|
}}></div>
|
||
|
|
|
||
|
|
{/* Glow effect */}
|
||
|
|
<div className="absolute inset-0 rounded-[2rem] pointer-events-none" style={{
|
||
|
|
boxShadow: '0 0 80px rgba(212, 175, 55, 0.5), inset 0 0 80px rgba(212, 175, 55, 0.1)'
|
||
|
|
}}></div>
|
||
|
|
|
||
|
|
<div className="relative z-10 p-8 md:p-12 lg:p-16">
|
||
|
|
{/* Animated Trophy Icon */}
|
||
|
|
<motion.div
|
||
|
|
className="flex justify-center mb-8"
|
||
|
|
animate={{
|
||
|
|
rotate: [0, -10, 10, -10, 10, 0],
|
||
|
|
scale: [1, 1.1, 1]
|
||
|
|
}}
|
||
|
|
transition={{
|
||
|
|
duration: 2,
|
||
|
|
repeat: Infinity,
|
||
|
|
repeatDelay: 1
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<div
|
||
|
|
className="relative w-28 h-28 md:w-32 md:h-32 rounded-full flex items-center justify-center"
|
||
|
|
style={{
|
||
|
|
background: 'linear-gradient(135deg, #bf953f 0%, #fcf6ba 50%, #b38728 100%)',
|
||
|
|
boxShadow: '0 8px 40px rgba(212, 175, 55, 0.6), inset 0 2px 4px rgba(255, 255, 255, 0.3)'
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<Trophy size={60} color="#FFFFFF" strokeWidth={2.5} />
|
||
|
|
|
||
|
|
{/* Sparkle effect */}
|
||
|
|
<motion.div
|
||
|
|
className="absolute top-0 right-0"
|
||
|
|
animate={{
|
||
|
|
scale: [0, 1, 0],
|
||
|
|
rotate: [0, 180, 360]
|
||
|
|
}}
|
||
|
|
transition={{
|
||
|
|
duration: 2,
|
||
|
|
repeat: Infinity,
|
||
|
|
repeatDelay: 0.5
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<Star size={24} fill="#FFE87C" color="#FFE87C" />
|
||
|
|
</motion.div>
|
||
|
|
</div>
|
||
|
|
</motion.div>
|
||
|
|
|
||
|
|
{/* Title with shimmer effect */}
|
||
|
|
<motion.h2
|
||
|
|
className="text-4xl md:text-5xl lg:text-6xl font-black text-center mb-6 relative"
|
||
|
|
initial={{ y: -20, opacity: 0 }}
|
||
|
|
animate={{ y: 0, opacity: 1 }}
|
||
|
|
transition={{ delay: 0.3 }}
|
||
|
|
>
|
||
|
|
<span style={{
|
||
|
|
background: 'linear-gradient(to bottom, #bf953f 0%, #fcf6ba 40%, #b38728 55%, #fbf5b7 100%)',
|
||
|
|
WebkitBackgroundClip: 'text',
|
||
|
|
WebkitTextFillColor: 'transparent',
|
||
|
|
filter: 'drop-shadow(0 4px 12px rgba(212, 175, 55, 0.8))',
|
||
|
|
}}>
|
||
|
|
WINNER!
|
||
|
|
</span>
|
||
|
|
</motion.h2>
|
||
|
|
|
||
|
|
{/* Subtitle */}
|
||
|
|
<motion.p
|
||
|
|
className="text-center text-white/70 text-xl md:text-2xl mb-10 font-medium"
|
||
|
|
initial={{ y: -10, opacity: 0 }}
|
||
|
|
animate={{ y: 0, opacity: 1 }}
|
||
|
|
transition={{ delay: 0.4 }}
|
||
|
|
>
|
||
|
|
The winning number is
|
||
|
|
</motion.p>
|
||
|
|
|
||
|
|
{/* Winner Number with luxury card */}
|
||
|
|
<motion.div
|
||
|
|
initial={{ scale: 0.8, opacity: 0 }}
|
||
|
|
animate={{ scale: 1, opacity: 1 }}
|
||
|
|
transition={{ delay: 0.5, type: 'spring' }}
|
||
|
|
className="relative mb-12 p-8 md:p-10 rounded-3xl overflow-hidden"
|
||
|
|
style={{
|
||
|
|
background: 'linear-gradient(135deg, rgba(191, 149, 63, 0.15) 0%, rgba(179, 135, 40, 0.1) 100%)',
|
||
|
|
border: '3px solid rgba(212, 175, 55, 0.5)',
|
||
|
|
boxShadow: '0 8px 40px rgba(212, 175, 55, 0.3), inset 0 2px 4px rgba(255, 255, 255, 0.1)'
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
{/* Animated shimmer overlay */}
|
||
|
|
<motion.div
|
||
|
|
className="absolute inset-0 pointer-events-none"
|
||
|
|
style={{
|
||
|
|
background: 'linear-gradient(90deg, transparent 0%, rgba(252, 246, 186, 0.3) 50%, transparent 100%)',
|
||
|
|
}}
|
||
|
|
animate={{
|
||
|
|
x: ['-100%', '200%']
|
||
|
|
}}
|
||
|
|
transition={{
|
||
|
|
duration: 3,
|
||
|
|
repeat: Infinity,
|
||
|
|
ease: 'linear'
|
||
|
|
}}
|
||
|
|
></motion.div>
|
||
|
|
|
||
|
|
<div
|
||
|
|
className="text-center text-8xl md:text-9xl font-black tabular-nums relative z-10"
|
||
|
|
style={{
|
||
|
|
background: 'linear-gradient(to bottom, #bf953f 0%, #fcf6ba 40%, #b38728 55%, #fbf5b7 100%)',
|
||
|
|
WebkitBackgroundClip: 'text',
|
||
|
|
WebkitTextFillColor: 'transparent',
|
||
|
|
filter: 'drop-shadow(0 6px 16px rgba(212, 175, 55, 0.5))',
|
||
|
|
letterSpacing: '0.1em'
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
{winner !== null ? winner.toString().padStart(3, '0') : '000'}
|
||
|
|
</div>
|
||
|
|
</motion.div>
|
||
|
|
|
||
|
|
{/* OK Button with luxury styling */}
|
||
|
|
<motion.button
|
||
|
|
onClick={onConfirm}
|
||
|
|
className="w-full py-5 rounded-2xl text-2xl font-bold uppercase tracking-wider relative overflow-hidden group"
|
||
|
|
style={{
|
||
|
|
background: 'linear-gradient(135deg, #bf953f 0%, #fcf6ba 25%, #b38728 50%, #fbf5b7 75%, #aa771c 100%)',
|
||
|
|
boxShadow: '0 6px 30px rgba(212, 175, 55, 0.6)',
|
||
|
|
color: '#000000',
|
||
|
|
fontFamily: "'Inter', sans-serif",
|
||
|
|
}}
|
||
|
|
whileHover={{ scale: 1.05 }}
|
||
|
|
whileTap={{ scale: 0.95 }}
|
||
|
|
initial={{ y: 20, opacity: 0 }}
|
||
|
|
animate={{ y: 0, opacity: 1 }}
|
||
|
|
transition={{ delay: 0.6 }}
|
||
|
|
>
|
||
|
|
{/* Button shimmer effect */}
|
||
|
|
<motion.div
|
||
|
|
className="absolute inset-0 pointer-events-none"
|
||
|
|
style={{
|
||
|
|
background: 'linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.6) 50%, transparent 100%)',
|
||
|
|
}}
|
||
|
|
animate={{
|
||
|
|
x: ['-100%', '200%']
|
||
|
|
}}
|
||
|
|
transition={{
|
||
|
|
duration: 2,
|
||
|
|
repeat: Infinity,
|
||
|
|
ease: 'linear'
|
||
|
|
}}
|
||
|
|
></motion.div>
|
||
|
|
<span className="relative z-10 font-black">CONTINUE</span>
|
||
|
|
</motion.button>
|
||
|
|
</div>
|
||
|
|
</motion.div>
|
||
|
|
</motion.div>
|
||
|
|
</AnimatePresence>,
|
||
|
|
document.body
|
||
|
|
);
|
||
|
|
}
|