Commit 6a8bf93c authored by Mahmoud Aglan's avatar Mahmoud Aglan

feat: complete UI/UX overhaul — Clash Royale / Brawl Stars style

Vibrant, playful design system with thick 3px borders, bold/black
font weights, bouncy spring animations, and saturated colors.
Desktop-friendly with responsive container (480-560px max-width).

- New color palette: brighter gold, vibrant coral/cyan/purple
- Thick bordered cards, push-effect buttons, animated glow rings
- app-container system: centered content, generous gutters
- All 14 pages rewritten with the new design language
- No edge-to-edge elements anywhere
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent f574992e
...@@ -23,7 +23,7 @@ export function DailyRewardModal({ open, streak, reward, loading, onClaim, onClo ...@@ -23,7 +23,7 @@ export function DailyRewardModal({ open, streak, reward, loading, onClaim, onClo
onClick={onClose} onClick={onClose}
> >
<motion.div <motion.div
className="w-full max-w-[320px] rounded-3xl bg-surface-1 border border-border p-7 flex flex-col items-center gap-5 relative overflow-hidden" className="w-full max-w-[340px] rounded-3xl bg-surface-1 border-3 border-border p-7 flex flex-col items-center gap-5 relative overflow-hidden"
initial={{ scale: 0.8, opacity: 0 }} initial={{ scale: 0.8, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }} animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.8, opacity: 0 }} exit={{ scale: 0.8, opacity: 0 }}
...@@ -33,7 +33,7 @@ export function DailyRewardModal({ open, streak, reward, loading, onClaim, onClo ...@@ -33,7 +33,7 @@ export function DailyRewardModal({ open, streak, reward, loading, onClaim, onClo
<div className="absolute top-0 left-0 w-full h-32 bg-gradient-to-b from-gold/8 to-transparent" /> <div className="absolute top-0 left-0 w-full h-32 bg-gradient-to-b from-gold/8 to-transparent" />
<motion.div <motion.div
className="w-20 h-20 rounded-full bg-gradient-to-br from-gold/20 to-gold/5 border-2 border-gold/40 flex items-center justify-center relative" className="w-20 h-20 rounded-full bg-gradient-to-br from-gold/20 to-gold/5 border-3 border-gold/50 flex items-center justify-center relative"
animate={{ rotate: [0, 5, -5, 0] }} animate={{ rotate: [0, 5, -5, 0] }}
transition={{ duration: 3, repeat: Infinity }} transition={{ duration: 3, repeat: Infinity }}
> >
...@@ -73,7 +73,7 @@ export function DailyRewardModal({ open, streak, reward, loading, onClaim, onClo ...@@ -73,7 +73,7 @@ export function DailyRewardModal({ open, streak, reward, loading, onClaim, onClo
size="lg" size="lg"
onClick={onClaim} onClick={onClaim}
loading={loading} loading={loading}
className="w-full" className="w-[80%] mx-auto"
> >
استلم المكافأة استلم المكافأة
</Button> </Button>
......
...@@ -10,9 +10,9 @@ export function AppShell() { ...@@ -10,9 +10,9 @@ export function AppShell() {
useNotifications() useNotifications()
return ( return (
<div className="flex flex-col min-h-dvh bg-[#0a0a12]"> <div className="flex flex-col min-h-dvh bg-background">
<Header /> <Header />
<main className="flex-1 pb-24 overflow-y-auto"> <main className="flex-1 pb-28 overflow-y-auto">
<Outlet /> <Outlet />
</main> </main>
<BottomNav /> <BottomNav />
......
...@@ -15,8 +15,9 @@ export function BottomNav() { ...@@ -15,8 +15,9 @@ export function BottomNav() {
const navigate = useNavigate() const navigate = useNavigate()
return ( return (
<nav className="fixed bottom-0 left-0 right-0 z-50 px-5 pb-3 pt-1"> <nav className="fixed bottom-0 left-0 right-0 z-50 px-4 pb-3 pt-1 md:px-6">
<div className="flex items-center justify-around px-3 py-3 bg-surface-2 border border-border rounded-[18px] shadow-2xl shadow-black/40"> <div className="app-container !p-0">
<div className="flex items-center justify-around px-2 py-2 bg-surface-2 border-3 border-border rounded-2xl shadow-2xl shadow-black/50">
{NAV_ITEMS.map((item) => { {NAV_ITEMS.map((item) => {
const isActive = location.pathname === item.path const isActive = location.pathname === item.path
const Icon = item.icon const Icon = item.icon
...@@ -25,28 +26,38 @@ export function BottomNav() { ...@@ -25,28 +26,38 @@ export function BottomNav() {
<motion.button <motion.button
key={item.path} key={item.path}
onClick={() => navigate(item.path)} onClick={() => navigate(item.path)}
className="flex flex-col items-center gap-1 px-2 py-1 rounded-xl relative" className={`flex flex-col items-center gap-0.5 px-3 py-2 rounded-xl relative ${
whileTap={{ scale: 0.85 }} isActive ? 'bg-gold/15' : ''
}`}
whileTap={{ scale: 0.8 }}
whileHover={{ scale: 1.05 }}
>
<motion.div
animate={isActive ? { y: -2 } : { y: 0 }}
transition={{ type: 'spring', stiffness: 500, damping: 20 }}
> >
<Icon <Icon
size={20} size={22}
className={isActive ? 'text-gold' : 'text-text-muted'} className={isActive ? 'text-gold' : 'text-text-muted'}
strokeWidth={isActive ? 2.5 : 1.8} strokeWidth={isActive ? 2.8 : 2}
/> />
<span className={`text-[9px] font-semibold ${isActive ? 'text-gold' : 'text-text-muted'}`}> </motion.div>
<span className={`text-[10px] font-bold ${isActive ? 'text-gold' : 'text-text-muted'}`}>
{item.label} {item.label}
</span> </span>
{isActive && ( {isActive && (
<motion.div <motion.div
className="absolute -bottom-1 w-4 h-[3px] rounded-full bg-gold" className="absolute -bottom-0.5 w-6 h-[4px] rounded-full bg-gold"
layoutId="nav-dot" layoutId="nav-indicator"
style={{ boxShadow: '0 0 6px rgba(212, 168, 67, 0.7)' }} style={{ boxShadow: '0 0 10px rgba(255, 200, 60, 0.8)' }}
transition={{ type: 'spring', stiffness: 500, damping: 30 }}
/> />
)} )}
</motion.button> </motion.button>
) )
})} })}
</div> </div>
</div>
</nav> </nav>
) )
} }
import { motion } from 'framer-motion' import { motion } from 'framer-motion'
import { Bell, Coins } from 'lucide-react' import { Bell, Coins, Gem } from 'lucide-react'
import { useNotificationStore } from '../../stores/notificationStore' import { useNotificationStore } from '../../stores/notificationStore'
import { useAuthStore } from '../../stores/authStore' import { useAuthStore } from '../../stores/authStore'
import { AnimatedIcon } from '../icons/AnimatedIcon'
import { GoldCrown } from '../icons/GoldCrown' import { GoldCrown } from '../icons/GoldCrown'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
...@@ -12,32 +11,55 @@ export function Header() { ...@@ -12,32 +11,55 @@ export function Header() {
const navigate = useNavigate() const navigate = useNavigate()
return ( return (
<header className="sticky top-0 z-50 px-5 pt-3 pb-1"> <header className="sticky top-0 z-50 px-4 pt-3 pb-1 md:px-6">
<div className="flex items-center justify-between px-5 py-3 bg-surface-2 border border-border rounded-[18px]"> <div className="app-container !p-0">
<div className="flex items-center gap-2.5"> <div className="flex items-center justify-between px-4 py-2.5 bg-surface-2 border-3 border-border rounded-2xl shadow-lg shadow-black/30">
<GoldCrown size={24} animate={false} /> <div className="flex items-center gap-2">
<span className="text-sm font-bold text-gold tracking-wide">EL3AB</span> <GoldCrown size={28} animate={false} />
<span className="text-base font-black text-gold tracking-wider">EL3AB</span>
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-2">
{profile && ( {profile && (
<div className="flex items-center gap-1.5 px-3 py-1 rounded-full bg-surface-3/80 border border-gold/20"> <>
<Coins size={12} className="text-gold" /> <motion.div
<span className="text-[11px] font-bold text-gold">{profile.coins}</span> className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-gold/15 border-2 border-gold/40"
</div> whileTap={{ scale: 0.9 }}
>
<Coins size={14} className="text-gold" />
<span className="text-xs font-black text-gold">{profile.coins}</span>
</motion.div>
{profile.gems > 0 && (
<motion.div
className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-purple/15 border-2 border-purple/40"
whileTap={{ scale: 0.9 }}
>
<Gem size={12} className="text-purple" />
<span className="text-xs font-black text-purple">{profile.gems}</span>
</motion.div>
)}
</>
)} )}
<div className="relative" onClick={() => navigate('/notifications')}> <motion.button
<AnimatedIcon icon={Bell} size={18} color="var(--color-text-secondary)" onClick={() => {}} /> className="relative p-2.5 rounded-xl bg-surface-3 border-2 border-border"
onClick={() => navigate('/notifications')}
whileTap={{ scale: 0.85 }}
whileHover={{ scale: 1.05 }}
>
<Bell size={18} className="text-text-secondary" />
{unreadCount > 0 && ( {unreadCount > 0 && (
<motion.div <motion.div
className="absolute -top-1 -right-1 w-3.5 h-3.5 rounded-full bg-coral flex items-center justify-center" className="absolute -top-1 -right-1 w-5 h-5 rounded-full bg-coral border-2 border-surface-2 flex items-center justify-center"
initial={{ scale: 0 }} initial={{ scale: 0 }}
animate={{ scale: 1 }} animate={{ scale: 1 }}
transition={{ type: 'spring', stiffness: 500, damping: 15 }}
> >
<span className="text-[8px] font-bold text-white">{unreadCount > 9 ? '9+' : unreadCount}</span> <span className="text-[9px] font-black text-white">{unreadCount > 9 ? '9+' : unreadCount}</span>
</motion.div> </motion.div>
)} )}
</motion.button>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -9,11 +9,11 @@ interface PageTransitionProps { ...@@ -9,11 +9,11 @@ interface PageTransitionProps {
export function PageTransition({ children, className = '' }: PageTransitionProps) { export function PageTransition({ children, className = '' }: PageTransitionProps) {
return ( return (
<motion.div <motion.div
className={`max-w-[360px] mx-auto w-full ${className}`} className={`app-container py-6 flex flex-col gap-6 ${className}`}
initial={{ opacity: 0, x: -20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, x: 20 }} exit={{ opacity: 0, y: -20 }}
transition={{ type: 'spring', stiffness: 300, damping: 30 }} transition={{ type: 'spring', stiffness: 300, damping: 28 }}
> >
{children} {children}
</motion.div> </motion.div>
......
...@@ -5,7 +5,7 @@ import { Loader2 } from 'lucide-react' ...@@ -5,7 +5,7 @@ import { Loader2 } from 'lucide-react'
interface ButtonProps { interface ButtonProps {
children: ReactNode children: ReactNode
onClick?: () => void onClick?: () => void
variant?: 'gold' | 'ghost' | 'coral' | 'cyan' variant?: 'gold' | 'ghost' | 'coral' | 'cyan' | 'purple'
size?: 'sm' | 'md' | 'lg' size?: 'sm' | 'md' | 'lg'
disabled?: boolean disabled?: boolean
loading?: boolean loading?: boolean
...@@ -14,16 +14,17 @@ interface ButtonProps { ...@@ -14,16 +14,17 @@ interface ButtonProps {
} }
const variants = { const variants = {
gold: 'bg-gradient-to-l from-gold to-gold-light text-background font-bold shadow-lg shadow-gold/20', gold: 'bg-gradient-to-b from-gold-light to-gold text-background font-black shadow-lg shadow-gold/30 border-b-4 border-gold-muted',
ghost: 'bg-surface-2 border border-border text-text-primary font-semibold hover:border-gold/40 hover:bg-surface-3', ghost: 'bg-surface-2 border-2 border-border text-text-primary font-bold hover:border-gold/50 hover:bg-surface-3',
coral: 'bg-gradient-to-l from-coral to-coral/80 text-white font-bold shadow-md shadow-coral/15', coral: 'bg-gradient-to-b from-[#FF7070] to-coral text-white font-black shadow-lg shadow-coral/25 border-b-4 border-[#B83A3A]',
cyan: 'bg-gradient-to-l from-cyan to-cyan/80 text-background font-bold shadow-md shadow-cyan/15', cyan: 'bg-gradient-to-b from-[#33FFE5] to-cyan text-background font-black shadow-lg shadow-cyan/25 border-b-4 border-[#009E8E]',
purple: 'bg-gradient-to-b from-[#CC70FF] to-purple text-white font-black shadow-lg shadow-purple/25 border-b-4 border-[#6B3D8F]',
} }
const sizes = { const sizes = {
sm: 'px-5 py-2.5 text-sm rounded-xl gap-2 min-w-[80px]', sm: 'px-5 py-2 text-sm rounded-xl gap-2 min-w-[90px]',
md: 'px-7 py-3.5 text-base rounded-xl gap-2.5 min-w-[100px]', md: 'px-7 py-3 text-base rounded-2xl gap-2.5 min-w-[120px]',
lg: 'px-8 py-4.5 text-lg rounded-2xl gap-3 min-w-[120px]', lg: 'px-10 py-4 text-lg rounded-2xl gap-3 min-w-[160px]',
} }
export function Button({ export function Button({
...@@ -41,9 +42,10 @@ export function Button({ ...@@ -41,9 +42,10 @@ export function Button({
type={type} type={type}
onClick={onClick} onClick={onClick}
disabled={disabled || loading} disabled={disabled || loading}
className={`inline-flex items-center justify-center transition-all duration-200 ${variants[variant]} ${sizes[size]} ${disabled ? 'opacity-50 cursor-not-allowed' : ''} ${className}`} className={`inline-flex items-center justify-center transition-all uppercase tracking-wide ${variants[variant]} ${sizes[size]} ${disabled ? 'opacity-50 cursor-not-allowed saturate-50' : ''} ${className}`}
whileTap={!disabled ? { scale: 0.96 } : undefined} whileTap={!disabled ? { scale: 0.92, y: 2 } : undefined}
transition={{ type: 'spring', stiffness: 500, damping: 20 }} whileHover={!disabled ? { scale: 1.03 } : undefined}
transition={{ type: 'spring', stiffness: 600, damping: 20 }}
> >
{loading && <Loader2 size={18} className="animate-spin" />} {loading && <Loader2 size={18} className="animate-spin" />}
{children} {children}
......
...@@ -6,16 +6,25 @@ interface CardProps { ...@@ -6,16 +6,25 @@ interface CardProps {
className?: string className?: string
glow?: boolean glow?: boolean
onClick?: () => void onClick?: () => void
variant?: 'default' | 'gold' | 'elevated'
} }
export function Card({ children, className = '', glow = false, onClick }: CardProps) { export function Card({ children, className = '', glow = false, onClick, variant = 'default' }: CardProps) {
const base = 'rounded-2xl p-5 border-3 transition-all'
const variantStyles = {
default: `bg-surface-1 border-border ${glow ? 'border-gold shadow-[0_0_20px_rgba(255,200,60,0.2)]' : ''}`,
gold: 'bg-gradient-to-br from-surface-2 to-surface-1 border-gold/60 shadow-[0_0_16px_rgba(255,200,60,0.15)]',
elevated: 'bg-surface-2 border-border shadow-xl shadow-black/40',
}
return ( return (
<motion.div <motion.div
className={`rounded-2xl bg-surface-1 border border-border p-5 ${glow ? 'border-gold/50 shadow-[0_0_20px_rgba(212,168,67,0.15)]' : ''} ${onClick ? 'cursor-pointer active:scale-[0.98]' : ''} ${className}`} className={`${base} ${variantStyles[variant]} ${onClick ? 'cursor-pointer' : ''} ${className}`}
whileHover={onClick ? { y: -2, boxShadow: '0 8px 30px rgba(212,168,67,0.12)' } : undefined} whileHover={onClick ? { y: -4, scale: 1.01, boxShadow: '0 12px 32px rgba(0,0,0,0.5)' } : undefined}
whileTap={onClick ? { scale: 0.98 } : undefined} whileTap={onClick ? { scale: 0.97 } : undefined}
onClick={onClick} onClick={onClick}
transition={{ type: 'spring', stiffness: 400, damping: 25 }} transition={{ type: 'spring', stiffness: 400, damping: 22 }}
> >
{children} {children}
</motion.div> </motion.div>
......
...@@ -23,8 +23,8 @@ export function Input({ ...@@ -23,8 +23,8 @@ export function Input({
const [focused, setFocused] = useState(false) const [focused, setFocused] = useState(false)
return ( return (
<div className="flex flex-col gap-1.5"> <div className="flex flex-col gap-2">
<label className="text-sm font-semibold text-text-secondary">{label}</label> <label className="text-sm font-bold text-text-secondary">{label}</label>
<div className="relative"> <div className="relative">
<input <input
type={type} type={type}
...@@ -34,19 +34,21 @@ export function Input({ ...@@ -34,19 +34,21 @@ export function Input({
disabled={disabled} disabled={disabled}
onFocus={() => setFocused(true)} onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)} onBlur={() => setFocused(false)}
className="w-full px-4 py-3 rounded-xl bg-surface-3 text-text-primary placeholder:text-text-muted border-2 border-transparent outline-none transition-colors focus:border-gold/60 disabled:opacity-50" className={`w-full px-4 py-3.5 rounded-xl bg-surface-3 text-text-primary placeholder:text-text-muted border-3 outline-none transition-all font-semibold ${
focused ? 'border-gold shadow-[0_0_12px_rgba(255,200,60,0.2)]' : 'border-border'
} ${error ? 'border-coral' : ''} disabled:opacity-50`}
dir="auto" dir="auto"
/> />
<motion.div <motion.div
className="absolute bottom-0 left-0 right-0 h-0.5 bg-gold rounded-full origin-center" className="absolute bottom-0 left-2 right-2 h-[3px] bg-gold rounded-full"
initial={{ scaleX: 0 }} initial={{ scaleX: 0 }}
animate={{ scaleX: focused ? 1 : 0 }} animate={{ scaleX: focused ? 1 : 0 }}
transition={{ type: 'spring', stiffness: 400, damping: 30 }} transition={{ type: 'spring', stiffness: 500, damping: 30 }}
/> />
</div> </div>
{error && ( {error && (
<motion.span <motion.span
className="text-xs text-coral" className="text-xs text-coral font-bold"
initial={{ opacity: 0, y: -4 }} initial={{ opacity: 0, y: -4 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
> >
......
...@@ -66,11 +66,11 @@ function ToastItem({ ...@@ -66,11 +66,11 @@ function ToastItem({
animate={{ opacity: 1, y: 0, scale: 1 }} animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: -30, scale: 0.9 }} exit={{ opacity: 0, y: -30, scale: 0.9 }}
transition={{ type: 'spring', stiffness: 400, damping: 25 }} transition={{ type: 'spring', stiffness: 400, damping: 25 }}
className={`pointer-events-auto w-full rounded-xl border p-3.5 flex items-center gap-3 backdrop-blur-xl shadow-lg ${colorClass}`} className={`pointer-events-auto w-full rounded-xl border-2 p-3.5 flex items-center gap-3 backdrop-blur-xl shadow-xl shadow-black/30 ${colorClass}`}
onClick={() => onDismiss(id)} onClick={() => onDismiss(id)}
> >
<Icon size={18} className="flex-shrink-0" /> <Icon size={18} className="flex-shrink-0" />
<span className="text-sm font-medium text-text-primary">{title}</span> <span className="text-sm font-bold text-text-primary">{title}</span>
</motion.div> </motion.div>
) )
} }
@import "tailwindcss"; @import "tailwindcss";
@theme { @theme {
--color-background: #06060E; /* === BACKGROUNDS === */
--color-surface-1: #141420; --color-background: #0B0E1A;
--color-surface-2: #1C1C30; --color-surface-1: #151929;
--color-surface-3: #26263C; --color-surface-2: #1E2340;
--color-border: #32324E; --color-surface-3: #2A3055;
--color-border-gold: rgba(212, 168, 67, 0.25); --color-border: #3D4570;
--color-border-gold: rgba(255, 200, 60, 0.35);
--color-gold: #D4A843;
--color-gold-light: #E8C55A; /* === BRAND (vibrant Brawl Stars palette) === */
--color-gold-muted: #B8964A; --color-gold: #FFC83D;
--color-coral: #E06B4A; --color-gold-light: #FFE066;
--color-royal-blue: #5B7FD6; --color-gold-muted: #C9972E;
--color-cyan: #4ECDC4; --color-coral: #FF5252;
--color-purple: #7B5EA7; --color-royal-blue: #4D8BFF;
--color-cyan: #00E5CC;
--color-purple: #B44DFF;
--color-electric-blue: #3B82F6; --color-electric-blue: #3B82F6;
--color-green: #4ADE80;
--color-orange: #FF8C42;
--color-text-primary: #F5F5F5; /* === TEXT === */
--color-text-secondary: #A0A0B8; --color-text-primary: #FFFFFF;
--color-text-muted: #6B6B80; --color-text-secondary: #B8BDD6;
--color-text-muted: #6E748C;
--color-win: #4ECDC4; /* === SEMANTIC === */
--color-loss: #E06B4A; --color-win: #00E5CC;
--color-draw: #A0A0B8; --color-loss: #FF5252;
--color-draw: #B8BDD6;
--font-family-cairo: 'Cairo', sans-serif; --font-family-cairo: 'Cairo', sans-serif;
} }
...@@ -52,8 +58,9 @@ body { ...@@ -52,8 +58,9 @@ body {
flex-direction: column; flex-direction: column;
} }
/* === SCROLLBAR === */
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 4px; width: 6px;
} }
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
...@@ -62,9 +69,101 @@ body { ...@@ -62,9 +69,101 @@ body {
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
background: var(--color-border); background: var(--color-border);
border-radius: 2px; border-radius: 3px;
} }
::-webkit-scrollbar-thumb:hover { ::-webkit-scrollbar-thumb:hover {
background: var(--color-gold-muted); background: var(--color-gold-muted);
} }
/* === GAME CARD THICK BORDERS === */
.game-card {
border: 3px solid var(--color-border);
border-radius: 20px;
background: var(--color-surface-1);
transition: transform 0.15s, box-shadow 0.15s;
}
.game-card:hover {
transform: translateY(-3px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
}
.game-card-gold {
border-color: var(--color-gold);
box-shadow: 0 0 20px rgba(255, 200, 60, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.05);
}
/* === PLAYFUL BUTTON PUSH EFFECT === */
.btn-push {
position: relative;
border-bottom: 4px solid rgba(0, 0, 0, 0.3);
transition: all 0.1s;
}
.btn-push:active {
border-bottom-width: 1px;
transform: translateY(3px);
}
/* === GLOW ANIMATIONS === */
@keyframes pulse-glow {
0%, 100% { box-shadow: 0 0 8px rgba(255, 200, 60, 0.3); }
50% { box-shadow: 0 0 24px rgba(255, 200, 60, 0.6); }
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-6px); }
}
.animate-pulse-glow {
animation: pulse-glow 2s ease-in-out infinite;
}
.animate-float {
animation: float 3s ease-in-out infinite;
}
/* === RESPONSIVE CONTAINER === */
.app-container {
width: 100%;
max-width: 480px;
margin: 0 auto;
padding-left: 20px;
padding-right: 20px;
}
@media (min-width: 768px) {
.app-container {
max-width: 520px;
padding-left: 24px;
padding-right: 24px;
}
}
@media (min-width: 1024px) {
.app-container {
max-width: 560px;
padding-left: 32px;
padding-right: 32px;
}
}
/* === DESKTOP LAYOUT === */
@media (min-width: 1024px) {
.desktop-sidebar {
position: fixed;
top: 0;
right: 0;
width: 260px;
height: 100dvh;
border-left: 2px solid var(--color-border);
background: var(--color-surface-1);
z-index: 40;
}
.desktop-main {
margin-right: 260px;
}
}
...@@ -23,9 +23,9 @@ export const GAMES = [ ...@@ -23,9 +23,9 @@ export const GAMES = [
] as const ] as const
export const RARITY_COLORS = { export const RARITY_COLORS = {
common: '#6B6B80', common: '#6E748C',
uncommon: '#5B7FD6', uncommon: '#4D8BFF',
rare: '#7B5EA7', rare: '#B44DFF',
epic: '#E06B4A', epic: '#FF5252',
legendary: '#D4A843', legendary: '#FFC83D',
} as const } as const
...@@ -8,13 +8,13 @@ import { fetchBots, getBotPortraitUrl, type Bot } from '../lib/stockfish' ...@@ -8,13 +8,13 @@ import { fetchBots, getBotPortraitUrl, type Bot } from '../lib/stockfish'
import { ChevronRight, Swords } from 'lucide-react' import { ChevronRight, Swords } from 'lucide-react'
const DIFFICULTY_COLORS: Record<string, string> = { const DIFFICULTY_COLORS: Record<string, string> = {
beginner: '#4ECDC4', beginner: '#00E5CC',
defensive: '#5B7FD6', defensive: '#4D8BFF',
aggressive: '#E06B4A', aggressive: '#FF5252',
positional: '#7B5EA7', positional: '#B44DFF',
creative: '#D4A843', creative: '#FFC83D',
solid: '#6B6B80', solid: '#6E748C',
near_perfect: '#FFD700', near_perfect: '#FFE066',
} }
const TOTAL_STRENGTH_DOTS = 7 const TOTAL_STRENGTH_DOTS = 7
...@@ -39,55 +39,50 @@ export function BotSelectPage() { ...@@ -39,55 +39,50 @@ export function BotSelectPage() {
if (loading) { if (loading) {
return ( return (
<PageTransition className="px-6 py-8 flex flex-col items-center justify-center min-h-[60vh]"> <PageTransition className="flex flex-col items-center justify-center min-h-[60vh]">
<div className="w-10 h-10 border-2 border-gold/30 border-t-gold rounded-full animate-spin" /> <div className="w-12 h-12 border-3 border-gold/30 border-t-gold rounded-full animate-spin" />
<p className="mt-4 text-sm text-text-muted">جاري تحميل الروبوتات...</p> <p className="mt-4 text-sm text-text-muted font-bold">جاري تحميل الروبوتات...</p>
</PageTransition> </PageTransition>
) )
} }
return ( return (
<PageTransition className="px-7 py-7 flex flex-col gap-6 pb-36"> <PageTransition className="pb-36">
{/* Header */}
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<motion.button <motion.button
onClick={() => navigate(-1)} onClick={() => navigate(-1)}
className="w-10 h-10 rounded-full bg-surface-2 border border-border flex items-center justify-center" className="w-11 h-11 rounded-xl bg-surface-2 border-2 border-border flex items-center justify-center"
whileTap={{ scale: 0.9 }} whileTap={{ scale: 0.85 }}
> >
<ChevronRight size={20} className="text-text-secondary" /> <ChevronRight size={20} className="text-text-secondary" />
</motion.button> </motion.button>
<div> <div>
<h1 className="text-xl font-bold">العب ضد الروبوت</h1> <h1 className="text-xl font-black">العب ضد الروبوت</h1>
<p className="text-xs text-text-muted mt-1">اختر خصمك حسب مستواك</p> <p className="text-xs text-text-muted font-semibold mt-0.5">اختر خصمك حسب مستواك</p>
</div> </div>
</div> </div>
{/* Bot List */}
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
{bots.map((bot, i) => { {bots.map((bot, i) => {
const isSelected = selectedBot === bot.id const isSelected = selectedBot === bot.id
const diffColor = DIFFICULTY_COLORS[bot.style] || '#6B6B80' const diffColor = DIFFICULTY_COLORS[bot.style] || '#6E748C'
const strengthLevel = Math.min(i + 1, TOTAL_STRENGTH_DOTS) const strengthLevel = Math.min(i + 1, TOTAL_STRENGTH_DOTS)
return ( return (
<motion.div <motion.div
key={bot.id} key={bot.id}
initial={{ opacity: 0, y: 15 }} initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, x: 0 }}
transition={{ delay: i * 0.05, type: 'spring', stiffness: 400, damping: 25 }} transition={{ delay: i * 0.06, type: 'spring', stiffness: 400, damping: 22 }}
> >
<Card <Card
glow={isSelected} glow={isSelected}
onClick={() => setSelectedBot(bot.id)} onClick={() => setSelectedBot(bot.id)}
className={`flex items-center gap-4 transition-all ${ className={`flex items-center gap-4 ${isSelected ? '!border-gold' : ''}`}
isSelected ? 'border-gold/60 scale-[1.01]' : ''
}`}
> >
{/* Portrait */}
<div <div
className="relative w-14 h-14 rounded-full overflow-hidden flex-shrink-0 border-2" className="relative w-14 h-14 rounded-xl overflow-hidden flex-shrink-0 border-2"
style={{ backgroundColor: `${diffColor}15`, borderColor: `${diffColor}40` }} style={{ backgroundColor: `${diffColor}15`, borderColor: `${diffColor}60` }}
> >
<img <img
src={getBotPortraitUrl(bot.id)} src={getBotPortraitUrl(bot.id)}
...@@ -96,45 +91,43 @@ export function BotSelectPage() { ...@@ -96,45 +91,43 @@ export function BotSelectPage() {
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none' }} onError={(e) => { (e.target as HTMLImageElement).style.display = 'none' }}
/> />
<div className="absolute inset-0 flex items-center justify-center" style={{ color: diffColor }}> <div className="absolute inset-0 flex items-center justify-center" style={{ color: diffColor }}>
<span className="text-xl font-bold">{bot.name_ar?.charAt(0) || bot.name.charAt(0)}</span> <span className="text-xl font-black">{bot.name_ar?.charAt(0) || bot.name.charAt(0)}</span>
</div> </div>
</div> </div>
{/* Info */}
<div className="flex-1 min-w-0 space-y-1.5"> <div className="flex-1 min-w-0 space-y-1.5">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<h3 className="text-sm font-bold truncate">{bot.name_ar}</h3> <h3 className="text-sm font-black truncate">{bot.name_ar}</h3>
<span <span
className="px-2 py-0.5 rounded-md text-[9px] font-bold" className="px-2 py-0.5 rounded-lg text-[9px] font-black"
style={{ backgroundColor: `${diffColor}20`, color: diffColor }} style={{ backgroundColor: `${diffColor}25`, color: diffColor }}
> >
{bot.style_ar} {bot.style_ar}
</span> </span>
</div> </div>
<p className="text-[11px] text-text-muted truncate leading-relaxed">{bot.bio_ar}</p> <p className="text-[11px] text-text-muted truncate font-semibold">{bot.bio_ar}</p>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<span className="text-[10px] text-text-muted">{bot.elo_min}-{bot.elo_max} تقييم</span> <span className="text-[10px] text-text-muted font-bold">{bot.elo_min}-{bot.elo_max}</span>
<div className="flex items-center gap-[3px]"> <div className="flex items-center gap-[3px]">
{Array.from({ length: TOTAL_STRENGTH_DOTS }).map((_, dotIndex) => ( {Array.from({ length: TOTAL_STRENGTH_DOTS }).map((_, dotIndex) => (
<div <div
key={dotIndex} key={dotIndex}
className="w-[5px] h-[5px] rounded-full" className="w-[6px] h-[6px] rounded-full"
style={{ backgroundColor: dotIndex < strengthLevel ? diffColor : `${diffColor}25` }} style={{ backgroundColor: dotIndex < strengthLevel ? diffColor : `${diffColor}20` }}
/> />
))} ))}
</div> </div>
</div> </div>
</div> </div>
{/* Check */}
{isSelected && ( {isSelected && (
<motion.div <motion.div
className="w-7 h-7 rounded-full bg-gold flex items-center justify-center flex-shrink-0" className="w-8 h-8 rounded-full bg-gold flex items-center justify-center flex-shrink-0"
initial={{ scale: 0 }} initial={{ scale: 0, rotate: -90 }}
animate={{ scale: 1 }} animate={{ scale: 1, rotate: 0 }}
transition={{ type: 'spring', stiffness: 500, damping: 20 }} transition={{ type: 'spring', stiffness: 500, damping: 15 }}
> >
<Swords size={12} className="text-background" /> <Swords size={14} className="text-background" />
</motion.div> </motion.div>
)} )}
</Card> </Card>
...@@ -143,17 +136,18 @@ export function BotSelectPage() { ...@@ -143,17 +136,18 @@ export function BotSelectPage() {
})} })}
</div> </div>
{/* CTA */}
{selectedBot && ( {selectedBot && (
<motion.div <motion.div
className="fixed bottom-0 left-0 right-0 px-5 pt-4 pb-10 bg-gradient-to-t from-background via-background/95 to-transparent" className="fixed bottom-0 left-0 right-0 px-5 pt-4 pb-10 bg-gradient-to-t from-background via-background/95 to-transparent"
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ type: 'spring', stiffness: 400, damping: 25 }} transition={{ type: 'spring', stiffness: 400, damping: 22 }}
> >
<Button onClick={startGame} className="w-full" size="lg"> <div className="app-container !p-0">
<Button onClick={startGame} className="w-[80%] mx-auto block" size="lg">
ابدا المباراة ابدا المباراة
</Button> </Button>
</div>
</motion.div> </motion.div>
)} )}
</PageTransition> </PageTransition>
......
import { useState } from 'react' import { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion' import { motion, AnimatePresence } from 'framer-motion'
import { UserPlus, UserCheck, UserX, Search, X, Loader2 } from 'lucide-react' import { UserPlus, UserCheck, UserX, Search, Loader2 } from 'lucide-react'
import { PageTransition } from '../components/layout/PageTransition' import { PageTransition } from '../components/layout/PageTransition'
import { Card } from '../components/ui/Card' import { Card } from '../components/ui/Card'
import { Button } from '../components/ui/Button' import { Button } from '../components/ui/Button'
...@@ -28,11 +28,20 @@ function relativeTime(date: string | null): string { ...@@ -28,11 +28,20 @@ function relativeTime(date: string | null): string {
return `${days} ي` return `${days} ي`
} }
const stagger = {
hidden: { opacity: 0 },
show: { opacity: 1, transition: { staggerChildren: 0.06 } },
}
const item = {
hidden: { opacity: 0, y: 12, scale: 0.95 },
show: { opacity: 1, y: 0, scale: 1, transition: { type: 'spring', stiffness: 400, damping: 22 } },
}
export function FriendsPage() { export function FriendsPage() {
const { user } = useAuthStore() const { user } = useAuthStore()
const { friends, pendingReceived, loading, sendRequest, acceptRequest, rejectRequest, removeFriend } = useFriends() const { friends, pendingReceived, loading, sendRequest, acceptRequest, rejectRequest, removeFriend } = useFriends()
const [showSearch, setShowSearch] = useState(false)
const [searchQuery, setSearchQuery] = useState('') const [searchQuery, setSearchQuery] = useState('')
const [searchResults, setSearchResults] = useState<SearchResult[]>([]) const [searchResults, setSearchResults] = useState<SearchResult[]>([])
const [searching, setSearching] = useState(false) const [searching, setSearching] = useState(false)
...@@ -64,79 +73,67 @@ export function FriendsPage() { ...@@ -64,79 +73,67 @@ export function FriendsPage() {
const onlineFriends = friends.filter((f) => f.profile.is_online) const onlineFriends = friends.filter((f) => f.profile.is_online)
const offlineFriends = friends.filter((f) => !f.profile.is_online) const offlineFriends = friends.filter((f) => !f.profile.is_online)
const stagger = {
hidden: { opacity: 0 },
show: { opacity: 1, transition: { staggerChildren: 0.05 } },
}
const item = {
hidden: { opacity: 0, y: 10 },
show: { opacity: 1, y: 0 },
}
return ( return (
<PageTransition className="px-7 py-7 flex flex-col gap-6"> <PageTransition>
<div className="flex flex-col gap-5 py-6">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h1 className="text-xl font-bold">الاصدقاء</h1> <h1 className="text-xl font-black">الأصدقاء</h1>
<motion.button <motion.div
className="p-2 rounded-lg bg-gold/10 border border-gold/20" className="p-2.5 rounded-xl bg-[#FFC83D]/10 border-3 border-[#FFC83D]/30"
whileTap={{ scale: 0.9 }} whileTap={{ scale: 0.85 }}
onClick={() => setShowSearch(!showSearch)}
> >
{showSearch ? <X size={18} className="text-gold" /> : <UserPlus size={18} className="text-gold" />} <UserPlus size={20} className="text-[#FFC83D]" />
</motion.button> </motion.div>
</div> </div>
<AnimatePresence> <div className="relative">
{showSearch && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
className="overflow-hidden"
>
<div className="relative mb-3">
<Search size={16} className="absolute right-3 top-1/2 -translate-y-1/2 text-text-muted" /> <Search size={16} className="absolute right-3 top-1/2 -translate-y-1/2 text-text-muted" />
<input <input
type="text" type="text"
placeholder="بحث عن لاعب..." placeholder="بحث عن لاعب..."
value={searchQuery} value={searchQuery}
onChange={(e) => handleSearch(e.target.value)} onChange={(e) => handleSearch(e.target.value)}
className="w-full pr-10 pl-4 py-2.5 rounded-xl bg-surface-2 border border-border text-sm placeholder:text-text-muted outline-none focus:border-gold/40" className="w-full pr-10 pl-4 py-3 rounded-xl bg-surface-2 border-3 border-border text-sm font-bold placeholder:text-text-muted outline-none focus:border-[#FFC83D]/50 transition-colors"
dir="rtl" dir="rtl"
autoFocus
/> />
</div> </div>
{searching && ( {searching && (
<div className="flex justify-center py-3"> <div className="flex justify-center py-4">
<Loader2 size={20} className="animate-spin text-text-muted" /> <Loader2 size={22} className="animate-spin text-[#FFC83D]" />
</div> </div>
)} )}
<AnimatePresence>
{searchResults.length > 0 && ( {searchResults.length > 0 && (
<motion.div className="flex flex-col gap-2 mb-4" variants={stagger} initial="hidden" animate="show"> <motion.div
className="flex flex-col gap-2"
variants={stagger}
initial="hidden"
animate="show"
exit={{ opacity: 0 }}
>
{searchResults.map((result) => { {searchResults.map((result) => {
const alreadyFriend = friends.some((f) => f.profile.id === result.id) const alreadyFriend = friends.some((f) => f.profile.id === result.id)
const alreadySent = sentIds.has(result.id) const alreadySent = sentIds.has(result.id)
return ( return (
<motion.div key={result.id} variants={item}> <motion.div key={result.id} variants={item}>
<Card className="!p-3 flex items-center gap-3"> <Card className="!p-3.5 flex items-center gap-3">
<div className="w-9 h-9 rounded-full bg-surface-3 border border-border flex items-center justify-center shrink-0"> <div className="w-10 h-10 rounded-full bg-gradient-to-br from-[#FFC83D]/20 to-[#B44DFF]/10 border-3 border-border flex items-center justify-center shrink-0">
<span className="text-sm font-bold text-gold"> <span className="text-sm font-black text-[#FFC83D]">
{result.display_name?.[0] || result.username[0]} {result.display_name?.[0] || result.username[0]}
</span> </span>
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-sm font-semibold truncate">{result.display_name || result.username}</p> <p className="text-sm font-black truncate">{result.display_name || result.username}</p>
<p className="text-xs text-text-muted">{result.elo_blitz}</p> <p className="text-xs text-text-muted font-bold">{result.elo_blitz}</p>
</div> </div>
{alreadyFriend ? ( {alreadyFriend ? (
<UserCheck size={16} className="text-cyan shrink-0" /> <UserCheck size={18} className="text-[#00E5CC] shrink-0" />
) : alreadySent ? ( ) : alreadySent ? (
<span className="text-xs text-text-muted">تم الارسال</span> <span className="text-xs font-bold text-text-muted">تم الارسال</span>
) : ( ) : (
<Button size="sm" variant="ghost" onClick={() => handleSendRequest(result.id)}> <Button size="sm" variant="gold" onClick={() => handleSendRequest(result.id)}>
اضافة اضافة
</Button> </Button>
)} )}
...@@ -146,113 +143,98 @@ export function FriendsPage() { ...@@ -146,113 +143,98 @@ export function FriendsPage() {
})} })}
</motion.div> </motion.div>
)} )}
</motion.div>
)}
</AnimatePresence> </AnimatePresence>
{loading ? ( {loading ? (
<div className="flex-1 flex items-center justify-center"> <div className="flex-1 flex items-center justify-center py-12">
<Loader2 size={24} className="animate-spin text-text-muted" /> <Loader2 size={28} className="animate-spin text-[#FFC83D]" />
</div> </div>
) : ( ) : (
<> <motion.div className="flex flex-col gap-5" variants={stagger} initial="hidden" animate="show">
{pendingReceived.length > 0 && ( {pendingReceived.length > 0 && (
<motion.div className="flex flex-col gap-2" variants={item}>
<h2 className="text-sm font-black text-[#FFC83D]">طلبات الصداقة ({pendingReceived.length})</h2>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<h2 className="text-sm font-semibold text-text-muted">طلبات الصداقة</h2>
<motion.div className="flex flex-col gap-2" variants={stagger} initial="hidden" animate="show">
{pendingReceived.map((req) => ( {pendingReceived.map((req) => (
<motion.div key={req.id} variants={item}> <Card key={req.id} className="!p-3.5 flex items-center gap-3">
<Card className="!p-3 flex items-center gap-3"> <div className="w-10 h-10 rounded-full bg-gradient-to-br from-[#FFC83D]/20 to-[#B44DFF]/10 border-3 border-[#FFC83D]/40 flex items-center justify-center shrink-0">
<div className="w-9 h-9 rounded-full bg-surface-3 border border-gold/30 flex items-center justify-center shrink-0"> <span className="text-sm font-black text-[#FFC83D]">
<span className="text-sm font-bold text-gold">
{req.profile.display_name?.[0] || req.profile.username[0]} {req.profile.display_name?.[0] || req.profile.username[0]}
</span> </span>
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-sm font-semibold truncate"> <p className="text-sm font-black truncate">
{req.profile.display_name || req.profile.username} {req.profile.display_name || req.profile.username}
</p> </p>
<p className="text-xs text-text-muted">{req.profile.elo_blitz}</p> <p className="text-xs text-text-muted font-bold">{req.profile.elo_blitz}</p>
</div> </div>
<div className="flex gap-1.5"> <div className="flex gap-2">
<motion.button <Button size="sm" variant="cyan" onClick={() => acceptRequest(req.id)}>
className="p-1.5 rounded-lg bg-cyan/10 border border-cyan/20" قبول
whileTap={{ scale: 0.85 }} </Button>
onClick={() => acceptRequest(req.id)} <Button size="sm" variant="coral" onClick={() => rejectRequest(req.id)}>
> رفض
<UserCheck size={14} className="text-cyan" /> </Button>
</motion.button>
<motion.button
className="p-1.5 rounded-lg bg-coral/10 border border-coral/20"
whileTap={{ scale: 0.85 }}
onClick={() => rejectRequest(req.id)}
>
<UserX size={14} className="text-coral" />
</motion.button>
</div> </div>
</Card> </Card>
</motion.div>
))} ))}
</motion.div>
</div> </div>
</motion.div>
)} )}
{onlineFriends.length > 0 && ( {onlineFriends.length > 0 && (
<div className="flex flex-col gap-2"> <motion.div className="flex flex-col gap-2" variants={item}>
<h2 className="text-sm font-semibold text-cyan"> <h2 className="text-sm font-black text-[#00E5CC]">
متصل ({onlineFriends.length}) متصل ({onlineFriends.length})
</h2> </h2>
<motion.div className="flex flex-col gap-2" variants={stagger} initial="hidden" animate="show"> <div className="flex flex-col gap-2">
{onlineFriends.map((friend) => ( {onlineFriends.map((friend) => (
<motion.div key={friend.id} variants={item}> <Card key={friend.id} className="!p-3.5 flex items-center gap-3">
<Card className="!p-3 flex items-center gap-3">
<div className="relative"> <div className="relative">
<div className="w-9 h-9 rounded-full bg-surface-3 border border-border flex items-center justify-center"> <div className="w-10 h-10 rounded-full bg-gradient-to-br from-[#00E5CC]/15 to-surface-3 border-3 border-border flex items-center justify-center">
<span className="text-sm font-bold text-gold"> <span className="text-sm font-black text-[#FFC83D]">
{friend.profile.display_name?.[0] || friend.profile.username[0]} {friend.profile.display_name?.[0] || friend.profile.username[0]}
</span> </span>
</div> </div>
<div className="absolute -bottom-0.5 -left-0.5 w-3 h-3 rounded-full bg-cyan border-2 border-surface-1" /> <div className="absolute -bottom-0.5 -left-0.5 w-3.5 h-3.5 rounded-full bg-[#4ADE80] border-2 border-surface-1 animate-pulse" />
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-sm font-semibold truncate"> <p className="text-sm font-black truncate">
{friend.profile.display_name || friend.profile.username} {friend.profile.display_name || friend.profile.username}
</p> </p>
<p className="text-xs text-text-muted">{friend.profile.elo_blitz}</p> <p className="text-xs text-text-muted font-bold">{friend.profile.elo_blitz}</p>
</div> </div>
<motion.button <motion.button
className="p-1.5 rounded-lg bg-coral/10 border border-coral/20 opacity-0 group-hover:opacity-100" className="p-2 rounded-xl bg-[#FF5252]/10 border-2 border-[#FF5252]/20"
whileTap={{ scale: 0.85 }} whileTap={{ scale: 0.8 }}
onClick={() => removeFriend(friend.id)} onClick={() => removeFriend(friend.id)}
> >
<UserX size={14} className="text-coral" /> <UserX size={14} className="text-[#FF5252]" />
</motion.button> </motion.button>
</Card> </Card>
</motion.div>
))} ))}
</motion.div>
</div> </div>
</motion.div>
)} )}
{offlineFriends.length > 0 && ( {offlineFriends.length > 0 && (
<div className="flex flex-col gap-2"> <motion.div className="flex flex-col gap-2" variants={item}>
<h2 className="text-sm font-semibold text-text-muted"> <h2 className="text-sm font-black text-text-muted">
غير متصل ({offlineFriends.length}) غير متصل ({offlineFriends.length})
</h2> </h2>
<motion.div className="flex flex-col gap-2" variants={stagger} initial="hidden" animate="show"> <div className="flex flex-col gap-2">
{offlineFriends.map((friend) => ( {offlineFriends.map((friend) => (
<motion.div key={friend.id} variants={item}> <Card key={friend.id} className="!p-3.5 flex items-center gap-3 opacity-70">
<Card className="!p-3 flex items-center gap-3">
<div className="relative"> <div className="relative">
<div className="w-9 h-9 rounded-full bg-surface-3 border border-border flex items-center justify-center opacity-60"> <div className="w-10 h-10 rounded-full bg-surface-3 border-3 border-border flex items-center justify-center">
<span className="text-sm font-bold text-text-muted"> <span className="text-sm font-black text-text-muted">
{friend.profile.display_name?.[0] || friend.profile.username[0]} {friend.profile.display_name?.[0] || friend.profile.username[0]}
</span> </span>
</div> </div>
<div className="absolute -bottom-0.5 -left-0.5 w-3 h-3 rounded-full bg-text-muted/50 border-2 border-surface-1" /> <div className="absolute -bottom-0.5 -left-0.5 w-3.5 h-3.5 rounded-full bg-text-muted/40 border-2 border-surface-1" />
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-sm font-semibold truncate opacity-60"> <p className="text-sm font-bold truncate">
{friend.profile.display_name || friend.profile.username} {friend.profile.display_name || friend.profile.username}
</p> </p>
<p className="text-xs text-text-muted"> <p className="text-xs text-text-muted">
...@@ -260,35 +242,40 @@ export function FriendsPage() { ...@@ -260,35 +242,40 @@ export function FriendsPage() {
</p> </p>
</div> </div>
<motion.button <motion.button
className="p-1.5 rounded-lg bg-coral/10 border border-coral/20" className="p-2 rounded-xl bg-[#FF5252]/10 border-2 border-[#FF5252]/20"
whileTap={{ scale: 0.85 }} whileTap={{ scale: 0.8 }}
onClick={() => removeFriend(friend.id)} onClick={() => removeFriend(friend.id)}
> >
<UserX size={14} className="text-coral" /> <UserX size={14} className="text-[#FF5252]" />
</motion.button> </motion.button>
</Card> </Card>
</motion.div>
))} ))}
</motion.div>
</div> </div>
</motion.div>
)} )}
{friends.length === 0 && pendingReceived.length === 0 && ( {friends.length === 0 && pendingReceived.length === 0 && (
<div className="flex-1 flex flex-col items-center justify-center py-12">
<motion.div <motion.div
className="w-16 h-16 rounded-full bg-surface-2 border border-border flex items-center justify-center mb-4" className="flex flex-col items-center justify-center py-16 gap-4"
initial={{ scale: 0 }} variants={item}
animate={{ scale: 1 }} >
transition={{ type: 'spring', stiffness: 300, damping: 20 }} <motion.div
className="w-20 h-20 rounded-full bg-surface-2 border-3 border-border flex items-center justify-center"
initial={{ scale: 0, rotate: -15 }}
animate={{ scale: 1, rotate: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 18 }}
> >
<UserPlus size={24} className="text-text-muted" /> <UserPlus size={32} className="text-text-muted" />
</motion.div> </motion.div>
<p className="text-text-muted text-sm">لا يوجد اصدقاء بعد</p> <div className="text-center">
<p className="text-text-muted text-xs mt-1">ابحث عن لاعبين لاضافتهم</p> <p className="text-text-muted font-black text-base">لا يوجد اصدقاء بعد</p>
<p className="text-text-muted text-xs font-bold mt-1">ابحث عن لاعبين لاضافتهم</p>
</div> </div>
</motion.div>
)} )}
</> </motion.div>
)} )}
</div>
</PageTransition> </PageTransition>
) )
} }
import { useState } from 'react' import { useState } from 'react'
import { motion } from 'framer-motion' import { motion } from 'framer-motion'
import { Play, TrendingUp, Swords, Flame, Grid3X3, Bot, Users, Lightbulb, Crown, Trophy, X, Minus, ArrowUpRight, ArrowDownRight } from 'lucide-react' import { Play, TrendingUp, Swords, Flame, Bot, Users, Lightbulb, Crown } from 'lucide-react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { useAuthStore } from '../stores/authStore' import { useAuthStore } from '../stores/authStore'
import { PageTransition } from '../components/layout/PageTransition' import { PageTransition } from '../components/layout/PageTransition'
import { Card } from '../components/ui/Card'
import { DailyRewardModal } from '../components/DailyRewardModal' import { DailyRewardModal } from '../components/DailyRewardModal'
import { useDailyReward } from '../hooks/useDailyReward' import { useDailyReward } from '../hooks/useDailyReward'
import { useRecentGames } from '../hooks/useRecentGames' import { playSound } from '../lib/sounds'
const dailyTips = [ const dailyTips = [
'تحكم بالمركز في بداية اللعبة - الاحصنة والفيلة تكون اقوى من المركز', 'تحكم بالمركز في بداية اللعبة - الاحصنة والفيلة تكون اقوى من المركز',
...@@ -18,13 +19,24 @@ const dailyTips = [ ...@@ -18,13 +19,24 @@ const dailyTips = [
'فكر في خطة خصمك قبل ان تلعب - لا تركز فقط على هجومك', 'فكر في خطة خصمك قبل ان تلعب - لا تركز فقط على هجومك',
] ]
const stagger = {
hidden: {},
show: {
transition: { staggerChildren: 0.06 },
},
}
const fadeUp = {
hidden: { opacity: 0, y: 20 },
show: { opacity: 1, y: 0, transition: { type: 'spring', stiffness: 500, damping: 22 } },
}
export function HomePage() { export function HomePage() {
const navigate = useNavigate() const navigate = useNavigate()
const { user, profile } = useAuthStore() const { profile } = useAuthStore()
const todayTip = dailyTips[new Date().getDay()] const todayTip = dailyTips[new Date().getDay()]
const { canClaim, streak, reward, claim, loading: dailyLoading } = useDailyReward() const { canClaim, streak, reward, claim, loading: dailyLoading } = useDailyReward()
const [showDailyModal, setShowDailyModal] = useState(true) const [showDailyModal, setShowDailyModal] = useState(true)
const { games, loading: gamesLoading } = useRecentGames(user?.id, 3)
async function handleClaimReward() { async function handleClaimReward() {
const result = await claim() const result = await claim()
...@@ -34,7 +46,7 @@ export function HomePage() { ...@@ -34,7 +46,7 @@ export function HomePage() {
} }
return ( return (
<PageTransition className="px-7 py-7 flex flex-col gap-8"> <PageTransition>
<DailyRewardModal <DailyRewardModal
open={canClaim && showDailyModal} open={canClaim && showDailyModal}
streak={streak} streak={streak}
...@@ -43,212 +55,160 @@ export function HomePage() { ...@@ -43,212 +55,160 @@ export function HomePage() {
onClaim={handleClaimReward} onClaim={handleClaimReward}
onClose={() => setShowDailyModal(false)} onClose={() => setShowDailyModal(false)}
/> />
<motion.div
variants={stagger}
initial="hidden"
animate="show"
className="flex flex-col gap-6"
>
{profile && ( {profile && (
<motion.div <motion.div
variants={fadeUp}
className="flex items-center gap-4" className="flex items-center gap-4"
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
> >
<div className="w-14 h-14 rounded-2xl bg-gradient-to-br from-gold/20 to-purple/20 border-2 border-gold/30 flex items-center justify-center"> <div className="w-16 h-16 rounded-2xl bg-gradient-to-br from-gold/25 to-purple/25 border-3 border-gold/40 flex items-center justify-center">
<span className="text-xl font-bold text-gold"> <span className="text-2xl font-black text-gold">
{profile.display_name?.charAt(0) || 'L'} {profile.display_name?.charAt(0) || 'L'}
</span> </span>
</div> </div>
<div> <div>
<h2 className="text-xl font-bold">اهلا، {profile.display_name}</h2> <h2 className="text-2xl font-black">اهلا، {profile.display_name}</h2>
<p className="text-xs text-text-muted mt-0.5">المستوى {profile.level}</p> <div className="flex items-center gap-2 mt-1">
<span className="px-2.5 py-0.5 rounded-lg bg-gold/15 border-2 border-gold/30 text-[11px] font-bold text-gold">
المستوى {profile.level}
</span>
</div>
</div> </div>
</motion.div> </motion.div>
)} )}
<motion.div variants={fadeUp} className="flex justify-center">
<motion.button <motion.button
onClick={() => navigate('/play')} onClick={() => {
className="relative w-full py-9 rounded-[20px] bg-gradient-to-bl from-gold via-gold-light to-gold overflow-hidden shadow-2xl shadow-gold/30" playSound('click')
whileTap={{ scale: 0.96 }} navigate('/play')
}}
className="relative w-[85%] py-10 rounded-[22px] bg-gradient-to-b from-gold-light to-gold overflow-hidden border-b-4 border-gold-muted shadow-2xl shadow-gold/40"
whileTap={{ scale: 0.93, y: 3 }}
whileHover={{ scale: 1.03 }}
transition={{ type: 'spring', stiffness: 600, damping: 20 }}
> >
<motion.div <motion.div
className="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent" className="absolute inset-0 rounded-[22px] border-[3px] border-gold/60 animate-pulse-glow"
/>
<motion.div
className="absolute inset-0 bg-gradient-to-r from-transparent via-white/25 to-transparent"
animate={{ x: ['-200%', '200%'] }} animate={{ x: ['-200%', '200%'] }}
transition={{ duration: 2.5, repeat: Infinity, ease: 'linear' }} transition={{ duration: 2.5, repeat: Infinity, ease: 'linear' }}
/> />
<div className="relative flex flex-col items-center gap-3"> <div className="relative flex flex-col items-center gap-3">
<motion.div <motion.div
animate={{ scale: [1, 1.08, 1] }} animate={{ scale: [1, 1.12, 1] }}
transition={{ duration: 2, repeat: Infinity, ease: 'easeInOut' }} transition={{ duration: 1.8, repeat: Infinity, ease: 'easeInOut' }}
> >
<Play size={42} className="text-background" fill="currentColor" /> <Play size={48} className="text-background" fill="currentColor" />
</motion.div> </motion.div>
<span className="text-2xl font-black text-background">العب الان</span> <span className="text-3xl font-black text-background uppercase tracking-wide">العب الان</span>
</div> </div>
</motion.button> </motion.button>
</motion.div>
{profile && ( {profile && (
<div className="grid grid-cols-3 gap-3"> <motion.div variants={fadeUp} className="grid grid-cols-3 gap-3">
<StatCard icon={<Swords size={20} className="text-cyan" />} value={profile.total_games_played} label="مباراة" delay={0.2} accent="cyan" /> <StatCard
<StatCard icon={<TrendingUp size={20} className="text-gold" />} value={profile.elo_blitz} label="تقييم" delay={0.3} accent="gold" /> icon={<TrendingUp size={22} className="text-gold" />}
<StatCard icon={<Flame size={20} className="text-coral" />} value={profile.win_streak} label="سلسلة فوز" delay={0.4} accent="coral" /> value={profile.elo_blitz}
</div> label="تقييم"
accent="gold"
/>
<StatCard
icon={<Swords size={22} className="text-cyan" />}
value={profile.total_games_played}
label="مباراة"
accent="cyan"
/>
<StatCard
icon={<Flame size={22} className="text-coral" />}
value={profile.win_streak}
label="سلسلة فوز"
accent="coral"
/>
</motion.div>
)} )}
<div className="grid grid-cols-2 gap-3"> <motion.div variants={fadeUp} className="grid grid-cols-2 gap-3">
<motion.button <Card
onClick={() => navigate('/bot-select')} variant="default"
className="flex flex-col items-center gap-3 p-6 rounded-2xl bg-surface-1 border border-border" className="flex flex-col items-center gap-3 !p-5"
initial={{ opacity: 0, y: 10 }} onClick={() => {
animate={{ opacity: 1, y: 0 }} playSound('click')
transition={{ delay: 0.3 }} navigate('/bot-select')
whileTap={{ scale: 0.96 }} }}
> >
<div className="w-12 h-12 rounded-2xl bg-purple/10 border border-purple/20 flex items-center justify-center"> <div className="w-14 h-14 rounded-2xl bg-purple/15 border-3 border-purple/30 flex items-center justify-center">
<Bot size={24} className="text-purple" /> <Bot size={28} className="text-purple" />
</div> </div>
<div className="text-center"> <div className="text-center">
<p className="text-sm font-bold">العب ضد روبوت</p> <p className="text-sm font-black">العب ضد روبوت</p>
<p className="text-[10px] text-text-muted mt-0.5">تدريب وتحسين</p> <p className="text-[10px] text-text-muted mt-1">تدريب وتحسين</p>
</div> </div>
</motion.button> </Card>
<motion.button <Card
onClick={() => navigate('/friends')} variant="default"
className="flex flex-col items-center gap-3 p-6 rounded-2xl bg-surface-1 border border-border" className="flex flex-col items-center gap-3 !p-5"
initial={{ opacity: 0, y: 10 }} onClick={() => {
animate={{ opacity: 1, y: 0 }} playSound('click')
transition={{ delay: 0.4 }} navigate('/friends')
whileTap={{ scale: 0.96 }} }}
> >
<div className="w-12 h-12 rounded-2xl bg-gold/10 border border-gold/20 flex items-center justify-center"> <div className="w-14 h-14 rounded-2xl bg-gold/15 border-3 border-gold/30 flex items-center justify-center">
<Users size={24} className="text-gold" /> <Users size={28} className="text-gold" />
</div> </div>
<div className="text-center"> <div className="text-center">
<p className="text-sm font-bold">تحدى صديق</p> <p className="text-sm font-black">تحدى صديق</p>
<p className="text-[10px] text-text-muted mt-0.5">ارسل دعوة</p> <p className="text-[10px] text-text-muted mt-1">ارسل دعوة</p>
</div>
</motion.button>
</div>
<div>
<div className="flex items-center justify-between mb-4">
<h3 className="text-sm font-bold text-text-muted">اخر المباريات</h3>
{games.length > 0 && (
<motion.button
onClick={() => navigate('/profile')}
className="text-[11px] font-bold text-gold"
whileTap={{ scale: 0.95 }}
>
عرض الكل
</motion.button>
)}
</div>
{gamesLoading ? (
<div className="flex items-center justify-center py-8">
<div className="w-6 h-6 border-2 border-gold/30 border-t-gold rounded-full animate-spin" />
</div>
) : games.length === 0 ? (
<div className="flex flex-col items-center py-10 gap-4 rounded-2xl bg-surface-1 border border-border">
<motion.div
className="w-14 h-14 rounded-2xl bg-surface-3/60 flex items-center justify-center"
animate={{ rotate: [0, 3, -3, 0] }}
transition={{ duration: 5, repeat: Infinity }}
>
<Grid3X3 size={28} className="text-text-muted/40" />
</motion.div>
<div className="text-center space-y-1">
<p className="text-sm font-semibold text-text-secondary">لا توجد مباريات بعد</p>
<p className="text-xs text-text-muted">العب اول مباراة وابدا رحلتك</p>
</div>
<motion.button
onClick={() => navigate('/play')}
className="px-6 py-2.5 rounded-xl bg-gold/10 border border-gold/30 text-gold text-sm font-bold"
whileTap={{ scale: 0.95 }}
>
ابدا الان
</motion.button>
</div>
) : (
<div className="flex flex-col gap-2">
{games.map((game, i) => (
<motion.div
key={game.id}
className="flex items-center justify-between p-3.5 rounded-xl bg-surface-1 border border-border/80"
initial={{ opacity: 0, x: 10 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.3 + 0.05 * i }}
>
<div className="flex items-center gap-3">
<HomeResultBadge result={game.result} />
<div>
<p className="text-sm font-semibold">{game.opponent}</p>
<p className="text-[10px] text-text-muted">{game.timeControl}</p>
</div> </div>
</div> </Card>
<HomeRatingBadge change={game.ratingChange} />
</motion.div> </motion.div>
))}
</div>
)}
</div>
<div className="p-5 rounded-2xl bg-surface-1 border border-border relative overflow-hidden"> <motion.div variants={fadeUp}>
<div className="absolute top-0 right-0 w-24 h-24 bg-gradient-to-bl from-gold/6 to-transparent rounded-bl-full" /> <Card variant="gold" className="relative overflow-hidden">
<div className="absolute top-0 right-0 w-28 h-28 bg-gradient-to-bl from-gold/10 to-transparent rounded-bl-full" />
<div className="flex items-start gap-4"> <div className="flex items-start gap-4">
<div className="w-10 h-10 rounded-xl bg-gold/10 flex items-center justify-center flex-shrink-0"> <div className="w-12 h-12 rounded-xl bg-gold/15 border-3 border-gold/30 flex items-center justify-center flex-shrink-0">
<Lightbulb size={20} className="text-gold" /> <Lightbulb size={22} className="text-gold" />
</div> </div>
<div className="flex-1 pt-0.5"> <div className="flex-1 pt-0.5">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<h4 className="text-xs font-bold text-gold">نصيحة اليوم</h4> <h4 className="text-xs font-black text-gold uppercase">نصيحة اليوم</h4>
<Crown size={11} className="text-gold/40" /> <Crown size={12} className="text-gold/50" />
</div>
<p className="text-[12px] text-text-secondary leading-[1.7]">{todayTip}</p>
</div> </div>
<p className="text-[12px] text-text-secondary leading-[1.8]">{todayTip}</p>
</div> </div>
</div> </div>
</Card>
</motion.div>
</motion.div>
</PageTransition> </PageTransition>
) )
} }
function StatCard({ icon, value, label, delay, accent }: { icon: React.ReactNode; value: number; label: string; delay: number; accent: string }) { function StatCard({ icon, value, label, accent }: { icon: React.ReactNode; value: number; label: string; accent: string }) {
const borderMap: Record<string, string> = { cyan: 'border-cyan/20', gold: 'border-gold/20', coral: 'border-coral/20' } const borderMap: Record<string, string> = {
cyan: 'border-t-cyan',
gold: 'border-t-gold',
coral: 'border-t-coral',
}
return ( return (
<motion.div <div
className={`flex flex-col items-center gap-2 p-4 rounded-2xl bg-surface-1 border ${borderMap[accent] || 'border-border'}`} className={`flex flex-col items-center gap-2 p-4 rounded-2xl bg-surface-1 border-3 border-border ${borderMap[accent] || ''}`}
initial={{ opacity: 0, y: 10 }} style={{ borderTopWidth: '4px' }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay }}
> >
{icon} {icon}
<span className="text-xl font-black">{value}</span> <span className="text-2xl font-black">{value}</span>
<span className="text-[10px] text-text-muted font-medium">{label}</span> <span className="text-[10px] text-text-muted font-bold">{label}</span>
</motion.div>
)
}
function HomeResultBadge({ result }: { result: 'win' | 'loss' | 'draw' }) {
const config = {
win: { icon: <Trophy size={14} />, bg: 'bg-cyan/15 text-cyan' },
loss: { icon: <X size={14} />, bg: 'bg-coral/15 text-coral' },
draw: { icon: <Minus size={14} />, bg: 'bg-text-muted/15 text-text-muted' },
}
const c = config[result]
return (
<div className={`w-8 h-8 rounded-lg flex items-center justify-center ${c.bg}`}>
{c.icon}
</div> </div>
) )
} }
function HomeRatingBadge({ change }: { change: number }) {
if (change === 0) return <span className="text-xs text-text-muted font-medium">+0</span>
if (change > 0) {
return (
<span className="flex items-center gap-0.5 text-xs font-bold text-cyan">
<ArrowUpRight size={12} />+{change}
</span>
)
}
return (
<span className="flex items-center gap-0.5 text-xs font-bold text-coral">
<ArrowDownRight size={12} />{change}
</span>
)
}
...@@ -21,11 +21,21 @@ const PERIODS: { label: string; value: Period }[] = [ ...@@ -21,11 +21,21 @@ const PERIODS: { label: string; value: Period }[] = [
{ label: 'الكل', value: 'all_time' }, { label: 'الكل', value: 'all_time' },
] ]
function Avatar({ name, size = 44 }: { name: string; size?: number }) { function Avatar({ name, url, size = 44 }: { name: string; url?: string | null; size?: number }) {
if (url) {
return (
<img
src={url}
alt={name}
className="rounded-full object-cover border-2 border-border"
style={{ width: size, height: size }}
/>
)
}
return ( return (
<div <div
className="rounded-full bg-gradient-to-br from-surface-3 to-surface-2 flex items-center justify-center font-bold text-text-secondary" className="rounded-full bg-gradient-to-br from-surface-3 to-surface-2 flex items-center justify-center font-black text-text-secondary border-2 border-border"
style={{ width: size, height: size, fontSize: size * 0.4 }} style={{ width: size, height: size, fontSize: size * 0.38 }}
> >
{name?.charAt(0) || '?'} {name?.charAt(0) || '?'}
</div> </div>
...@@ -42,8 +52,11 @@ export function LeaderboardPage() { ...@@ -42,8 +52,11 @@ export function LeaderboardPage() {
const rest = entries.slice(3) const rest = entries.slice(3)
return ( return (
<PageTransition className="px-7 py-7 flex flex-col gap-5"> <PageTransition className="flex flex-col gap-5">
<h1 className="text-xl font-bold">لوحة المتصدرين</h1> <div className="flex items-center gap-2.5">
<Trophy size={24} className="text-[#FFC83D]" />
<h1 className="text-2xl font-black">لوحة المتصدرين</h1>
</div>
<div className="flex gap-2 overflow-x-auto no-scrollbar pb-1"> <div className="flex gap-2 overflow-x-auto no-scrollbar pb-1">
{TIME_CONTROLS.map((tc) => ( {TIME_CONTROLS.map((tc) => (
...@@ -51,11 +64,12 @@ export function LeaderboardPage() { ...@@ -51,11 +64,12 @@ export function LeaderboardPage() {
key={tc.value} key={tc.value}
whileTap={{ scale: 0.93 }} whileTap={{ scale: 0.93 }}
onClick={() => setTimeControl(tc.value)} onClick={() => setTimeControl(tc.value)}
className={`px-4 py-1.5 rounded-full text-xs font-bold whitespace-nowrap transition-colors ${ className={`px-4 py-2 rounded-xl text-xs font-bold whitespace-nowrap border-2 transition-colors ${
timeControl === tc.value timeControl === tc.value
? 'bg-gold text-bg-primary' ? 'bg-[#FFC83D] border-[#FFC83D] text-background font-black'
: 'bg-surface-2 text-text-muted border border-border' : 'bg-surface-2 text-text-muted border-border hover:border-[#FFC83D]/40'
}`} }`}
transition={{ type: 'spring', stiffness: 500, damping: 20 }}
> >
{tc.label} {tc.label}
</motion.button> </motion.button>
...@@ -68,11 +82,12 @@ export function LeaderboardPage() { ...@@ -68,11 +82,12 @@ export function LeaderboardPage() {
key={p.value} key={p.value}
whileTap={{ scale: 0.93 }} whileTap={{ scale: 0.93 }}
onClick={() => setPeriod(p.value)} onClick={() => setPeriod(p.value)}
className={`px-3 py-1 rounded-full text-[11px] font-bold whitespace-nowrap transition-colors ${ className={`px-3.5 py-1.5 rounded-xl text-[11px] font-bold whitespace-nowrap border-2 transition-colors ${
period === p.value period === p.value
? 'bg-surface-3 text-text-primary border border-gold/40' ? 'bg-[#B44DFF]/15 text-[#B44DFF] border-[#B44DFF]/50 font-black'
: 'bg-surface-1 text-text-muted border border-border' : 'bg-surface-1 text-text-muted border-border hover:border-[#B44DFF]/30'
}`} }`}
transition={{ type: 'spring', stiffness: 500, damping: 20 }}
> >
{p.label} {p.label}
</motion.button> </motion.button>
...@@ -81,48 +96,53 @@ export function LeaderboardPage() { ...@@ -81,48 +96,53 @@ export function LeaderboardPage() {
{loading ? ( {loading ? (
<div className="flex flex-col items-center py-16"> <div className="flex flex-col items-center py-16">
<div className="w-10 h-10 rounded-full border-2 border-gold border-t-transparent animate-spin" /> <motion.div
className="w-10 h-10 border-3 border-[#FFC83D]/30 border-t-[#FFC83D] rounded-full"
animate={{ rotate: 360 }}
transition={{ duration: 0.8, repeat: Infinity, ease: 'linear' }}
/>
</div> </div>
) : entries.length === 0 ? ( ) : entries.length === 0 ? (
<div className="flex flex-col items-center justify-center py-16 gap-4"> <div className="flex flex-col items-center justify-center py-16 gap-4">
<motion.div <motion.div
className="w-16 h-16 rounded-full bg-surface-2 border border-border flex items-center justify-center" className="w-20 h-20 rounded-2xl bg-surface-2 border-3 border-[#FFC83D]/30 flex items-center justify-center"
initial={{ scale: 0 }} initial={{ scale: 0, rotate: -10 }}
animate={{ scale: 1 }} animate={{ scale: 1, rotate: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 20 }} transition={{ type: 'spring', stiffness: 300, damping: 18 }}
> >
<Trophy size={24} className="text-gold" /> <Trophy size={32} className="text-[#FFC83D]" />
</motion.div> </motion.div>
<p className="text-text-muted text-sm">لا توجد بيانات بعد</p> <p className="text-text-muted text-sm font-bold">لا توجد بيانات بعد</p>
<p className="text-text-muted text-xs">العب مباريات لتظهر في القائمة</p> <p className="text-text-muted text-xs">العب مباريات لتظهر في القائمة</p>
</div> </div>
) : ( ) : (
<> <>
{top3.length > 0 && <Podium entries={top3} currentUserId={user?.id} />} {top3.length > 0 && <Podium entries={top3} currentUserId={user?.id} />}
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2.5">
{rest.map((entry, i) => ( {rest.map((entry, i) => (
<motion.div <motion.div
key={entry.player_id} key={entry.player_id}
initial={{ opacity: 0, x: -10 }} initial={{ opacity: 0, x: -12 }}
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.3 + i * 0.03 }} transition={{ type: 'spring', stiffness: 400, damping: 24, delay: 0.3 + i * 0.04 }}
className={`flex items-center gap-3 p-3 rounded-xl bg-surface-1 border ${ className={`flex items-center gap-3 p-3.5 rounded-2xl bg-surface-1 border-3 ${
entry.player_id === user?.id ? 'border-gold/50 bg-gold/5' : 'border-border' entry.player_id === user?.id
? 'border-[#FFC83D]/60 bg-[#FFC83D]/10'
: 'border-border'
}`} }`}
> >
<span className="w-7 text-center text-sm font-bold text-text-muted"> <span className="w-8 text-center text-sm font-black text-text-muted">
{entry.rank} {entry.rank}
</span> </span>
<Avatar name={entry.display_name} size={36} /> <Avatar name={entry.display_name} url={entry.avatar_url} size={38} />
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-sm font-bold truncate">{entry.display_name}</p> <p className="text-sm font-black truncate">{entry.display_name}</p>
<p className="text-[10px] text-text-muted"> <p className="text-[10px] text-text-muted font-bold">
{entry.games_played} مباراة &middot; {entry.win_rate}% فوز {entry.games_played} مباراة
{entry.country_code && ` &middot; ${entry.country_code}`}
</p> </p>
</div> </div>
<span className="text-sm font-bold text-gold">{entry.rating}</span> <span className="text-sm font-black text-[#FFC83D]">{entry.rating}</span>
</motion.div> </motion.div>
))} ))}
</div> </div>
...@@ -136,19 +156,34 @@ function Podium({ ...@@ -136,19 +156,34 @@ function Podium({
entries, entries,
currentUserId, currentUserId,
}: { }: {
entries: { rank: number; player_id: string; rating: number; games_played: number; display_name: string }[] entries: { rank: number; player_id: string; rating: number; games_played: number; display_name: string; avatar_url?: string | null }[]
currentUserId?: string currentUserId?: string
}) { }) {
const ordered = [entries[1], entries[0], entries[2]].filter(Boolean) const ordered = [entries[1], entries[0], entries[2]].filter(Boolean)
const podiumConfig = [ const podiumConfig = [
{ color: 'text-[#C0C0C0]', bg: 'bg-[#C0C0C0]/10', border: 'border-[#C0C0C0]/30', size: 52, label: '2' }, {
{ color: 'text-gold', bg: 'bg-gold/10', border: 'border-gold/30', size: 64, label: '1' }, color: '#C0C0C0',
{ color: 'text-[#CD7F32]', bg: 'bg-[#CD7F32]/10', border: 'border-[#CD7F32]/30', size: 48, label: '3' }, height: 'h-24',
avatarSize: 52,
rankIcon: <Medal size={16} className="text-[#C0C0C0]" />,
},
{
color: '#FFC83D',
height: 'h-32',
avatarSize: 68,
rankIcon: <Crown size={22} className="text-[#FFC83D] fill-[#FFC83D]/20" />,
},
{
color: '#CD7F32',
height: 'h-20',
avatarSize: 48,
rankIcon: <Medal size={16} className="text-[#CD7F32]" />,
},
] ]
return ( return (
<div className="flex items-end justify-center gap-4 py-4"> <div className="flex items-end justify-center gap-3 py-4">
{ordered.map((entry, i) => { {ordered.map((entry, i) => {
if (!entry) return null if (!entry) return null
const config = podiumConfig[i] const config = podiumConfig[i]
...@@ -158,34 +193,47 @@ function Podium({ ...@@ -158,34 +193,47 @@ function Podium({
<motion.div <motion.div
key={entry.player_id} key={entry.player_id}
className="flex flex-col items-center gap-2" className="flex flex-col items-center gap-2"
initial={{ scale: 0, opacity: 0 }} initial={{ scale: 0, opacity: 0, y: 30 }}
animate={{ scale: 1, opacity: 1 }} animate={{ scale: 1, opacity: 1, y: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 20, delay: 0.1 + i * 0.12 }} transition={{ type: 'spring', stiffness: 350, damping: 20, delay: 0.1 + i * 0.12 }}
> >
{i === 1 && (
<motion.div <motion.div
initial={{ scale: 0, rotate: -20 }} initial={{ scale: 0, rotate: -20 }}
animate={{ scale: 1, rotate: 0 }} animate={{ scale: 1, rotate: 0 }}
transition={{ delay: 0.5, type: 'spring' }} transition={{ delay: 0.4 + i * 0.1, type: 'spring', stiffness: 400, damping: 15 }}
> >
<Crown size={20} className="text-gold fill-gold/20" /> {config.rankIcon}
</motion.div> </motion.div>
)}
{i === 0 && <Medal size={16} className="text-[#C0C0C0]" />}
{i === 2 && <Medal size={16} className="text-[#CD7F32]" />}
<div <div
className={`rounded-full ${config.bg} border-2 ${config.border} flex items-center justify-center font-bold ${config.color} ${isMe ? 'ring-2 ring-gold/50' : ''}`} className={`rounded-full border-3 flex items-center justify-center font-black ${isMe ? 'ring-3 ring-[#FFC83D]/40' : ''}`}
style={{ width: config.size, height: config.size, fontSize: config.size * 0.35 }} style={{
width: config.avatarSize,
height: config.avatarSize,
borderColor: config.color,
backgroundColor: `${config.color}15`,
color: config.color,
fontSize: config.avatarSize * 0.35,
}}
> >
{entry.display_name?.charAt(0) || '?'} {entry.display_name?.charAt(0) || '?'}
</div> </div>
<div className="text-center"> <div className="text-center">
<p className="text-xs font-bold truncate max-w-[70px]">{entry.display_name}</p> <p className="text-xs font-black truncate max-w-[75px]">{entry.display_name}</p>
<p className={`text-sm font-bold ${config.color}`}>{entry.rating}</p> <p className="text-base font-black" style={{ color: config.color }}>
<p className="text-[9px] text-text-muted">{entry.games_played} مباراة</p> {entry.rating}
</p>
<p className="text-[9px] text-text-muted font-bold">{entry.games_played} مباراة</p>
</div> </div>
<div
className={`w-full ${config.height} rounded-t-xl border-3 border-b-0`}
style={{
borderColor: config.color,
background: `linear-gradient(to top, ${config.color}20, transparent)`,
}}
/>
</motion.div> </motion.div>
) )
})} })}
......
...@@ -32,35 +32,41 @@ export function LoginPage() { ...@@ -32,35 +32,41 @@ export function LoginPage() {
} }
return ( return (
<div className="min-h-dvh flex flex-col items-center justify-center px-7 relative overflow-hidden"> <div className="min-h-dvh flex items-center justify-center relative overflow-hidden bg-background">
<motion.div <motion.div
className="absolute inset-0 opacity-30" className="absolute inset-0"
style={{ style={{
background: 'radial-gradient(ellipse at 50% 30%, rgba(212,168,67,0.15) 0%, transparent 60%)', background: 'radial-gradient(ellipse at 50% 30%, rgba(255,200,61,0.12) 0%, transparent 60%)',
}} }}
animate={{ animate={{
background: [ background: [
'radial-gradient(ellipse at 50% 30%, rgba(212,168,67,0.15) 0%, transparent 60%)', 'radial-gradient(ellipse at 50% 30%, rgba(255,200,61,0.12) 0%, transparent 60%)',
'radial-gradient(ellipse at 50% 30%, rgba(212,168,67,0.08) 0%, transparent 60%)', 'radial-gradient(ellipse at 50% 30%, rgba(255,200,61,0.05) 0%, transparent 60%)',
'radial-gradient(ellipse at 50% 30%, rgba(212,168,67,0.15) 0%, transparent 60%)', 'radial-gradient(ellipse at 50% 30%, rgba(255,200,61,0.12) 0%, transparent 60%)',
], ],
}} }}
transition={{ duration: 4, repeat: Infinity, ease: 'easeInOut' }} transition={{ duration: 4, repeat: Infinity, ease: 'easeInOut' }}
/> />
<motion.div <motion.div
className="relative w-full max-w-[310px]" className="relative w-full max-w-[380px] px-5"
initial={{ opacity: 0, y: 30 }} initial={{ opacity: 0, y: 40 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 25 }} transition={{ type: 'spring', stiffness: 400, damping: 22 }}
> >
<div className="flex flex-col items-center mb-8"> <div className="flex flex-col items-center mb-8">
<GoldCrown size={64} /> <GoldCrown size={72} />
<h1 className="mt-4 text-3xl font-black text-gold">EL3AB</h1> <h1 className="mt-4 text-4xl font-black text-gold">EL3AB</h1>
<p className="mt-1 text-text-secondary">تسجيل الدخول</p> <p className="mt-2 text-lg font-bold text-text-secondary">تسجيل الدخول</p>
</div> </div>
<form onSubmit={handleLogin} className="flex flex-col gap-4 p-7 rounded-2xl bg-surface-3 border-2 border-gold/30 shadow-[0_8px_40px_rgba(0,0,0,0.6),0_0_20px_rgba(212,168,67,0.08)]"> <motion.form
onSubmit={handleLogin}
className="flex flex-col gap-4 p-6 rounded-2xl bg-surface-2 border-3 border-border shadow-xl"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ type: 'spring', stiffness: 450, damping: 25, delay: 0.1 }}
>
<Input <Input
label="البريد الالكتروني" label="البريد الالكتروني"
type="email" type="email"
...@@ -78,25 +84,26 @@ export function LoginPage() { ...@@ -78,25 +84,26 @@ export function LoginPage() {
{error && ( {error && (
<motion.p <motion.p
className="text-sm text-coral text-center" className="text-sm font-bold text-coral text-center"
initial={{ opacity: 0 }} initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1 }} animate={{ opacity: 1, scale: 1 }}
transition={{ type: 'spring', stiffness: 500, damping: 20 }}
> >
{error} {error}
</motion.p> </motion.p>
)} )}
<Button type="submit" loading={loading} className="w-[85%] mx-auto mt-2"> <Button type="submit" variant="gold" size="lg" loading={loading} className="w-[80%] mx-auto mt-2">
دخول دخول
</Button> </Button>
</form> </motion.form>
<motion.button <motion.button
onClick={() => navigate('/register')} onClick={() => navigate('/register')}
className="mt-4 w-full text-center text-sm text-text-secondary hover:text-gold transition-colors" className="mt-5 w-full text-center text-sm font-bold text-text-secondary hover:text-gold transition-colors"
whileTap={{ scale: 0.98 }} whileTap={{ scale: 0.96 }}
> >
ليس لديك حساب؟ <span className="text-gold font-semibold">سجل الان</span> ليس لديك حساب؟ <span className="text-gold font-black">سجل الان</span>
</motion.button> </motion.button>
</motion.div> </motion.div>
</div> </div>
......
...@@ -36,25 +36,25 @@ export function MatchmakingPage() { ...@@ -36,25 +36,25 @@ export function MatchmakingPage() {
} }
return ( return (
<div className="flex-1 flex flex-col items-center justify-center px-6 py-12 relative overflow-hidden"> <div className="flex-1 flex flex-col items-center justify-center px-6 py-12 relative overflow-hidden min-h-dvh bg-background">
<AnimatePresence mode="wait"> <AnimatePresence mode="wait">
{matchFound ? ( {matchFound ? (
<motion.div <motion.div
key="found" key="found"
className="flex flex-col items-center" className="flex flex-col items-center"
initial={{ scale: 0.5, opacity: 0 }} initial={{ scale: 0.3, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }} animate={{ scale: 1, opacity: 1 }}
transition={{ type: 'spring', stiffness: 300, damping: 20 }} transition={{ type: 'spring', stiffness: 300, damping: 15 }}
> >
<motion.div <motion.div
className="w-24 h-24 rounded-full bg-gold/20 border-2 border-gold flex items-center justify-center" className="w-28 h-28 rounded-full bg-gold/20 border-3 border-gold flex items-center justify-center animate-pulse-glow"
animate={{ scale: [1, 1.1, 1] }} animate={{ scale: [1, 1.15, 1] }}
transition={{ duration: 0.5, repeat: 2 }} transition={{ duration: 0.6, repeat: 2 }}
> >
<span className="text-4xl">⚔️</span> <span className="text-5xl font-black text-gold">VS</span>
</motion.div> </motion.div>
<motion.h2 <motion.h2
className="mt-6 text-xl font-bold text-gold" className="mt-6 text-2xl font-black text-gold"
initial={{ opacity: 0, y: 10 }} initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }} transition={{ delay: 0.2 }}
...@@ -68,11 +68,11 @@ export function MatchmakingPage() { ...@@ -68,11 +68,11 @@ export function MatchmakingPage() {
className="flex flex-col items-center" className="flex flex-col items-center"
exit={{ opacity: 0, scale: 0.8 }} exit={{ opacity: 0, scale: 0.8 }}
> >
<div className="relative w-40 h-40 flex items-center justify-center"> <div className="relative w-44 h-44 flex items-center justify-center">
{[0, 1, 2].map((i) => ( {[0, 1, 2].map((i) => (
<motion.div <motion.div
key={i} key={i}
className="absolute inset-0 rounded-full border-2 border-gold/30" className="absolute inset-0 rounded-full border-3 border-gold/30"
initial={{ scale: 0.5, opacity: 0.8 }} initial={{ scale: 0.5, opacity: 0.8 }}
animate={{ scale: 2.5, opacity: 0 }} animate={{ scale: 2.5, opacity: 0 }}
transition={{ transition={{
...@@ -85,16 +85,16 @@ export function MatchmakingPage() { ...@@ -85,16 +85,16 @@ export function MatchmakingPage() {
))} ))}
<motion.div <motion.div
className="w-20 h-20 rounded-full bg-gradient-to-br from-gold/20 to-gold/5 border-2 border-gold/40 flex items-center justify-center" className="w-24 h-24 rounded-full bg-gradient-to-br from-gold/25 to-surface-2 border-3 border-gold/50 flex items-center justify-center shadow-lg"
animate={{ scale: [1, 1.05, 1] }} animate={{ scale: [1, 1.08, 1], rotate: [0, 3, -3, 0] }}
transition={{ duration: 2, repeat: Infinity, ease: 'easeInOut' }} transition={{ duration: 2, repeat: Infinity, ease: 'easeInOut' }}
> >
<span className="text-3xl font-black text-gold">?</span> <span className="text-4xl font-black text-gold">?</span>
</motion.div> </motion.div>
</div> </div>
<motion.h2 <motion.h2
className="mt-8 text-xl font-bold" className="mt-8 text-xl font-black"
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
transition={{ delay: 0.3 }} transition={{ delay: 0.3 }}
...@@ -103,7 +103,7 @@ export function MatchmakingPage() { ...@@ -103,7 +103,7 @@ export function MatchmakingPage() {
</motion.h2> </motion.h2>
<motion.div <motion.div
className="mt-3 text-lg font-mono text-gold tabular-nums" className="mt-3 text-2xl font-black font-mono text-gold tabular-nums"
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
transition={{ delay: 0.4 }} transition={{ delay: 0.4 }}
...@@ -112,7 +112,7 @@ export function MatchmakingPage() { ...@@ -112,7 +112,7 @@ export function MatchmakingPage() {
</motion.div> </motion.div>
<motion.div <motion.div
className="mt-2 flex gap-1" className="mt-3 flex gap-1.5"
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
transition={{ delay: 0.5 }} transition={{ delay: 0.5 }}
...@@ -120,9 +120,9 @@ export function MatchmakingPage() { ...@@ -120,9 +120,9 @@ export function MatchmakingPage() {
{[0, 1, 2].map((i) => ( {[0, 1, 2].map((i) => (
<motion.span <motion.span
key={i} key={i}
className="w-2 h-2 rounded-full bg-gold" className="w-3 h-3 rounded-full bg-gold"
animate={{ opacity: [0.3, 1, 0.3] }} animate={{ opacity: [0.2, 1, 0.2], scale: [0.8, 1.2, 0.8] }}
transition={{ duration: 1, repeat: Infinity, delay: i * 0.3 }} transition={{ duration: 1.2, repeat: Infinity, delay: i * 0.3 }}
/> />
))} ))}
</motion.div> </motion.div>
...@@ -133,7 +133,7 @@ export function MatchmakingPage() { ...@@ -133,7 +133,7 @@ export function MatchmakingPage() {
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
transition={{ delay: 1 }} transition={{ delay: 1 }}
> >
<Button variant="ghost" onClick={handleCancel}> <Button variant="ghost" onClick={handleCancel} size="md">
الغاء الغاء
</Button> </Button>
</motion.div> </motion.div>
......
...@@ -7,7 +7,7 @@ export function NotFoundPage() { ...@@ -7,7 +7,7 @@ export function NotFoundPage() {
const navigate = useNavigate() const navigate = useNavigate()
return ( return (
<div className="flex-1 flex flex-col items-center justify-center px-6 py-12 gap-6"> <div className="flex-1 flex flex-col items-center justify-center px-6 py-12 gap-6 min-h-dvh bg-background">
<motion.div <motion.div
className="text-6xl" className="text-6xl"
animate={{ animate={{
......
import { motion } from 'framer-motion' import { motion } from 'framer-motion'
import { Bell, UserPlus, Trophy, Swords, CheckCheck, Loader2 } from 'lucide-react' import { Bell, UserPlus, Trophy, Swords, Loader2 } from 'lucide-react'
import { PageTransition } from '../components/layout/PageTransition' import { PageTransition } from '../components/layout/PageTransition'
import { Button } from '../components/ui/Button'
import { useNotifications } from '../hooks/useNotifications' import { useNotifications } from '../hooks/useNotifications'
function relativeTime(date: string): string { function relativeTime(date: string): string {
...@@ -18,104 +19,114 @@ function getIcon(type: string) { ...@@ -18,104 +19,114 @@ function getIcon(type: string) {
switch (type) { switch (type) {
case 'friend_request': case 'friend_request':
case 'friend_accepted': case 'friend_accepted':
return <UserPlus size={16} className="text-cyan" /> return <UserPlus size={18} className="text-[#00E5CC]" />
case 'match_result': case 'achievement':
case 'win': return <Trophy size={18} className="text-[#FFC83D]" />
return <Trophy size={16} className="text-gold" /> case 'match':
case 'match_invite': case 'match_invite':
case 'challenge': case 'challenge':
return <Swords size={16} className="text-coral" /> return <Swords size={18} className="text-[#FF5252]" />
default: default:
return <Bell size={16} className="text-text-muted" /> return <Bell size={18} className="text-[#B44DFF]" />
} }
} }
export function NotificationsPage() { const stagger = {
const { notifications, markAsRead, markAllRead, loading } = useNotifications()
const stagger = {
hidden: { opacity: 0 }, hidden: { opacity: 0 },
show: { opacity: 1, transition: { staggerChildren: 0.04 } }, show: { opacity: 1, transition: { staggerChildren: 0.06 } },
} }
const item = { const item = {
hidden: { opacity: 0, y: 10 }, hidden: { opacity: 0, y: 12, scale: 0.95 },
show: { opacity: 1, y: 0 }, show: { opacity: 1, y: 0, scale: 1, transition: { type: 'spring', stiffness: 400, damping: 22 } },
} }
export function NotificationsPage() {
const { notifications, markAsRead, markAllRead, loading } = useNotifications()
return ( return (
<PageTransition className="px-7 py-7 flex flex-col gap-6"> <PageTransition>
<div className="flex flex-col gap-5 py-6">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h1 className="text-xl font-bold">الاشعارات</h1> <h1 className="text-xl font-black">الاشعارات</h1>
{notifications.some((n) => !n.is_read) && ( {notifications.some((n) => !n.is_read) && (
<motion.button <Button size="sm" variant="ghost" onClick={markAllRead}>
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-surface-2 border border-border text-xs text-text-muted"
whileTap={{ scale: 0.9 }}
onClick={markAllRead}
>
<CheckCheck size={14} />
قراءة الكل قراءة الكل
</motion.button> </Button>
)} )}
</div> </div>
{loading ? ( {loading ? (
<div className="flex-1 flex items-center justify-center"> <div className="flex-1 flex items-center justify-center py-16">
<Loader2 size={24} className="animate-spin text-text-muted" /> <Loader2 size={28} className="animate-spin text-[#FFC83D]" />
</div> </div>
) : notifications.length === 0 ? ( ) : notifications.length === 0 ? (
<div className="flex-1 flex flex-col items-center justify-center py-12">
<motion.div <motion.div
className="w-16 h-16 rounded-full bg-surface-2 border border-border flex items-center justify-center mb-4" className="flex flex-col items-center justify-center py-20 gap-4"
initial={{ scale: 0 }} initial={{ opacity: 0, scale: 0.9 }}
animate={{ scale: 1 }} animate={{ opacity: 1, scale: 1 }}
transition={{ type: 'spring', stiffness: 300, damping: 20 }} transition={{ type: 'spring', stiffness: 300, damping: 20 }}
> >
<Bell size={24} className="text-text-muted" /> <motion.div
className="w-20 h-20 rounded-full bg-surface-2 border-3 border-border flex items-center justify-center"
initial={{ scale: 0, rotate: -15 }}
animate={{ scale: 1, rotate: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 18 }}
>
<Bell size={32} className="text-text-muted" />
</motion.div>
<p className="text-text-muted font-black text-base">لا توجد اشعارات</p>
</motion.div> </motion.div>
<p className="text-text-muted text-sm">لا توجد اشعارات</p>
</div>
) : ( ) : (
<motion.div className="flex flex-col gap-2" variants={stagger} initial="hidden" animate="show"> <motion.div className="flex flex-col gap-3" variants={stagger} initial="hidden" animate="show">
{notifications.map((notification) => ( {notifications.map((notification) => (
<motion.div <motion.div
key={notification.id} key={notification.id}
variants={item} variants={item}
className={`rounded-xl p-3.5 border cursor-pointer transition-colors ${ className={`rounded-xl p-4 border-3 cursor-pointer transition-colors ${
notification.is_read notification.is_read
? 'bg-surface-1 border-border' ? 'bg-surface-1 border-border'
: 'bg-surface-2 border-gold/30 border-r-4 border-r-gold' : 'bg-surface-2 border-[#FFC83D]/40'
}`} }`}
whileTap={{ scale: 0.97 }} whileTap={{ scale: 0.96 }}
onClick={() => { onClick={() => {
if (!notification.is_read) markAsRead(notification.id) if (!notification.is_read) markAsRead(notification.id)
}} }}
> >
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="w-8 h-8 rounded-full bg-surface-3 border border-border flex items-center justify-center shrink-0 mt-0.5"> <div className={`w-10 h-10 rounded-xl border-3 flex items-center justify-center shrink-0 mt-0.5 ${
notification.is_read
? 'bg-surface-3 border-border'
: 'bg-surface-3 border-[#FFC83D]/30'
}`}>
{getIcon(notification.type)} {getIcon(notification.type)}
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className={`text-sm font-semibold ${notification.is_read ? 'text-text-muted' : 'text-text-primary'}`}> <p className={`text-sm font-black ${notification.is_read ? 'text-text-muted' : 'text-text-primary'}`}>
{notification.title_ar || notification.title} {notification.title_ar || notification.title}
</p> </p>
{(notification.body_ar || notification.body) && ( {(notification.body_ar || notification.body) && (
<p className="text-xs text-text-muted mt-0.5 line-clamp-2"> <p className="text-xs text-text-muted font-bold mt-0.5 line-clamp-2">
{notification.body_ar || notification.body} {notification.body_ar || notification.body}
</p> </p>
)} )}
<p className="text-[10px] text-text-muted mt-1"> <p className="text-[10px] text-text-muted font-bold mt-1.5">
{relativeTime(notification.created_at)} {relativeTime(notification.created_at)}
</p> </p>
</div> </div>
{!notification.is_read && ( {!notification.is_read && (
<div className="w-2 h-2 rounded-full bg-gold shrink-0 mt-2" /> <motion.div
className="w-3 h-3 rounded-full bg-[#FFC83D] shrink-0 mt-2 shadow-md shadow-[#FFC83D]/40"
animate={{ scale: [1, 1.2, 1] }}
transition={{ repeat: Infinity, duration: 2 }}
/>
)} )}
</div> </div>
</motion.div> </motion.div>
))} ))}
</motion.div> </motion.div>
)} )}
</div>
</PageTransition> </PageTransition>
) )
} }
import { useState } from 'react'
import { motion } from 'framer-motion' import { motion } from 'framer-motion'
import { Lock, Zap, Timer, Clock, Hourglass, Cpu, Crown } from 'lucide-react' import { Zap, Timer, Clock, Hourglass, Lock, Cpu, Crown } from 'lucide-react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { PageTransition } from '../components/layout/PageTransition' import { PageTransition } from '../components/layout/PageTransition'
import { GAMES, TIME_CONTROLS } from '../lib/constants' import { Card } from '../components/ui/Card'
import { useState } from 'react'
import { Button } from '../components/ui/Button' import { Button } from '../components/ui/Button'
import { GAMES, TIME_CONTROLS } from '../lib/constants'
import { playSound } from '../lib/sounds'
const CATEGORIES = [ const CATEGORIES = [
{ key: 'bullet', label: 'رصاصة', icon: Zap }, { key: 'bullet', label: 'رصاصة', icon: Zap },
...@@ -13,11 +15,23 @@ const CATEGORIES = [ ...@@ -13,11 +15,23 @@ const CATEGORIES = [
{ key: 'classical', label: 'كلاسيكي', icon: Hourglass }, { key: 'classical', label: 'كلاسيكي', icon: Hourglass },
] as const ] as const
const GAME_ICONS: Record<string, React.ReactNode> = { const GAME_ICONS: Record<string, string> = {
backgammon: <span className="text-2xl font-bold text-text-muted">&#x2680;</span>, backgammon: '⚀',
dominoes: <span className="text-2xl font-bold text-text-muted">&#x1F0A1;</span>, dominoes: '🂡',
ludo: <span className="text-2xl font-bold text-text-muted">&#x2684;</span>, ludo: '⚄',
trivia: <span className="text-lg font-bold text-text-muted">?</span>, trivia: '?',
}
const stagger = {
hidden: {},
show: {
transition: { staggerChildren: 0.06 },
},
}
const fadeUp = {
hidden: { opacity: 0, y: 20 },
show: { opacity: 1, y: 0, transition: { type: 'spring', stiffness: 500, damping: 22 } },
} }
export function PlayPage() { export function PlayPage() {
...@@ -27,77 +41,84 @@ export function PlayPage() { ...@@ -27,77 +41,84 @@ export function PlayPage() {
const chessGame = GAMES.find((g) => g.key === 'chess')! const chessGame = GAMES.find((g) => g.key === 'chess')!
const otherGames = GAMES.filter((g) => g.key !== 'chess') const otherGames = GAMES.filter((g) => g.key !== 'chess')
return ( const activeCategory = Object.entries(TIME_CONTROLS).find(
<PageTransition className="px-7 py-7 flex flex-col gap-7"> ([k]) => k === selectedTC
<h1 className="text-xl font-bold">اختر اللعبة</h1> )?.[1].category
{/* Chess Hero Card */} return (
<PageTransition>
<motion.div <motion.div
className="relative rounded-2xl overflow-hidden border-2 border-gold/30 bg-gradient-to-br from-surface-2 via-surface-1 to-gold/5 p-6 flex items-center gap-5" variants={stagger}
initial={{ opacity: 0, y: 20 }} initial="hidden"
animate={{ opacity: 1, y: 0 }} animate="show"
transition={{ type: 'spring', stiffness: 400, damping: 25 }} className="flex flex-col gap-6"
whileTap={{ scale: 0.97 }}
> >
<div className="w-16 h-16 rounded-2xl bg-gold/10 border border-gold/20 flex items-center justify-center shadow-inner"> <motion.h1 variants={fadeUp} className="text-2xl font-black">
<Crown size={32} className="text-gold" /> اختر اللعبة
</motion.h1>
<motion.div variants={fadeUp}>
<Card variant="gold" className="relative !p-6">
<div className="flex items-center gap-5">
<div className="w-18 h-18 min-w-[72px] min-h-[72px] rounded-2xl bg-gold/15 border-3 border-gold/40 flex items-center justify-center">
<Crown size={38} className="text-gold" />
</div> </div>
<div className="flex-1"> <div className="flex-1">
<span className="text-lg font-bold">{chessGame.nameAr}</span> <span className="text-xl font-black">{chessGame.nameAr}</span>
<p className="text-xs text-text-secondary mt-1.5 leading-relaxed">العب شطرنج اونلاين ضد لاعبين حقيقيين</p> <p className="text-xs text-text-secondary mt-2 leading-relaxed">
العب شطرنج اونلاين ضد لاعبين حقيقيين
</p>
</div>
</div> </div>
<motion.div <motion.div
className="absolute top-3 left-3 w-2 h-2 rounded-full bg-green-500" className="absolute top-4 left-4 w-3 h-3 rounded-full bg-green-500 border-2 border-green-400/50"
animate={{ opacity: [1, 0.4, 1] }} animate={{ opacity: [1, 0.4, 1], scale: [1, 1.2, 1] }}
transition={{ duration: 2, repeat: Infinity }} transition={{ duration: 2, repeat: Infinity }}
/> />
</Card>
</motion.div> </motion.div>
{/* Other Games Grid */} <motion.div variants={fadeUp} className="grid grid-cols-2 gap-3">
<div className="grid grid-cols-2 gap-3"> {otherGames.map((game) => (
{otherGames.map((game, i) => ( <div
<motion.div
key={game.key} key={game.key}
className="relative rounded-2xl overflow-hidden border border-border bg-surface-1 opacity-50 p-5 flex flex-col items-center gap-3" className="relative rounded-2xl overflow-hidden border-3 border-border bg-surface-1 opacity-50 p-5 flex flex-col items-center gap-3"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 0.5, y: 0 }}
transition={{ delay: 0.08 + i * 0.08, type: 'spring', stiffness: 400, damping: 25 }}
> >
<div className="w-12 h-12 rounded-xl bg-surface-3/80 flex items-center justify-center"> <div className="w-12 h-12 rounded-xl bg-surface-3/80 border-2 border-border flex items-center justify-center">
{GAME_ICONS[game.key] || null} <span className="text-2xl font-bold text-text-muted">
{GAME_ICONS[game.key] || ''}
</span>
</div> </div>
<span className="text-sm font-bold text-text-secondary">{game.nameAr}</span> <span className="text-sm font-black text-text-secondary">{game.nameAr}</span>
<div className="absolute inset-0 flex items-center justify-center bg-background/50 backdrop-blur-[2px]"> <div className="absolute inset-0 flex items-center justify-center bg-background/60 backdrop-blur-[2px]">
<div className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-surface-2/80 border border-border"> <div className="flex items-center gap-1.5 px-3.5 py-2 rounded-full bg-surface-2/90 border-2 border-border">
<Lock size={12} className="text-text-muted" /> <Lock size={13} className="text-text-muted" />
<span className="text-[11px] text-text-muted font-medium">قريبا</span> <span className="text-[11px] text-text-muted font-bold">قريبا</span>
</div> </div>
</div> </div>
</motion.div>
))}
</div> </div>
))}
</motion.div>
{/* Time Control */} <motion.div variants={fadeUp} className="space-y-4">
<div className="space-y-4"> <h2 className="text-lg font-black">نظام الوقت</h2>
<h2 className="text-base font-bold">نظام الوقت</h2>
{/* Category Pills */}
<div className="flex gap-2 overflow-x-auto scrollbar-hide pb-1"> <div className="flex gap-2 overflow-x-auto scrollbar-hide pb-1">
{CATEGORIES.map((cat) => { {CATEGORIES.map((cat) => {
const isActive = Object.entries(TIME_CONTROLS).find( const isActive = activeCategory === cat.key
([k]) => k === selectedTC
)?.[1].category === cat.key
const Icon = cat.icon const Icon = cat.icon
return ( return (
<motion.button <motion.button
key={cat.key} key={cat.key}
className={`flex items-center gap-2 px-4 py-2.5 rounded-full text-xs font-bold border whitespace-nowrap ${ className={`flex items-center gap-2 px-4 py-2.5 rounded-full text-xs font-black border-2 whitespace-nowrap transition-all ${
isActive isActive
? 'bg-gold/15 border-gold/50 text-gold shadow-sm shadow-gold/10' ? 'bg-gold/20 border-gold text-gold shadow-md shadow-gold/15'
: 'bg-surface-2 border-border text-text-muted' : 'bg-surface-2 border-border text-text-muted'
}`} }`}
whileTap={{ scale: 0.93 }} whileTap={{ scale: 0.9 }}
transition={{ type: 'spring', stiffness: 500, damping: 20 }}
onClick={() => { onClick={() => {
playSound('click')
const first = Object.entries(TIME_CONTROLS).find(([, v]) => v.category === cat.key) const first = Object.entries(TIME_CONTROLS).find(([, v]) => v.category === cat.key)
if (first) setSelectedTC(first[0]) if (first) setSelectedTC(first[0])
}} }}
...@@ -109,42 +130,55 @@ export function PlayPage() { ...@@ -109,42 +130,55 @@ export function PlayPage() {
})} })}
</div> </div>
{/* Time Buttons */}
<div className="grid grid-cols-3 gap-2.5"> <div className="grid grid-cols-3 gap-2.5">
{Object.entries(TIME_CONTROLS) {Object.entries(TIME_CONTROLS)
.filter(([, v]) => { .filter(([, v]) => v.category === activeCategory)
const activeCategory = Object.entries(TIME_CONTROLS).find(
([k]) => k === selectedTC
)?.[1].category
return v.category === activeCategory
})
.map(([key, tc]) => ( .map(([key, tc]) => (
<motion.button <motion.button
key={key} key={key}
className={`py-3.5 rounded-xl text-center font-bold text-sm border transition-all ${ className={`py-4 rounded-xl text-center font-black text-sm border-3 transition-all ${
selectedTC === key selectedTC === key
? 'bg-gold/15 border-gold text-gold shadow-sm shadow-gold/10' ? 'bg-gold/20 border-gold text-gold shadow-md shadow-gold/15 scale-105'
: 'bg-surface-2 border-border text-text-secondary' : 'bg-surface-2 border-border text-text-secondary hover:border-border/80'
}`} }`}
whileTap={{ scale: 0.93 }} whileTap={{ scale: 0.9 }}
onClick={() => setSelectedTC(key)} transition={{ type: 'spring', stiffness: 500, damping: 20 }}
onClick={() => {
playSound('click')
setSelectedTC(key)
}}
> >
{tc.labelAr} {tc.labelAr}
</motion.button> </motion.button>
))} ))}
</div> </div>
</div> </motion.div>
{/* Action Buttons */} <motion.div variants={fadeUp} className="flex flex-col items-center gap-3 mt-2 pb-4">
<div className="flex flex-col items-center gap-3 mt-2 pb-4"> <Button
<Button onClick={() => navigate(`/matchmaking?tc=${selectedTC}&game=chess`)} className="w-[85%]" size="lg"> onClick={() => {
playSound('click')
navigate(`/matchmaking?tc=${selectedTC}&game=chess`)
}}
className="w-[80%]"
size="lg"
>
البحث عن خصم البحث عن خصم
</Button> </Button>
<Button onClick={() => navigate('/bot-select')} variant="ghost" className="w-[85%]" size="md"> <Button
<Cpu size={18} className="text-gold" /> onClick={() => {
playSound('click')
navigate('/bot-select')
}}
variant="ghost"
className="w-[80%]"
size="md"
>
<Cpu size={18} className="text-purple" />
العب ضد الروبوت العب ضد الروبوت
</Button> </Button>
</div> </motion.div>
</motion.div>
</PageTransition> </PageTransition>
) )
} }
import { useState } from 'react' import { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion' import { motion, AnimatePresence } from 'framer-motion'
import { Settings, TrendingUp, Target, Flame, Trophy, Lock, Pencil, X, Clock, ArrowUpRight, ArrowDownRight, Minus } from 'lucide-react' import { Trophy, Target, Flame, TrendingUp, Pencil, X, LogOut } from 'lucide-react'
import { useNavigate } from 'react-router-dom'
import { useAuthStore } from '../stores/authStore' import { useAuthStore } from '../stores/authStore'
import { useNotificationStore } from '../stores/notificationStore' import { useNotificationStore } from '../stores/notificationStore'
import { PageTransition } from '../components/layout/PageTransition' import { PageTransition } from '../components/layout/PageTransition'
import { Card } from '../components/ui/Card' import { Card } from '../components/ui/Card'
import { Button } from '../components/ui/Button' import { Button } from '../components/ui/Button'
import { supabase } from '../lib/supabase' import { supabase } from '../lib/supabase'
import { useRecentGames } from '../hooks/useRecentGames'
function SectionDivider() { const stagger = {
return <div className="w-full h-px bg-gradient-to-l from-transparent via-border to-transparent my-1" /> hidden: { opacity: 0 },
show: { opacity: 1, transition: { staggerChildren: 0.06 } },
}
const item = {
hidden: { opacity: 0, y: 12, scale: 0.95 },
show: { opacity: 1, y: 0, scale: 1, transition: { type: 'spring', stiffness: 400, damping: 22 } },
} }
export function ProfilePage() { export function ProfilePage() {
const { user, profile, setProfile } = useAuthStore() const { user, profile, setProfile } = useAuthStore()
const { showToast } = useNotificationStore() const { showToast } = useNotificationStore()
const navigate = useNavigate()
const [editOpen, setEditOpen] = useState(false) const [editOpen, setEditOpen] = useState(false)
const [editForm, setEditForm] = useState({ const [editForm, setEditForm] = useState({ display_name: '', bio: '' })
display_name: '',
bio: '',
country_code: '',
})
const [saving, setSaving] = useState(false) const [saving, setSaving] = useState(false)
const { games, loading: gamesLoading } = useRecentGames(user?.id, 5)
function openEdit() { function openEdit() {
if (!profile) return if (!profile) return
setEditForm({ setEditForm({
display_name: profile.display_name || '', display_name: profile.display_name || '',
bio: profile.bio || '', bio: profile.bio || '',
country_code: profile.country_code || '',
}) })
setEditOpen(true) setEditOpen(true)
} }
...@@ -46,7 +43,6 @@ export function ProfilePage() { ...@@ -46,7 +43,6 @@ export function ProfilePage() {
.update({ .update({
display_name: editForm.display_name.trim(), display_name: editForm.display_name.trim(),
bio: editForm.bio.trim() || null, bio: editForm.bio.trim() || null,
country_code: editForm.country_code.trim() || null,
}) })
.eq('id', user.id) .eq('id', user.id)
...@@ -61,180 +57,132 @@ export function ProfilePage() { ...@@ -61,180 +57,132 @@ export function ProfilePage() {
...profile, ...profile,
display_name: editForm.display_name.trim(), display_name: editForm.display_name.trim(),
bio: editForm.bio.trim() || null, bio: editForm.bio.trim() || null,
country_code: editForm.country_code.trim() || null,
}) })
showToast({ type: 'success', title: 'تم حفظ التعديلات' }) showToast({ type: 'success', title: 'تم حفظ التعديلات' })
setEditOpen(false) setEditOpen(false)
} }
async function handleLogout() {
await supabase.auth.signOut()
}
if (!profile) return null if (!profile) return null
const winRate = profile.total_games_played > 0 const winRate = profile.total_games_played > 0
? Math.round((profile.total_wins / profile.total_games_played) * 100) ? Math.round((profile.total_wins / profile.total_games_played) * 100)
: 0 : 0
const xpForNextLevel = 500
const currentXpInLevel = profile.xp % xpForNextLevel
const xpRemaining = xpForNextLevel - currentXpInLevel
const xpPercent = Math.min((currentXpInLevel / xpForNextLevel) * 100, 100)
const ratings = [
{ label: 'رصاصة', value: profile.elo_bullet, color: 'border-t-[#FF5252]' },
{ label: 'خاطف', value: profile.elo_blitz, color: 'border-t-[#FFC83D]' },
{ label: 'سريع', value: profile.elo_rapid, color: 'border-t-[#00E5CC]' },
{ label: 'كلاسيكي', value: profile.elo_classical, color: 'border-t-[#B44DFF]' },
]
return ( return (
<PageTransition className="px-7 py-7 flex flex-col gap-6"> <PageTransition>
<div className="flex items-start justify-between">
<div className="flex items-center gap-4">
<motion.div <motion.div
className="w-18 h-18 rounded-full bg-gradient-to-br from-gold/30 to-purple/20 border-2 border-gold flex items-center justify-center shadow-lg shadow-gold/10" className="flex flex-col gap-5 py-6"
style={{ width: 72, height: 72 }} variants={stagger}
initial={{ scale: 0 }} initial="hidden"
animate={{ scale: 1 }} animate="show"
transition={{ type: 'spring', stiffness: 300, damping: 20 }} >
<motion.div className="flex flex-col items-center gap-3" variants={item}>
<div className="relative">
<motion.div
className="w-[88px] h-[88px] rounded-full bg-gradient-to-br from-[#FFC83D]/30 to-[#B44DFF]/20 border-3 border-[#FFC83D] flex items-center justify-center shadow-lg shadow-[#FFC83D]/20"
initial={{ scale: 0, rotate: -20 }}
animate={{ scale: 1, rotate: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 18 }}
> >
<span className="text-3xl font-black text-gold"> <span className="text-4xl font-black text-[#FFC83D]">
{profile.display_name?.charAt(0) || '?'} {profile.display_name?.charAt(0) || '?'}
</span> </span>
</motion.div> </motion.div>
<div className="space-y-1"> <motion.div
<h1 className="text-xl font-bold">{profile.display_name}</h1> className="absolute -bottom-1 -right-1 w-7 h-7 rounded-full bg-gradient-to-b from-[#FFC83D] to-[#E0A800] border-3 border-background flex items-center justify-center"
<p className="text-sm text-text-muted">@{profile.username}</p> initial={{ scale: 0 }}
{profile.bio && ( animate={{ scale: 1 }}
<p className="text-xs text-text-secondary mt-1 max-w-[180px] line-clamp-2">{profile.bio}</p> transition={{ type: 'spring', stiffness: 500, damping: 20, delay: 0.2 }}
)}
</div>
</div>
<div className="flex items-center gap-2">
<motion.button
whileTap={{ scale: 0.9 }}
onClick={openEdit}
className="p-2.5 rounded-xl bg-surface-2 border border-border"
>
<Pencil size={18} className="text-text-muted" />
</motion.button>
<motion.button
whileTap={{ scale: 0.9 }}
onClick={() => navigate('/settings')}
className="p-2.5 rounded-xl bg-surface-2 border border-border"
> >
<Settings size={18} className="text-text-muted" /> <span className="text-[10px] font-black text-background">{profile.level}</span>
</motion.button> </motion.div>
</div> </div>
<div className="text-center">
<h1 className="text-xl font-black">{profile.display_name}</h1>
<p className="text-sm text-text-muted font-bold">@{profile.username}</p>
{profile.bio && (
<p className="text-xs text-text-secondary mt-1 max-w-[220px] mx-auto line-clamp-2">{profile.bio}</p>
)}
</div> </div>
</motion.div>
<Card className="p-5"> <motion.div variants={item}>
<div className="flex items-center justify-between mb-3"> <Card variant="gold" className="!p-4">
<h2 className="text-lg font-bold">المستوى {profile.level}</h2> <div className="flex items-center justify-between mb-2">
<span className="text-sm text-gold font-bold px-3 py-1 rounded-full bg-gold/10">{profile.xp} XP</span> <span className="text-lg font-black">المستوى {profile.level}</span>
<span className="text-sm font-black text-[#FFC83D] px-3 py-1 rounded-full bg-[#FFC83D]/15 border-2 border-[#FFC83D]/30">
{profile.xp} XP
</span>
</div> </div>
<div className="w-full h-3 rounded-full bg-surface-3 overflow-hidden"> <div className="w-full h-4 rounded-full bg-surface-3 border-2 border-border overflow-hidden">
<motion.div <motion.div
className="h-full rounded-full bg-gradient-to-l from-gold to-gold-light" className="h-full rounded-full bg-gradient-to-l from-[#FFC83D] to-[#FFE082]"
initial={{ width: 0 }} initial={{ width: 0 }}
animate={{ width: `${Math.min((profile.xp % 500) / 5, 100)}%` }} animate={{ width: `${xpPercent}%` }}
transition={{ duration: 1, ease: 'easeOut' }} transition={{ duration: 1.2, ease: 'easeOut', delay: 0.3 }}
/> />
</div> </div>
<p className="text-xs text-text-muted mt-2.5">{500 - (profile.xp % 500)} XP للمستوى التالي</p> <p className="text-xs text-text-muted font-bold mt-2">{xpRemaining} XP للمستوى التالي</p>
</Card> </Card>
</motion.div>
<SectionDivider /> <motion.div variants={item}>
<div>
<h2 className="text-sm font-bold text-text-secondary mb-3">التقييمات</h2>
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
{[ {ratings.map((rating, i) => (
{ label: 'رصاصة', value: profile.elo_bullet, icon: '1+0' },
{ label: 'خاطف', value: profile.elo_blitz, icon: '5+0' },
{ label: 'سريع', value: profile.elo_rapid, icon: '10+0' },
{ label: 'كلاسيكي', value: profile.elo_classical, icon: '60+0' },
].map((rating, i) => (
<motion.div <motion.div
key={rating.label} key={rating.label}
className="p-4 rounded-xl bg-surface-1 border border-border/80 flex items-center gap-3" className={`p-4 rounded-2xl bg-surface-1 border-3 border-border border-t-4 ${rating.color} flex flex-col items-center gap-1`}
initial={{ opacity: 0, y: 10 }} initial={{ opacity: 0, y: 14 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 + i * 0.05 }} transition={{ type: 'spring', stiffness: 400, damping: 22, delay: 0.15 + i * 0.06 }}
> >
<div className="w-10 h-10 rounded-lg bg-surface-3 flex items-center justify-center"> <span className="text-2xl font-black">{rating.value}</span>
<span className="text-[11px] font-bold text-text-muted">{rating.icon}</span> <span className="text-xs font-bold text-text-muted">{rating.label}</span>
</div>
<div>
<p className="text-lg font-bold">{rating.value}</p>
<p className="text-[11px] text-text-muted">{rating.label}</p>
</div>
</motion.div> </motion.div>
))} ))}
</div> </div>
</div> </motion.div>
<SectionDivider />
<div>
<h2 className="text-sm font-bold text-text-secondary mb-3">الاحصائيات</h2>
<div className="grid grid-cols-4 gap-2.5">
<StatMini icon={<Flame size={16} className="text-coral" />} value={profile.best_win_streak} label="افضل سلسلة" />
<StatMini icon={<Trophy size={16} className="text-gold" />} value={profile.total_wins} label="انتصارات" />
<StatMini icon={<Target size={16} className="text-cyan" />} value={`${winRate}%`} label="نسبة الفوز" />
<StatMini icon={<TrendingUp size={16} className="text-purple" />} value={profile.total_games_played} label="مباريات" />
</div>
</div>
<SectionDivider />
<div> <motion.div variants={item}>
<h2 className="text-sm font-bold text-text-secondary mb-3">الانجازات</h2> <div className="grid grid-cols-4 gap-2">
<div className="grid grid-cols-3 gap-3"> <StatBox icon={<TrendingUp size={18} className="text-[#B44DFF]" />} value={profile.total_games_played} label="مباريات" />
{[ <StatBox icon={<Trophy size={18} className="text-[#FFC83D]" />} value={profile.total_wins} label="انتصارات" />
{ label: 'المحارب' }, <StatBox icon={<Target size={18} className="text-[#00E5CC]" />} value={`${winRate}%`} label="نسبة الفوز" />
{ label: 'البطل' }, <StatBox icon={<Flame size={18} className="text-[#FF5252]" />} value={profile.best_win_streak} label="افضل سلسلة" />
{ label: 'الاسطورة' },
].map((badge, i) => (
<motion.div
key={badge.label}
className="flex flex-col items-center gap-2.5 p-4 rounded-xl bg-surface-1 border border-border/60 opacity-50"
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 0.5, scale: 1 }}
transition={{ delay: 0.2 + i * 0.08 }}
>
<div className="w-12 h-12 rounded-full bg-surface-3/80 border border-border flex items-center justify-center">
<Lock size={16} className="text-text-muted/60" />
</div> </div>
<span className="text-[11px] font-semibold text-text-muted">{badge.label}</span>
<span className="text-[9px] text-gold/50 font-medium">قريبا</span>
</motion.div> </motion.div>
))}
</div>
</div>
<SectionDivider />
<div> <motion.div className="flex flex-col items-center gap-3 mt-2" variants={item}>
<h2 className="text-sm font-bold text-text-secondary mb-3">اخر المباريات</h2> <Button variant="ghost" onClick={openEdit} className="w-[80%]">
{gamesLoading ? ( <Pencil size={16} />
<div className="flex items-center justify-center py-8"> تعديل الملف الشخصي
<div className="w-6 h-6 border-2 border-gold/30 border-t-gold rounded-full animate-spin" /> </Button>
</div> <motion.button
) : games.length === 0 ? ( className="text-sm font-bold text-[#FF5252]/70 flex items-center gap-1.5 py-2"
<div className="flex flex-col items-center py-8 gap-2 rounded-2xl bg-surface-1 border border-border"> whileTap={{ scale: 0.9 }}
<Clock size={24} className="text-text-muted/40" /> onClick={handleLogout}
<p className="text-sm text-text-muted">لا توجد مباريات بعد</p>
</div>
) : (
<div className="flex flex-col gap-2">
{games.map((game, i) => (
<motion.div
key={game.id}
className="flex items-center justify-between p-3.5 rounded-xl bg-surface-1 border border-border/80"
initial={{ opacity: 0, x: 10 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.05 * i }}
> >
<div className="flex items-center gap-3"> <LogOut size={14} />
<ResultBadge result={game.result} /> تسجيل الخروج
<div> </motion.button>
<p className="text-sm font-semibold">{game.opponent}</p> </motion.div>
<p className="text-[10px] text-text-muted">{game.timeControl} &middot; {formatRelativeTime(game.completedAt)}</p>
</div>
</div>
<RatingBadge change={game.ratingChange} />
</motion.div> </motion.div>
))}
</div>
)}
</div>
<AnimatePresence> <AnimatePresence>
{editOpen && ( {editOpen && (
...@@ -245,25 +193,25 @@ export function ProfilePage() { ...@@ -245,25 +193,25 @@ export function ProfilePage() {
exit={{ opacity: 0 }} exit={{ opacity: 0 }}
> >
<motion.div <motion.div
className="absolute inset-0 bg-background/80 backdrop-blur-sm" className="absolute inset-0 bg-background/85 backdrop-blur-sm"
onClick={() => setEditOpen(false)} onClick={() => setEditOpen(false)}
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
exit={{ opacity: 0 }} exit={{ opacity: 0 }}
/> />
<motion.div <motion.div
className="relative w-full max-w-[340px] rounded-2xl bg-surface-1 border border-border p-6 flex flex-col gap-5" className="relative w-full max-w-[360px] rounded-2xl bg-surface-1 border-3 border-border p-6 flex flex-col gap-5"
initial={{ scale: 0.9, opacity: 0, y: 20 }} initial={{ scale: 0.85, opacity: 0, y: 30 }}
animate={{ scale: 1, opacity: 1, y: 0 }} animate={{ scale: 1, opacity: 1, y: 0 }}
exit={{ scale: 0.9, opacity: 0, y: 20 }} exit={{ scale: 0.85, opacity: 0, y: 30 }}
transition={{ type: 'spring', stiffness: 400, damping: 25 }} transition={{ type: 'spring', stiffness: 400, damping: 22 }}
> >
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h2 className="text-lg font-bold">تعديل الملف الشخصي</h2> <h2 className="text-lg font-black">تعديل الملف الشخصي</h2>
<motion.button <motion.button
whileTap={{ scale: 0.9 }} whileTap={{ scale: 0.85 }}
onClick={() => setEditOpen(false)} onClick={() => setEditOpen(false)}
className="p-2 rounded-lg bg-surface-3" className="p-2 rounded-xl bg-surface-3 border-2 border-border"
> >
<X size={16} className="text-text-muted" /> <X size={16} className="text-text-muted" />
</motion.button> </motion.button>
...@@ -271,35 +219,24 @@ export function ProfilePage() { ...@@ -271,35 +219,24 @@ export function ProfilePage() {
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div className="flex flex-col gap-1.5"> <div className="flex flex-col gap-1.5">
<label className="text-xs font-semibold text-text-secondary">الاسم</label> <label className="text-xs font-black text-text-secondary">الاسم</label>
<input <input
type="text" type="text"
value={editForm.display_name} value={editForm.display_name}
onChange={(e) => setEditForm({ ...editForm, display_name: e.target.value })} onChange={(e) => setEditForm({ ...editForm, display_name: e.target.value })}
className="px-4 py-3 rounded-xl bg-surface-3 border-2 border-transparent focus:border-gold/60 text-sm outline-none transition-colors" className="px-4 py-3 rounded-xl bg-surface-3 border-3 border-border focus:border-[#FFC83D]/60 text-sm font-bold outline-none transition-colors"
maxLength={30} maxLength={30}
dir="rtl"
/> />
</div> </div>
<div className="flex flex-col gap-1.5"> <div className="flex flex-col gap-1.5">
<label className="text-xs font-semibold text-text-secondary">نبذة</label> <label className="text-xs font-black text-text-secondary">نبذة</label>
<textarea <textarea
value={editForm.bio} value={editForm.bio}
onChange={(e) => setEditForm({ ...editForm, bio: e.target.value })} onChange={(e) => setEditForm({ ...editForm, bio: e.target.value })}
className="px-4 py-3 rounded-xl bg-surface-3 border-2 border-transparent focus:border-gold/60 text-sm outline-none transition-colors resize-none h-20" className="px-4 py-3 rounded-xl bg-surface-3 border-3 border-border focus:border-[#FFC83D]/60 text-sm font-bold outline-none transition-colors resize-none h-20"
maxLength={150} maxLength={150}
/> dir="rtl"
</div>
<div className="flex flex-col gap-1.5">
<label className="text-xs font-semibold text-text-secondary">رمز الدولة</label>
<input
type="text"
value={editForm.country_code}
onChange={(e) => setEditForm({ ...editForm, country_code: e.target.value })}
placeholder="EG"
className="px-4 py-3 rounded-xl bg-surface-3 border-2 border-transparent focus:border-gold/60 text-sm outline-none transition-colors"
maxLength={2}
/> />
</div> </div>
</div> </div>
...@@ -308,6 +245,7 @@ export function ProfilePage() { ...@@ -308,6 +245,7 @@ export function ProfilePage() {
onClick={handleSave} onClick={handleSave}
loading={saving} loading={saving}
disabled={!editForm.display_name.trim()} disabled={!editForm.display_name.trim()}
className="w-[80%] mx-auto"
> >
حفظ التعديلات حفظ التعديلات
</Button> </Button>
...@@ -319,54 +257,12 @@ export function ProfilePage() { ...@@ -319,54 +257,12 @@ export function ProfilePage() {
) )
} }
function ResultBadge({ result }: { result: 'win' | 'loss' | 'draw' }) { function StatBox({ icon, value, label }: { icon: React.ReactNode; value: number | string; label: string }) {
const config = {
win: { icon: <Trophy size={14} />, bg: 'bg-cyan/15 text-cyan', label: 'فوز' },
loss: { icon: <X size={14} />, bg: 'bg-coral/15 text-coral', label: 'خسارة' },
draw: { icon: <Minus size={14} />, bg: 'bg-text-muted/15 text-text-muted', label: 'تعادل' },
}
const c = config[result]
return (
<div className={`w-8 h-8 rounded-lg flex items-center justify-center ${c.bg}`}>
{c.icon}
</div>
)
}
function RatingBadge({ change }: { change: number }) {
if (change === 0) return <span className="text-xs text-text-muted font-medium">+0</span>
if (change > 0) {
return (
<span className="flex items-center gap-0.5 text-xs font-bold text-cyan">
<ArrowUpRight size={12} />+{change}
</span>
)
}
return (
<span className="flex items-center gap-0.5 text-xs font-bold text-coral">
<ArrowDownRight size={12} />{change}
</span>
)
}
function formatRelativeTime(dateStr: string) {
const now = Date.now()
const then = new Date(dateStr).getTime()
const diff = Math.floor((now - then) / 1000)
if (diff < 60) return 'الان'
if (diff < 3600) return `${Math.floor(diff / 60)} د`
if (diff < 86400) return `${Math.floor(diff / 3600)} س`
if (diff < 604800) return `${Math.floor(diff / 86400)} ي`
return `${Math.floor(diff / 604800)} أ`
}
function StatMini({ icon, value, label }: { icon: React.ReactNode; value: number | string; label: string }) {
return ( return (
<div className="flex flex-col items-center gap-1.5 p-3 rounded-xl bg-surface-1 border border-border/80"> <div className="flex flex-col items-center gap-1.5 p-3 rounded-2xl bg-surface-1 border-3 border-border">
{icon} {icon}
<span className="text-base font-bold">{value}</span> <span className="text-base font-black">{value}</span>
<span className="text-[9px] text-text-muted text-center leading-tight font-medium">{label}</span> <span className="text-[9px] text-text-muted text-center leading-tight font-bold">{label}</span>
</div> </div>
) )
} }
...@@ -50,27 +50,41 @@ export function RegisterPage() { ...@@ -50,27 +50,41 @@ export function RegisterPage() {
} }
return ( return (
<div className="min-h-dvh flex flex-col items-center justify-center px-7 relative overflow-hidden"> <div className="min-h-dvh flex items-center justify-center relative overflow-hidden bg-background">
<motion.div <motion.div
className="absolute inset-0 opacity-30" className="absolute inset-0"
style={{ style={{
background: 'radial-gradient(ellipse at 50% 30%, rgba(212,168,67,0.15) 0%, transparent 60%)', background: 'radial-gradient(ellipse at 50% 30%, rgba(255,200,61,0.12) 0%, transparent 60%)',
}} }}
animate={{
background: [
'radial-gradient(ellipse at 50% 30%, rgba(255,200,61,0.12) 0%, transparent 60%)',
'radial-gradient(ellipse at 50% 30%, rgba(255,200,61,0.05) 0%, transparent 60%)',
'radial-gradient(ellipse at 50% 30%, rgba(255,200,61,0.12) 0%, transparent 60%)',
],
}}
transition={{ duration: 4, repeat: Infinity, ease: 'easeInOut' }}
/> />
<motion.div <motion.div
className="relative w-full max-w-[310px]" className="relative w-full max-w-[380px] px-5"
initial={{ opacity: 0, y: 30 }} initial={{ opacity: 0, y: 40 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 25 }} transition={{ type: 'spring', stiffness: 400, damping: 22 }}
> >
<div className="flex flex-col items-center mb-6"> <div className="flex flex-col items-center mb-6">
<GoldCrown size={56} /> <GoldCrown size={72} />
<h1 className="mt-3 text-2xl font-black text-gold">حساب جديد</h1> <h1 className="mt-4 text-4xl font-black text-gold">EL3AB</h1>
<p className="mt-1 text-sm text-text-secondary">انضم الى مجتمع EL3AB</p> <p className="mt-2 text-lg font-bold text-text-secondary">حساب جديد</p>
</div> </div>
<form onSubmit={handleRegister} className="flex flex-col gap-3.5 p-7 rounded-2xl bg-surface-3 border-2 border-gold/30 shadow-[0_8px_40px_rgba(0,0,0,0.6),0_0_20px_rgba(212,168,67,0.08)]"> <motion.form
onSubmit={handleRegister}
className="flex flex-col gap-4 p-6 rounded-2xl bg-surface-2 border-3 border-border shadow-xl"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ type: 'spring', stiffness: 450, damping: 25, delay: 0.1 }}
>
<Input <Input
label="اسم المستخدم" label="اسم المستخدم"
value={username} value={username}
...@@ -100,25 +114,26 @@ export function RegisterPage() { ...@@ -100,25 +114,26 @@ export function RegisterPage() {
{error && ( {error && (
<motion.p <motion.p
className="text-sm text-coral text-center" className="text-sm font-bold text-coral text-center"
initial={{ opacity: 0 }} initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1 }} animate={{ opacity: 1, scale: 1 }}
transition={{ type: 'spring', stiffness: 500, damping: 20 }}
> >
{error} {error}
</motion.p> </motion.p>
)} )}
<Button type="submit" loading={loading} className="w-[85%] mx-auto mt-2"> <Button type="submit" variant="gold" size="lg" loading={loading} className="w-[80%] mx-auto mt-2">
انشاء حساب انشاء حساب
</Button> </Button>
</form> </motion.form>
<motion.button <motion.button
onClick={() => navigate('/login')} onClick={() => navigate('/login')}
className="mt-4 w-full text-center text-sm text-text-secondary hover:text-gold transition-colors" className="mt-5 w-full text-center text-sm font-bold text-text-secondary hover:text-gold transition-colors"
whileTap={{ scale: 0.98 }} whileTap={{ scale: 0.96 }}
> >
لديك حساب بالفعل؟ <span className="text-gold font-semibold">سجل دخول</span> لديك حساب بالفعل؟ <span className="text-gold font-black">سجل دخول</span>
</motion.button> </motion.button>
</motion.div> </motion.div>
</div> </div>
......
...@@ -16,64 +16,63 @@ export function SettingsPage() { ...@@ -16,64 +16,63 @@ export function SettingsPage() {
} }
return ( return (
<PageTransition className="px-7 py-7 flex flex-col gap-5"> <PageTransition>
<h1 className="text-xl font-bold">الاعدادات</h1> <h1 className="text-2xl font-black">الاعدادات</h1>
<div className="flex flex-col gap-4">
<Card className="flex items-center justify-between"> <Card className="flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
{soundEnabled ? <Volume2 size={18} className="text-cyan" /> : <VolumeX size={18} className="text-text-muted" />} {soundEnabled ? <Volume2 size={20} className="text-cyan" /> : <VolumeX size={20} className="text-text-muted" />}
<span className="text-sm font-semibold">الاصوات</span> <span className="text-sm font-bold">الاصوات</span>
</div> </div>
<motion.button <motion.button
className={`w-12 h-6 rounded-full p-0.5 ${soundEnabled ? 'bg-cyan' : 'bg-surface-3'}`} className={`w-14 h-7 rounded-full p-1 border-2 ${soundEnabled ? 'bg-cyan/20 border-cyan' : 'bg-surface-3 border-border'}`}
onClick={() => setSoundEnabled(!soundEnabled)} onClick={() => setSoundEnabled(!soundEnabled)}
whileTap={{ scale: 0.9 }} whileTap={{ scale: 0.9 }}
> >
<motion.div <motion.div
className="w-5 h-5 rounded-full bg-white" className={`w-5 h-5 rounded-full ${soundEnabled ? 'bg-cyan' : 'bg-text-muted'}`}
animate={{ x: soundEnabled ? 0 : 24 }} animate={{ x: soundEnabled ? 0 : 28 }}
transition={{ type: 'spring', stiffness: 500, damping: 30 }} transition={{ type: 'spring', stiffness: 500, damping: 30 }}
/> />
</motion.button> </motion.button>
</Card> </Card>
<Card className="flex items-center gap-3"> <Card className="flex items-center gap-3">
<Info size={18} className="text-text-muted" /> <Info size={20} className="text-gold" />
<div> <div>
<p className="text-sm font-semibold">EL3AB Player</p> <p className="text-sm font-bold">EL3AB Player</p>
<p className="text-xs text-text-muted">الاصدار 1.0.0</p> <p className="text-xs text-text-muted font-semibold">الاصدار 1.0.0</p>
</div> </div>
</Card> </Card>
<Card className="flex items-center gap-3"> <Card className="flex items-center gap-3">
<Bug size={18} className="text-text-muted" /> <Bug size={20} className="text-purple" />
<div> <div>
<p className="text-sm font-semibold">الابلاغ عن مشكلة</p> <p className="text-sm font-bold">الابلاغ عن مشكلة</p>
<p className="text-xs text-text-muted">ساعدنا نحسن التطبيق</p> <p className="text-xs text-text-muted font-semibold">ساعدنا في تحسين التطبيق</p>
</div> </div>
</Card> </Card>
</div>
<div className="pt-4"> <div className="flex flex-col gap-3 mt-4">
<motion.button <motion.button
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.95 }}
onClick={handleLogout} onClick={handleLogout}
className="w-full flex items-center justify-center gap-2.5 px-5 py-3.5 rounded-xl bg-coral/10 border border-coral/20 text-coral text-sm font-bold" className="flex items-center justify-center gap-2 w-[80%] mx-auto px-6 py-3 rounded-2xl bg-coral/10 border-2 border-coral/30 text-coral font-bold text-sm"
> >
<LogOut size={16} /> <LogOut size={16} />
<span>تسجيل الخروج</span> <span>تسجيل الخروج</span>
</motion.button> </motion.button>
</div>
<div className="pt-2"> <motion.button
<h3 className="text-xs font-bold text-text-muted mb-3">منطقة الخطر</h3>
<button
disabled disabled
className="w-full flex items-center justify-center gap-2.5 px-5 py-3.5 rounded-xl bg-surface-2 border border-border text-text-muted text-sm font-semibold opacity-50 cursor-not-allowed" className="flex items-center justify-center gap-2 w-[80%] mx-auto px-6 py-3 rounded-2xl bg-surface-2 border-2 border-border text-text-muted font-bold text-sm opacity-50 cursor-not-allowed"
> >
<Trash2 size={16} /> <Trash2 size={16} />
<span>حذف الحساب</span> <span>حذف الحساب</span>
<span className="text-[10px] mr-1 px-2 py-0.5 rounded-full bg-surface-3 text-text-muted">قريبا</span> <span className="text-[10px] bg-surface-3 px-2 py-0.5 rounded-full">قريبا</span>
</button> </motion.button>
</div> </div>
</PageTransition> </PageTransition>
) )
......
...@@ -57,20 +57,20 @@ export function ShopPage() { ...@@ -57,20 +57,20 @@ export function ShopPage() {
} }
return ( return (
<PageTransition className="px-7 py-7 flex flex-col gap-5"> <PageTransition className="flex flex-col gap-5">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2.5">
<ShoppingBag size={22} className="text-gold" /> <ShoppingBag size={24} className="text-[#FFC83D]" />
<h1 className="text-xl font-bold">المتجر</h1> <h1 className="text-2xl font-black">المتجر</h1>
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-2">
<div className="flex items-center gap-1.5 bg-surface-2 border border-border rounded-xl px-3 py-1.5"> <div className="flex items-center gap-1.5 border-3 border-[#FFC83D]/50 rounded-full px-3 py-1.5 bg-[#FFC83D]/10">
<Coins size={14} className="text-gold" /> <Coins size={14} className="text-[#FFC83D]" />
<span className="text-sm font-bold text-gold">{profile?.coins || 0}</span> <span className="text-sm font-black text-[#FFC83D]">{profile?.coins || 0}</span>
</div> </div>
<div className="flex items-center gap-1.5 bg-surface-2 border border-border rounded-xl px-3 py-1.5"> <div className="flex items-center gap-1.5 border-3 border-[#B44DFF]/50 rounded-full px-3 py-1.5 bg-[#B44DFF]/10">
<Gem size={14} className="text-purple" /> <Gem size={14} className="text-[#B44DFF]" />
<span className="text-sm font-bold text-purple">{profile?.gems || 0}</span> <span className="text-sm font-black text-[#B44DFF]">{profile?.gems || 0}</span>
</div> </div>
</div> </div>
</div> </div>
...@@ -80,12 +80,13 @@ export function ShopPage() { ...@@ -80,12 +80,13 @@ export function ShopPage() {
<motion.button <motion.button
key={opt.key} key={opt.key}
onClick={() => setFilter(opt.key)} onClick={() => setFilter(opt.key)}
className={`px-4 py-2 rounded-xl text-xs font-bold whitespace-nowrap transition-colors ${ className={`px-4 py-2 rounded-xl text-xs font-bold whitespace-nowrap border-2 transition-colors ${
filter === opt.key filter === opt.key
? 'bg-gold/15 border border-gold/40 text-gold' ? 'bg-[#FF8C42] border-[#FF8C42] text-white'
: 'bg-surface-2 border border-border text-text-muted' : 'bg-surface-2 border-border text-text-muted hover:border-[#FF8C42]/40'
}`} }`}
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.93 }}
transition={{ type: 'spring', stiffness: 500, damping: 20 }}
> >
{opt.label} {opt.label}
</motion.button> </motion.button>
...@@ -95,25 +96,25 @@ export function ShopPage() { ...@@ -95,25 +96,25 @@ export function ShopPage() {
{loading ? ( {loading ? (
<div className="flex-1 flex items-center justify-center py-20"> <div className="flex-1 flex items-center justify-center py-20">
<motion.div <motion.div
className="w-8 h-8 border-2 border-gold/30 border-t-gold rounded-full" className="w-10 h-10 border-3 border-[#FFC83D]/30 border-t-[#FFC83D] rounded-full"
animate={{ rotate: 360 }} animate={{ rotate: 360 }}
transition={{ duration: 1, repeat: Infinity, ease: 'linear' }} transition={{ duration: 0.8, repeat: Infinity, ease: 'linear' }}
/> />
</div> </div>
) : items.length === 0 ? ( ) : items.length === 0 ? (
<div className="flex-1 flex flex-col items-center justify-center py-16 gap-4"> <div className="flex-1 flex flex-col items-center justify-center py-16 gap-4">
<motion.div <motion.div
className="w-16 h-16 rounded-full bg-surface-2 border border-border flex items-center justify-center" className="w-20 h-20 rounded-2xl bg-surface-2 border-3 border-border flex items-center justify-center"
initial={{ scale: 0 }} initial={{ scale: 0, rotate: -10 }}
animate={{ scale: 1 }} animate={{ scale: 1, rotate: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 20 }} transition={{ type: 'spring', stiffness: 300, damping: 18 }}
> >
<ShoppingBag size={24} className="text-text-muted" /> <ShoppingBag size={32} className="text-text-muted" />
</motion.div> </motion.div>
<p className="text-text-muted text-sm">لا توجد عناصر متاحة</p> <p className="text-text-muted text-sm font-bold">لا توجد عناصر متاحة</p>
</div> </div>
) : ( ) : (
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-4">
{items.map((item, index) => { {items.map((item, index) => {
const isOwned = ownedIds.includes(item.id) const isOwned = ownedIds.includes(item.id)
const isEquipped = equippedIds.includes(item.id) const isEquipped = equippedIds.includes(item.id)
...@@ -124,25 +125,35 @@ export function ShopPage() { ...@@ -124,25 +125,35 @@ export function ShopPage() {
<motion.button <motion.button
key={item.id} key={item.id}
onClick={() => setSelectedItem(item)} onClick={() => setSelectedItem(item)}
className="relative flex flex-col items-center gap-2 p-4 rounded-2xl bg-surface-1 border overflow-hidden text-center" className={`relative flex flex-col items-center gap-2 p-4 rounded-2xl bg-surface-1 border-3 overflow-hidden text-center ${isLegendary ? 'animate-pulse-glow' : ''}`}
style={{ borderColor: `${rarityColor}40` }} style={{ borderColor: rarityColor }}
initial={{ opacity: 0, y: 10 }} initial={{ opacity: 0, y: 16, scale: 0.9 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{ delay: index * 0.05 }} transition={{ type: 'spring', stiffness: 400, damping: 22, delay: index * 0.06 }}
whileTap={{ scale: 0.96 }} whileHover={{ y: -6, scale: 1.03, boxShadow: `0 12px 30px ${rarityColor}30` }}
whileTap={{ scale: 0.95 }}
> >
{isLegendary && ( {isLegendary && (
<motion.div <motion.div
className="absolute inset-0 rounded-2xl" className="absolute inset-0 rounded-2xl pointer-events-none"
style={{ boxShadow: `inset 0 0 20px ${rarityColor}20, 0 0 15px ${rarityColor}15` }} style={{ boxShadow: `inset 0 0 24px ${rarityColor}25, 0 0 20px ${rarityColor}20` }}
animate={{ opacity: [0.5, 1, 0.5] }} animate={{ opacity: [0.4, 1, 0.4] }}
transition={{ duration: 2, repeat: Infinity }} transition={{ duration: 2, repeat: Infinity }}
/> />
)} )}
{isOwned && (
<div className="absolute top-2 left-2 z-10 flex items-center gap-1 bg-green-500/20 border-2 border-green-500/50 rounded-lg px-2 py-0.5">
<Check size={10} className="text-green-400" />
<span className="text-[9px] font-black text-green-400">
{isEquipped ? 'مفعّل' : 'مملوك'}
</span>
</div>
)}
<div <div
className="w-full aspect-square rounded-xl bg-surface-2 flex items-center justify-center relative" className="w-full aspect-square rounded-xl bg-surface-2 border-2 flex items-center justify-center"
style={{ borderColor: `${rarityColor}30` }} style={{ borderColor: `${rarityColor}40` }}
> >
{item.preview_url ? ( {item.preview_url ? (
<img <img
...@@ -152,40 +163,33 @@ export function ShopPage() { ...@@ -152,40 +163,33 @@ export function ShopPage() {
/> />
) : ( ) : (
<div <div
className="w-12 h-12 rounded-xl" className="w-14 h-14 rounded-full"
style={{ background: `linear-gradient(135deg, ${rarityColor}40, ${rarityColor}10)` }} style={{ background: `radial-gradient(circle, ${rarityColor}60, ${rarityColor}15)` }}
/> />
)} )}
</div> </div>
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<div <div
className="w-2 h-2 rounded-full" className="w-2.5 h-2.5 rounded-full border-2"
style={{ backgroundColor: rarityColor }} style={{ backgroundColor: rarityColor, borderColor: `${rarityColor}80` }}
/> />
<span className="text-xs font-bold text-text-primary truncate max-w-[90px]"> <span className="text-xs font-black text-text-primary truncate max-w-[90px]">
{item.name_ar} {item.name_ar}
</span> </span>
</div> </div>
{isOwned ? ( {!isOwned && (
<div className="flex items-center gap-1 bg-green-500/10 border border-green-500/30 rounded-lg px-2 py-0.5"> <div className="flex items-center gap-1.5 bg-surface-2 border-2 border-border rounded-lg px-2.5 py-1">
<Check size={10} className="text-green-400" />
<span className="text-[10px] font-bold text-green-400">
{isEquipped ? 'مفعّل' : 'مملوك'}
</span>
</div>
) : (
<div className="flex items-center gap-1">
{item.price_gems ? ( {item.price_gems ? (
<> <>
<Gem size={12} className="text-purple" /> <Gem size={12} className="text-[#B44DFF]" />
<span className="text-xs font-bold text-purple">{item.price_gems}</span> <span className="text-xs font-black text-[#B44DFF]">{item.price_gems}</span>
</> </>
) : ( ) : (
<> <>
<Coins size={12} className="text-gold" /> <Coins size={12} className="text-[#FFC83D]" />
<span className="text-xs font-bold text-gold">{item.price_coins}</span> <span className="text-xs font-black text-[#FFC83D]">{item.price_coins}</span>
</> </>
)} )}
</div> </div>
...@@ -206,16 +210,16 @@ export function ShopPage() { ...@@ -206,16 +210,16 @@ export function ShopPage() {
onClick={() => !purchasing && setSelectedItem(null)} onClick={() => !purchasing && setSelectedItem(null)}
> >
<motion.div <motion.div
className="w-full max-w-[320px] rounded-3xl bg-surface-1 border border-border p-6 flex flex-col items-center gap-4 relative overflow-hidden" className="w-full max-w-[340px] rounded-3xl bg-surface-1 border-3 border-border p-6 flex flex-col items-center gap-4 relative overflow-hidden"
initial={{ scale: 0.8, opacity: 0 }} initial={{ scale: 0.7, opacity: 0, rotate: -3 }}
animate={{ scale: 1, opacity: 1 }} animate={{ scale: 1, opacity: 1, rotate: 0 }}
exit={{ scale: 0.8, opacity: 0 }} exit={{ scale: 0.7, opacity: 0, rotate: 3 }}
transition={{ type: 'spring', stiffness: 300, damping: 25 }} transition={{ type: 'spring', stiffness: 350, damping: 22 }}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<button <button
onClick={() => !purchasing && setSelectedItem(null)} onClick={() => !purchasing && setSelectedItem(null)}
className="absolute top-4 left-4 w-8 h-8 rounded-full bg-surface-2 border border-border flex items-center justify-center" className="absolute top-4 left-4 w-9 h-9 rounded-full bg-surface-2 border-2 border-border flex items-center justify-center hover:border-coral"
> >
<X size={16} className="text-text-muted" /> <X size={16} className="text-text-muted" />
</button> </button>
...@@ -225,33 +229,22 @@ export function ShopPage() { ...@@ -225,33 +229,22 @@ export function ShopPage() {
className="flex flex-col items-center gap-4 py-8" className="flex flex-col items-center gap-4 py-8"
initial={{ scale: 0 }} initial={{ scale: 0 }}
animate={{ scale: 1 }} animate={{ scale: 1 }}
transition={{ type: 'spring', stiffness: 300, damping: 20 }} transition={{ type: 'spring', stiffness: 300, damping: 18 }}
> >
<motion.div <motion.div
className="w-16 h-16 rounded-full bg-green-500/10 border-2 border-green-500/40 flex items-center justify-center" className="w-20 h-20 rounded-full bg-green-500/10 border-3 border-green-500/50 flex items-center justify-center"
animate={{ scale: [1, 1.1, 1] }} animate={{ scale: [1, 1.15, 1] }}
transition={{ duration: 0.5 }} transition={{ duration: 0.5 }}
> >
<Check size={32} className="text-green-400" /> <Check size={36} className="text-green-400" />
</motion.div>
<p className="text-lg font-bold text-green-400">تم الشراء بنجاح</p>
<motion.div
className="flex items-center gap-2"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
>
<Coins size={16} className="text-gold" />
<span className="text-sm text-text-muted">
-{selectedItem.price_gems || selectedItem.price_coins}
</span>
</motion.div> </motion.div>
<p className="text-xl font-black text-green-400">تم الشراء بنجاح</p>
</motion.div> </motion.div>
) : ( ) : (
<> <>
<div <div
className="w-full aspect-[4/3] rounded-2xl bg-surface-2 flex items-center justify-center mt-4" className="w-full aspect-[4/3] rounded-2xl bg-surface-2 border-2 flex items-center justify-center mt-6"
style={{ borderColor: `${RARITY_COLORS[selectedItem.rarity]}30` }} style={{ borderColor: `${RARITY_COLORS[selectedItem.rarity]}40` }}
> >
{selectedItem.preview_url ? ( {selectedItem.preview_url ? (
<img <img
...@@ -261,16 +254,16 @@ export function ShopPage() { ...@@ -261,16 +254,16 @@ export function ShopPage() {
/> />
) : ( ) : (
<div <div
className="w-20 h-20 rounded-2xl" className="w-24 h-24 rounded-full"
style={{ style={{
background: `linear-gradient(135deg, ${RARITY_COLORS[selectedItem.rarity]}40, ${RARITY_COLORS[selectedItem.rarity]}10)`, background: `radial-gradient(circle, ${RARITY_COLORS[selectedItem.rarity]}60, ${RARITY_COLORS[selectedItem.rarity]}15)`,
}} }}
/> />
)} )}
</div> </div>
<div className="text-center"> <div className="text-center">
<h3 className="text-lg font-black text-text-primary">{selectedItem.name_ar}</h3> <h3 className="text-xl font-black text-text-primary">{selectedItem.name_ar}</h3>
{selectedItem.description && ( {selectedItem.description && (
<p className="text-xs text-text-muted mt-1">{selectedItem.description}</p> <p className="text-xs text-text-muted mt-1">{selectedItem.description}</p>
)} )}
...@@ -278,26 +271,26 @@ export function ShopPage() { ...@@ -278,26 +271,26 @@ export function ShopPage() {
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div <div
className="w-2.5 h-2.5 rounded-full" className="w-3 h-3 rounded-full border-2"
style={{ backgroundColor: RARITY_COLORS[selectedItem.rarity] }} style={{ backgroundColor: RARITY_COLORS[selectedItem.rarity], borderColor: `${RARITY_COLORS[selectedItem.rarity]}80` }}
/> />
<span <span
className="text-xs font-bold" className="text-sm font-black"
style={{ color: RARITY_COLORS[selectedItem.rarity] }} style={{ color: RARITY_COLORS[selectedItem.rarity] }}
> >
{RARITY_LABELS[selectedItem.rarity]} {RARITY_LABELS[selectedItem.rarity]}
</span> </span>
{selectedItem.rarity === 'legendary' && ( {selectedItem.rarity === 'legendary' && (
<Sparkles size={12} style={{ color: RARITY_COLORS.legendary }} /> <Sparkles size={14} style={{ color: RARITY_COLORS.legendary }} />
)} )}
</div> </div>
{ownedIds.includes(selectedItem.id) ? ( {ownedIds.includes(selectedItem.id) ? (
<div className="flex flex-col gap-3 w-full"> <div className="flex flex-col gap-3 w-[70%] mx-auto">
{equippedIds.includes(selectedItem.id) ? ( {equippedIds.includes(selectedItem.id) ? (
<div className="flex items-center justify-center gap-2 py-3 rounded-xl bg-green-500/10 border border-green-500/30"> <div className="flex items-center justify-center gap-2 py-3 rounded-xl bg-green-500/10 border-3 border-green-500/40">
<Check size={16} className="text-green-400" /> <Check size={16} className="text-green-400" />
<span className="text-sm font-bold text-green-400">مفعّل حاليا</span> <span className="text-sm font-black text-green-400">مفعّل حاليا</span>
</div> </div>
) : ( ) : (
<Button <Button
...@@ -312,17 +305,17 @@ export function ShopPage() { ...@@ -312,17 +305,17 @@ export function ShopPage() {
)} )}
</div> </div>
) : ( ) : (
<div className="flex flex-col gap-3 w-full"> <div className="flex flex-col gap-3 w-[70%] mx-auto">
<div className="flex items-center justify-center gap-2 bg-surface-2 rounded-xl px-4 py-3 border border-border"> <div className="flex items-center justify-center gap-2 bg-surface-2 rounded-xl px-4 py-3 border-2 border-border">
{selectedItem.price_gems ? ( {selectedItem.price_gems ? (
<> <>
<Gem size={18} className="text-purple" /> <Gem size={18} className="text-[#B44DFF]" />
<span className="text-lg font-black text-purple">{selectedItem.price_gems}</span> <span className="text-lg font-black text-[#B44DFF]">{selectedItem.price_gems}</span>
</> </>
) : ( ) : (
<> <>
<Coins size={18} className="text-gold" /> <Coins size={18} className="text-[#FFC83D]" />
<span className="text-lg font-black text-gold">{selectedItem.price_coins}</span> <span className="text-lg font-black text-[#FFC83D]">{selectedItem.price_coins}</span>
</> </>
)} )}
</div> </div>
...@@ -343,7 +336,7 @@ export function ShopPage() { ...@@ -343,7 +336,7 @@ export function ShopPage() {
{(selectedItem.price_gems {(selectedItem.price_gems
? (profile?.gems || 0) < (selectedItem.price_gems || 0) ? (profile?.gems || 0) < (selectedItem.price_gems || 0)
: (profile?.coins || 0) < (selectedItem.price_coins || 0)) && ( : (profile?.coins || 0) < (selectedItem.price_coins || 0)) && (
<p className="text-[10px] text-coral text-center">رصيد غير كافي</p> <p className="text-[11px] text-[#FF5252] text-center font-bold">رصيد غير كافي</p>
)} )}
</div> </div>
)} )}
......
...@@ -57,7 +57,7 @@ export function SplashPage() { ...@@ -57,7 +57,7 @@ export function SplashPage() {
</motion.div> </motion.div>
<motion.h1 <motion.h1
className="mt-6 text-4xl font-black text-gold tracking-wider" className="mt-6 text-5xl font-black text-gold tracking-wider"
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.6, duration: 0.5 }} transition={{ delay: 0.6, duration: 0.5 }}
......
import { useState } from 'react' import { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion' import { motion, AnimatePresence } from 'framer-motion'
import { Trophy, Users, Calendar, Clock, Coins, Gem } from 'lucide-react' import { Trophy, Users, Clock, Coins, Gem } from 'lucide-react'
import { PageTransition } from '../components/layout/PageTransition' import { PageTransition } from '../components/layout/PageTransition'
import { Card } from '../components/ui/Card' import { Card } from '../components/ui/Card'
import { Button } from '../components/ui/Button'
import { useAuthStore } from '../stores/authStore' import { useAuthStore } from '../stores/authStore'
import { useTournaments } from '../hooks/useTournaments' import { useTournaments } from '../hooks/useTournaments'
...@@ -32,39 +33,18 @@ const TIME_CONTROL_LABELS: Record<string, string> = { ...@@ -32,39 +33,18 @@ const TIME_CONTROL_LABELS: Record<string, string> = {
classical: 'كلاسيكي', classical: 'كلاسيكي',
} }
function formatRelativeTime(dateStr: string): string {
const date = new Date(dateStr)
const now = new Date()
const diff = date.getTime() - now.getTime()
const absDiff = Math.abs(diff)
const minutes = Math.floor(absDiff / 60000)
const hours = Math.floor(minutes / 60)
const days = Math.floor(hours / 24)
if (diff > 0) {
if (days > 0) return `بعد ${days} يوم`
if (hours > 0) return `بعد ${hours} ساعة`
return `بعد ${minutes} دقيقة`
} else {
if (days > 0) return `منذ ${days} يوم`
if (hours > 0) return `منذ ${hours} ساعة`
return `منذ ${minutes} دقيقة`
}
}
function StatusBadge({ status }: { status: string }) { function StatusBadge({ status }: { status: string }) {
const config: Record<string, { label: string; classes: string }> = { const config: Record<string, { label: string; classes: string }> = {
registration: { label: 'مفتوحة', classes: 'bg-cyan/10 text-cyan border-cyan/30' }, registration: { label: 'مفتوحة', classes: 'bg-[#00E5CC]/15 text-[#00E5CC] border-[#00E5CC]/50' },
in_progress: { label: 'جارية', classes: 'bg-gold/10 text-gold border-gold/30 animate-pulse' }, in_progress: { label: 'جارية', classes: 'bg-[#FFC83D]/15 text-[#FFC83D] border-[#FFC83D]/50 animate-pulse' },
completed: { label: 'منتهية', classes: 'bg-surface-3 text-text-muted border-border' }, completed: { label: 'منتهية', classes: 'bg-surface-3 text-text-muted border-border' },
cancelled: { label: 'ملغاة', classes: 'bg-coral/10 text-coral border-coral/30' }, cancelled: { label: 'ملغاة', classes: 'bg-[#FF5252]/15 text-[#FF5252] border-[#FF5252]/50' },
} }
const c = config[status] ?? config.completed const c = config[status] ?? config.completed
return ( return (
<span className={`px-2 py-0.5 rounded-full text-[10px] font-bold border ${c.classes}`}> <span className={`px-3 py-1 rounded-full text-[10px] font-black border-2 ${c.classes}`}>
{c.label} {c.label}
</span> </span>
) )
...@@ -74,14 +54,16 @@ export function TournamentsPage() { ...@@ -74,14 +54,16 @@ export function TournamentsPage() {
const [activeFilter, setActiveFilter] = useState<StatusFilter>(null) const [activeFilter, setActiveFilter] = useState<StatusFilter>(null)
const { tournaments, myRegistrations, loading, register, unregister } = useTournaments(activeFilter) const { tournaments, myRegistrations, loading, register, unregister } = useTournaments(activeFilter)
const { user } = useAuthStore() const { user } = useAuthStore()
const [expandedId, setExpandedId] = useState<string | null>(null)
const isRegistered = (tournamentId: string) => const isRegistered = (tournamentId: string) =>
myRegistrations.some((r) => r.tournament_id === tournamentId) myRegistrations.some((r) => r.tournament_id === tournamentId)
return ( return (
<PageTransition className="px-7 py-7 flex flex-col gap-5"> <PageTransition className="flex flex-col gap-5">
<h1 className="text-xl font-bold">البطولات</h1> <div className="flex items-center gap-2.5">
<Trophy size={24} className="text-[#FFC83D]" />
<h1 className="text-2xl font-black">البطولات</h1>
</div>
<div className="flex gap-2 overflow-x-auto no-scrollbar pb-1"> <div className="flex gap-2 overflow-x-auto no-scrollbar pb-1">
{FILTERS.map((f) => ( {FILTERS.map((f) => (
...@@ -89,11 +71,12 @@ export function TournamentsPage() { ...@@ -89,11 +71,12 @@ export function TournamentsPage() {
key={f.label} key={f.label}
whileTap={{ scale: 0.93 }} whileTap={{ scale: 0.93 }}
onClick={() => setActiveFilter(f.value)} onClick={() => setActiveFilter(f.value)}
className={`px-4 py-1.5 rounded-full text-xs font-bold whitespace-nowrap transition-colors ${ className={`px-4 py-2 rounded-xl text-xs font-bold whitespace-nowrap border-2 transition-colors ${
activeFilter === f.value activeFilter === f.value
? 'bg-gold text-bg-primary' ? 'bg-[#00E5CC] border-[#00E5CC] text-background font-black'
: 'bg-surface-2 text-text-muted border border-border' : 'bg-surface-2 text-text-muted border-border hover:border-[#00E5CC]/40'
}`} }`}
transition={{ type: 'spring', stiffness: 500, damping: 20 }}
> >
{f.label} {f.label}
</motion.button> </motion.button>
...@@ -101,125 +84,104 @@ export function TournamentsPage() { ...@@ -101,125 +84,104 @@ export function TournamentsPage() {
</div> </div>
{loading ? ( {loading ? (
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-4">
{[1, 2, 3].map((i) => ( {[1, 2, 3].map((i) => (
<div key={i} className="rounded-2xl bg-surface-1 border border-border p-5 animate-pulse"> <div key={i} className="rounded-2xl bg-surface-1 border-3 border-border p-5 animate-pulse">
<div className="w-32 h-4 rounded bg-surface-3 mb-3" /> <div className="w-36 h-5 rounded bg-surface-3 mb-3" />
<div className="w-20 h-3 rounded bg-surface-3" /> <div className="w-24 h-3 rounded bg-surface-3" />
</div> </div>
))} ))}
</div> </div>
) : tournaments.length === 0 ? ( ) : tournaments.length === 0 ? (
<div className="flex flex-col items-center justify-center py-16 gap-4"> <div className="flex flex-col items-center justify-center py-16 gap-4">
<motion.div <motion.div
className="w-16 h-16 rounded-full bg-surface-2 border border-border flex items-center justify-center" className="w-20 h-20 rounded-2xl bg-surface-2 border-3 border-[#FFC83D]/30 flex items-center justify-center"
initial={{ scale: 0 }} initial={{ scale: 0, rotate: -10 }}
animate={{ scale: 1 }} animate={{ scale: 1, rotate: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 20 }} transition={{ type: 'spring', stiffness: 300, damping: 18 }}
> >
<Trophy size={24} className="text-gold" /> <Trophy size={32} className="text-[#FFC83D]" />
</motion.div> </motion.div>
<p className="text-text-muted text-sm">لا توجد بطولات حالية</p> <p className="text-text-muted text-sm font-bold">لا توجد بطولات</p>
</div> </div>
) : ( ) : (
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-4">
<AnimatePresence> <AnimatePresence>
{tournaments.map((t, i) => ( {tournaments.map((t, i) => (
<motion.div <motion.div
key={t.id} key={t.id}
initial={{ opacity: 0, y: 12 }} initial={{ opacity: 0, y: 20, scale: 0.95 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: -12 }} exit={{ opacity: 0, y: -16 }}
transition={{ delay: i * 0.05 }} transition={{ type: 'spring', stiffness: 400, damping: 24, delay: i * 0.06 }}
> >
<Card <Card className="flex flex-col gap-3 relative">
className="flex flex-col gap-3" <div className="absolute top-4 left-4">
onClick={() => setExpandedId(expandedId === t.id ? null : t.id)}
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-9 h-9 rounded-lg bg-gold/10 flex items-center justify-center">
<Trophy size={16} className="text-gold" />
</div>
<div>
<h3 className="text-sm font-bold leading-tight">{t.name_ar || t.name}</h3>
<p className="text-[11px] text-text-muted mt-0.5">
{FORMAT_LABELS[t.format] ?? t.format}
</p>
</div>
</div>
<StatusBadge status={t.status} /> <StatusBadge status={t.status} />
</div> </div>
<div className="flex items-center gap-4 text-[11px] text-text-muted"> <h3 className="text-base font-black leading-tight pr-0 pl-20">
{t.name_ar || t.name}
</h3>
<div className="flex items-center gap-4 text-xs text-text-muted font-bold">
<span className="flex items-center gap-1.5 bg-surface-2 border-2 border-border rounded-lg px-2 py-1">
{FORMAT_LABELS[t.format] ?? t.format}
</span>
<span className="flex items-center gap-1"> <span className="flex items-center gap-1">
<Clock size={11} /> <Clock size={12} />
{TIME_CONTROL_LABELS[t.time_control] ?? t.time_control} {TIME_CONTROL_LABELS[t.time_control] ?? t.time_control}
</span> </span>
<span className="flex items-center gap-1"> <span className="flex items-center gap-1">
<Users size={11} /> <Users size={12} />
{t.registrations_count}/{t.max_players} {t.registrations_count}/{t.max_players}
</span> </span>
<span className="flex items-center gap-1">
<Calendar size={11} />
{formatRelativeTime(t.starts_at)}
</span>
</div> </div>
{(t.prize_pool_coins > 0 || t.prize_pool_gems > 0) && ( {(t.prize_pool_coins > 0 || t.prize_pool_gems > 0) && (
<div className="flex items-center gap-3 text-[11px]"> <div className="flex items-center gap-3">
{t.prize_pool_coins > 0 && ( {t.prize_pool_coins > 0 && (
<span className="flex items-center gap-1 text-gold font-bold"> <span className="flex items-center gap-1.5 text-[#FFC83D] font-black text-sm">
<Coins size={12} className="text-gold" /> <Coins size={14} className="text-[#FFC83D]" />
{t.prize_pool_coins.toLocaleString('ar-EG')} {t.prize_pool_coins.toLocaleString('ar-EG')}
</span> </span>
)} )}
{t.prize_pool_gems > 0 && ( {t.prize_pool_gems > 0 && (
<span className="flex items-center gap-1 text-purple font-bold"> <span className="flex items-center gap-1.5 text-[#B44DFF] font-black text-sm">
<Gem size={12} className="text-purple" /> <Gem size={14} className="text-[#B44DFF]" />
{t.prize_pool_gems.toLocaleString('ar-EG')} {t.prize_pool_gems.toLocaleString('ar-EG')}
</span> </span>
)} )}
</div> </div>
)} )}
<AnimatePresence>
{expandedId === t.id && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.2 }}
className="overflow-hidden"
>
<div className="pt-3 border-t border-border flex flex-col gap-2">
<div className="grid grid-cols-2 gap-2 text-[11px] text-text-muted">
<span>الجولات: {t.rounds_total}</span>
<span>الجولة الحالية: {t.current_round}</span>
<span>الحد الأدنى: {t.min_players} لاعب</span>
<span>مصنفة: {t.is_rated ? 'نعم' : 'لا'}</span>
</div>
{t.status === 'registration' && user && ( {t.status === 'registration' && user && (
<div className="flex justify-center mt-1">
{isRegistered(t.id) ? (
<div className="flex items-center gap-2">
<span className="px-4 py-2 rounded-xl bg-[#FFC83D]/15 border-2 border-[#FFC83D]/50 text-[#FFC83D] text-xs font-black">
مسجل
</span>
<motion.button <motion.button
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.93 }}
onClick={(e) => { onClick={() => unregister(t.id)}
e.stopPropagation() className="px-3 py-2 rounded-xl bg-[#FF5252]/10 border-2 border-[#FF5252]/30 text-[#FF5252] text-xs font-bold"
isRegistered(t.id) ? unregister(t.id) : register(t.id)
}}
className={`mt-2 w-full py-2.5 rounded-xl text-sm font-bold transition-colors ${
isRegistered(t.id)
? 'bg-coral/10 text-coral border border-coral/30'
: 'bg-gold text-bg-primary'
}`}
> >
{isRegistered(t.id) ? 'إلغاء التسجيل' : 'سجّل الآن'} إلغاء
</motion.button> </motion.button>
</div>
) : (
<Button
variant="cyan"
size="sm"
onClick={() => register(t.id)}
className="w-[80%] mx-auto"
>
سجل
</Button>
)} )}
</div> </div>
</motion.div>
)} )}
</AnimatePresence>
</Card> </Card>
</motion.div> </motion.div>
))} ))}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment