| import React, { useState, useEffect } from 'react'; |
| import { motion, AnimatePresence } from 'motion/react'; |
| import { X, Check } from 'lucide-react'; |
| import { cn } from '@/lib/utils'; |
|
|
| export type AvatarConfig = { |
| id: string; |
| hue: number; |
| saturate: number; |
| glowColor: string; |
| }; |
|
|
| export const AURAS: (AvatarConfig & { label: string })[] = [ |
| { id: 'default', label: 'Classic', glowColor: 'transparent', hue: 0, saturate: 100 }, |
| { id: 'neon-cyber', label: 'Neon Cyber', glowColor: '#06b6d4', hue: 160, saturate: 150 }, |
| { id: 'crimson', label: 'Crimson', glowColor: '#e11d48', hue: 330, saturate: 130 }, |
| { id: 'matrix', label: 'Matrix', glowColor: '#22c55e', hue: 100, saturate: 200 }, |
| { id: 'golden', label: 'Golden', glowColor: '#fbbf24', hue: 45, saturate: 140 }, |
| { id: 'void', label: 'The Void', glowColor: '#a855f7', hue: 280, saturate: 180 }, |
| ]; |
|
|
| export default function AvatarCustomizerModal({ |
| isOpen, |
| onClose, |
| initialConfig, |
| onSave, |
| }: { |
| isOpen: boolean; |
| onClose: () => void; |
| initialConfig: AvatarConfig; |
| onSave: (config: AvatarConfig) => void; |
| }) { |
| const [activeTab, setActiveTab] = useState('aura'); |
| const [selectedConfig, setSelectedConfig] = useState<AvatarConfig>(initialConfig); |
|
|
| useEffect(() => { |
| if (isOpen) setSelectedConfig(initialConfig); |
| }, [isOpen, initialConfig]); |
|
|
| return ( |
| <AnimatePresence> |
| {isOpen && ( |
| <div className="fixed inset-0 z-50 flex items-center justify-center pointer-events-none p-4 md:p-12 font-sans font-medium"> |
| <motion.div |
| initial={{ opacity: 0 }} |
| animate={{ opacity: 1 }} |
| exit={{ opacity: 0 }} |
| className="absolute inset-0 bg-black/60 backdrop-blur-md pointer-events-auto" |
| onClick={onClose} |
| /> |
| <motion.div |
| initial={{ scale: 0.95, opacity: 0, y: 30 }} |
| animate={{ scale: 1, opacity: 1, y: 0 }} |
| exit={{ scale: 0.95, opacity: 0, y: 30 }} |
| transition={{ type: 'spring', damping: 25, stiffness: 300 }} |
| className="relative bg-[#0d0f1a] w-full max-w-4xl h-[85vh] md:h-[75vh] rounded-[2.5rem] border border-white/10 shadow-[0_0_80px_rgba(0,0,0,0.8)] overflow-hidden flex flex-col pointer-events-auto" |
| > |
| {/* Header */} |
| <div className="flex justify-between items-center p-6 border-b border-white/5 bg-[#121422]/50"> |
| <h2 className="text-xl font-black text-white px-2">Customize Avatar</h2> |
| <button |
| onClick={onClose} |
| className="p-2 rounded-full hover:bg-white/10 text-slate-400 hover:text-white transition-colors" |
| > |
| <X size={24} /> |
| </button> |
| </div> |
| |
| {/* Content Split */} |
| <div className="flex flex-col md:flex-row flex-1 overflow-hidden"> |
| {/* Avatar Stage */} |
| <div className="flex-1 bg-[radial-gradient(ellipse_at_center,rgba(255,255,255,0.05),transparent_70%)] relative flex items-center justify-center p-8 md:p-12 overflow-hidden border-b md:border-b-0 md:border-r border-white/5"> |
| <motion.div |
| animate={{ rotate: 360 }} |
| transition={{ duration: 25, repeat: Infinity, ease: "linear" }} |
| className="absolute md:-inset-1/2 -inset-[10%] opacity-60 mix-blend-screen transform-gpu" |
| style={{ |
| background: selectedConfig.glowColor !== 'transparent' ? `conic-gradient(from 0deg, transparent 0 90deg, ${selectedConfig.glowColor}40 90deg 180deg, transparent 180deg 270deg, ${selectedConfig.glowColor}20 270deg 360deg)` : 'transparent' |
| }} |
| /> |
| <div |
| className="relative z-10 w-64 h-64 md:w-80 md:h-80 transition-all duration-500 ease-out" |
| style={{ |
| filter: selectedConfig.glowColor !== 'transparent' ? `drop-shadow(0 0 50px ${selectedConfig.glowColor}80) hue-rotate(${selectedConfig.hue}deg) saturate(${selectedConfig.saturate}%)` : 'none' |
| }} |
| > |
| <img src="/avatar.png" alt="Avatar Preview" className="w-full h-full object-contain" /> |
| </div> |
| </div> |
| |
| {/* Drawer Menu */} |
| <div className="w-full md:w-[400px] flex flex-col bg-[#121422]/70 h-full"> |
| <div className="flex gap-6 p-6 border-b border-white/5 overflow-x-auto no-scrollbar"> |
| {['Aura'].map(tab => ( |
| <button key={tab} onClick={() => setActiveTab(tab.toLowerCase())} className={cn("text-[11px] font-black uppercase tracking-[0.2em] pb-3 border-b-2 transition-colors", activeTab === tab.toLowerCase() ? "border-indigo-400 text-white" : "border-transparent text-slate-500 hover:text-slate-300")}> |
| {tab} |
| </button> |
| ))} |
| </div> |
| |
| <div className="flex-1 overflow-y-auto p-6 custom-scrollbar"> |
| <div className="grid grid-cols-2 gap-4"> |
| {AURAS.map(aura => ( |
| <button |
| key={aura.id} |
| onClick={() => setSelectedConfig(aura)} |
| className={cn("flex flex-col items-center justify-center p-4 rounded-2xl border transition-all h-28 group", selectedConfig.id === aura.id ? "bg-white/10 border-white/20 shadow-[0_0_25px_rgba(255,255,255,0.05)] scale-[1.02]" : "bg-white/5 border-transparent hover:bg-white/10")} |
| > |
| <div |
| className="w-10 h-10 rounded-full mb-3 shadow-lg group-hover:scale-110 transition-transform" |
| style={{ |
| background: aura.glowColor === 'transparent' ? 'linear-gradient(135deg, #475569, #1e293b)' : aura.glowColor, |
| boxShadow: aura.glowColor !== 'transparent' ? `0 0 15px ${aura.glowColor}` : 'none' |
| }} |
| /> |
| <span className={cn("text-xs font-bold transition-colors", selectedConfig.id === aura.id ? "text-white" : "text-slate-400 group-hover:text-slate-300")}>{aura.label}</span> |
| </button> |
| ))} |
| </div> |
| </div> |
| |
| <div className="p-6 border-t border-white/5 bg-[#0d0f1a]"> |
| <button |
| onClick={() => { onSave(selectedConfig); onClose(); }} |
| className="w-full bg-white hover:bg-indigo-400 text-black hover:text-white py-3.5 rounded-full text-sm font-black tracking-wide shadow-[0_0_20px_rgba(255,255,255,0.1)] hover:shadow-[0_0_20px_rgba(99,102,241,0.4)] transition-all flex items-center justify-center gap-2" |
| > |
| <Check size={18} strokeWidth={3} /> Apply Appearance |
| </button> |
| </div> |
| </div> |
| </div> |
| </motion.div> |
| </div> |
| )} |
| </AnimatePresence> |
| ); |
| } |
|
|