RYP / src /components /AvatarCustomizerModal.tsx
Soumya79's picture
Upload 1361 files
f91a684 verified
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>
);
}