import React, { useRef, useEffect } from 'react'; interface ButtonProps extends React.ButtonHTMLAttributes { variant?: 'primary' | 'secondary' | 'danger' | 'ghost' | 'success'; size?: 'sm' | 'md' | 'lg'; children: React.ReactNode; } export const Button: React.FC = ({ variant = 'primary', size = 'md', className = '', children, ...props }) => { const canvasRef = useRef(null); const animationRef = useRef(0); const particles = useRef([]); // Cleanup useEffect(() => { return () => cancelAnimationFrame(animationRef.current); }, []); const createParticles = (x: number, y: number, theme: string) => { const count = theme === 'theme_midnight' ? 20 : theme === 'theme_royal' ? 12 : 8; for (let i = 0; i < count; i++) { const angle = Math.random() * Math.PI * 2; const speed = Math.random() * (theme === 'theme_midnight' ? 4 : 2) + 1; particles.current.push({ x, y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, life: 1.0, decay: Math.random() * 0.03 + 0.02, size: Math.random() * 3 + 1, theme }); } if (!animationRef.current) { animate(); } }; const animate = () => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; ctx.clearRect(0, 0, canvas.width, canvas.height); particles.current.forEach((p, index) => { p.x += p.vx; p.y += p.vy; p.life -= p.decay; // Theme Physics if (p.theme === 'theme_royal') { p.vy += 0.2; // Gravity for gold dust p.size *= 0.95; } else if (p.theme === 'theme_sky') { p.vy -= 0.1; // Float up for clouds p.size += 0.2; // Expand } ctx.save(); ctx.globalAlpha = Math.max(0, p.life); if (p.theme === 'theme_midnight') { // Star Burst ctx.fillStyle = '#fff'; ctx.shadowBlur = 4; ctx.shadowColor = '#6366f1'; ctx.beginPath(); ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); ctx.fill(); } else if (p.theme === 'theme_royal') { // Gold Diamonds ctx.fillStyle = '#facc15'; ctx.beginPath(); ctx.rect(p.x, p.y, p.size * 2, p.size * 2); ctx.fill(); } else if (p.theme === 'theme_sky') { // Cloud Puffs ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(p.x, p.y, p.size * 3, 0, Math.PI * 2); ctx.fill(); } else { // Default Ripple/Nature ctx.strokeStyle = 'rgba(255,255,255,0.5)'; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(p.x, p.y, (1 - p.life) * 30, 0, Math.PI * 2); ctx.stroke(); } ctx.restore(); if (p.life <= 0) { particles.current.splice(index, 1); } }); if (particles.current.length > 0) { animationRef.current = requestAnimationFrame(animate); } else { animationRef.current = 0; } }; const handleClick = (e: React.MouseEvent) => { if (props.disabled) return; // Get active theme from body attribute set by Layout const theme = document.body.getAttribute('data-theme') || 'default'; const rect = e.currentTarget.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; // Setup Canvas Resolution if (canvasRef.current) { canvasRef.current.width = rect.width; canvasRef.current.height = rect.height; } createParticles(x, y, theme); if (props.onClick) props.onClick(e); }; const baseStyles = "relative inline-flex items-center justify-center rounded-lg font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none active:scale-95 hover:shadow-md overflow-hidden"; const variants = { primary: "bg-red-700 text-white hover:bg-red-800 focus:ring-red-600 shadow-sm hover:shadow-red-500/30 border border-transparent", secondary: "bg-white text-gray-700 border border-gray-300 hover:bg-orange-50 focus:ring-orange-500 hover:border-orange-300", danger: "bg-red-600 text-white hover:bg-red-700 focus:ring-red-500 shadow-sm hover:shadow-red-500/30 border border-transparent", success: "bg-green-600 text-white hover:bg-green-700 focus:ring-green-500 shadow-sm hover:shadow-green-500/30 border border-transparent", ghost: "bg-transparent text-gray-600 hover:bg-orange-100 hover:text-red-700 focus:ring-gray-500 hover:shadow-none border border-transparent", }; const sizes = { sm: "h-8 px-3 text-sm", md: "h-10 px-4 py-2", lg: "h-12 px-6 text-lg", }; return ( ); };