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
onClick={onClose}
>
<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 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.8, opacity: 0 }}
......@@ -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" />
<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] }}
transition={{ duration: 3, repeat: Infinity }}
>
......@@ -73,7 +73,7 @@ export function DailyRewardModal({ open, streak, reward, loading, onClaim, onClo
size="lg"
onClick={onClaim}
loading={loading}
className="w-full"
className="w-[80%] mx-auto"
>
استلم المكافأة
</Button>
......
......@@ -10,9 +10,9 @@ export function AppShell() {
useNotifications()
return (
<div className="flex flex-col min-h-dvh bg-[#0a0a12]">
<div className="flex flex-col min-h-dvh bg-background">
<Header />
<main className="flex-1 pb-24 overflow-y-auto">
<main className="flex-1 pb-28 overflow-y-auto">
<Outlet />
</main>
<BottomNav />
......
......@@ -15,8 +15,9 @@ export function BottomNav() {
const navigate = useNavigate()
return (
<nav className="fixed bottom-0 left-0 right-0 z-50 px-5 pb-3 pt-1">
<div className="flex items-center justify-around px-3 py-3 bg-surface-2 border border-border rounded-[18px] shadow-2xl shadow-black/40">
<nav className="fixed bottom-0 left-0 right-0 z-50 px-4 pb-3 pt-1 md:px-6">
<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) => {
const isActive = location.pathname === item.path
const Icon = item.icon
......@@ -25,28 +26,38 @@ export function BottomNav() {
<motion.button
key={item.path}
onClick={() => navigate(item.path)}
className="flex flex-col items-center gap-1 px-2 py-1 rounded-xl relative"
whileTap={{ scale: 0.85 }}
className={`flex flex-col items-center gap-0.5 px-3 py-2 rounded-xl relative ${
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
size={20}
size={22}
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}
</span>
{isActive && (
<motion.div
className="absolute -bottom-1 w-4 h-[3px] rounded-full bg-gold"
layoutId="nav-dot"
style={{ boxShadow: '0 0 6px rgba(212, 168, 67, 0.7)' }}
className="absolute -bottom-0.5 w-6 h-[4px] rounded-full bg-gold"
layoutId="nav-indicator"
style={{ boxShadow: '0 0 10px rgba(255, 200, 60, 0.8)' }}
transition={{ type: 'spring', stiffness: 500, damping: 30 }}
/>
)}
</motion.button>
)
})}
</div>
</div>
</nav>
)
}
import { motion } from 'framer-motion'
import { Bell, Coins } from 'lucide-react'
import { Bell, Coins, Gem } from 'lucide-react'
import { useNotificationStore } from '../../stores/notificationStore'
import { useAuthStore } from '../../stores/authStore'
import { AnimatedIcon } from '../icons/AnimatedIcon'
import { GoldCrown } from '../icons/GoldCrown'
import { useNavigate } from 'react-router-dom'
......@@ -12,32 +11,55 @@ export function Header() {
const navigate = useNavigate()
return (
<header className="sticky top-0 z-50 px-5 pt-3 pb-1">
<div className="flex items-center justify-between px-5 py-3 bg-surface-2 border border-border rounded-[18px]">
<div className="flex items-center gap-2.5">
<GoldCrown size={24} animate={false} />
<span className="text-sm font-bold text-gold tracking-wide">EL3AB</span>
<header className="sticky top-0 z-50 px-4 pt-3 pb-1 md:px-6">
<div className="app-container !p-0">
<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">
<div className="flex items-center gap-2">
<GoldCrown size={28} animate={false} />
<span className="text-base font-black text-gold tracking-wider">EL3AB</span>
</div>
<div className="flex items-center gap-3">
<div className="flex items-center gap-2">
{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" />
<span className="text-[11px] font-bold text-gold">{profile.coins}</span>
</div>
<>
<motion.div
className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-gold/15 border-2 border-gold/40"
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')}>
<AnimatedIcon icon={Bell} size={18} color="var(--color-text-secondary)" onClick={() => {}} />
<motion.button
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 && (
<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 }}
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.button>
</div>
</div>
</div>
......
......@@ -9,11 +9,11 @@ interface PageTransitionProps {
export function PageTransition({ children, className = '' }: PageTransitionProps) {
return (
<motion.div
className={`max-w-[360px] mx-auto w-full ${className}`}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 20 }}
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
className={`app-container py-6 flex flex-col gap-6 ${className}`}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ type: 'spring', stiffness: 300, damping: 28 }}
>
{children}
</motion.div>
......
......@@ -5,7 +5,7 @@ import { Loader2 } from 'lucide-react'
interface ButtonProps {
children: ReactNode
onClick?: () => void
variant?: 'gold' | 'ghost' | 'coral' | 'cyan'
variant?: 'gold' | 'ghost' | 'coral' | 'cyan' | 'purple'
size?: 'sm' | 'md' | 'lg'
disabled?: boolean
loading?: boolean
......@@ -14,16 +14,17 @@ interface ButtonProps {
}
const variants = {
gold: 'bg-gradient-to-l from-gold to-gold-light text-background font-bold shadow-lg shadow-gold/20',
ghost: 'bg-surface-2 border border-border text-text-primary font-semibold hover:border-gold/40 hover:bg-surface-3',
coral: 'bg-gradient-to-l from-coral to-coral/80 text-white font-bold shadow-md shadow-coral/15',
cyan: 'bg-gradient-to-l from-cyan to-cyan/80 text-background font-bold shadow-md shadow-cyan/15',
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-2 border-border text-text-primary font-bold hover:border-gold/50 hover:bg-surface-3',
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-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 = {
sm: 'px-5 py-2.5 text-sm rounded-xl gap-2 min-w-[80px]',
md: 'px-7 py-3.5 text-base rounded-xl gap-2.5 min-w-[100px]',
lg: 'px-8 py-4.5 text-lg rounded-2xl gap-3 min-w-[120px]',
sm: 'px-5 py-2 text-sm rounded-xl gap-2 min-w-[90px]',
md: 'px-7 py-3 text-base rounded-2xl gap-2.5 min-w-[120px]',
lg: 'px-10 py-4 text-lg rounded-2xl gap-3 min-w-[160px]',
}
export function Button({
......@@ -41,9 +42,10 @@ export function Button({
type={type}
onClick={onClick}
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}`}
whileTap={!disabled ? { scale: 0.96 } : undefined}
transition={{ type: 'spring', stiffness: 500, damping: 20 }}
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.92, y: 2 } : undefined}
whileHover={!disabled ? { scale: 1.03 } : undefined}
transition={{ type: 'spring', stiffness: 600, damping: 20 }}
>
{loading && <Loader2 size={18} className="animate-spin" />}
{children}
......
......@@ -6,16 +6,25 @@ interface CardProps {
className?: string
glow?: boolean
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 (
<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}`}
whileHover={onClick ? { y: -2, boxShadow: '0 8px 30px rgba(212,168,67,0.12)' } : undefined}
whileTap={onClick ? { scale: 0.98 } : undefined}
className={`${base} ${variantStyles[variant]} ${onClick ? 'cursor-pointer' : ''} ${className}`}
whileHover={onClick ? { y: -4, scale: 1.01, boxShadow: '0 12px 32px rgba(0,0,0,0.5)' } : undefined}
whileTap={onClick ? { scale: 0.97 } : undefined}
onClick={onClick}
transition={{ type: 'spring', stiffness: 400, damping: 25 }}
transition={{ type: 'spring', stiffness: 400, damping: 22 }}
>
{children}
</motion.div>
......
......@@ -23,8 +23,8 @@ export function Input({
const [focused, setFocused] = useState(false)
return (
<div className="flex flex-col gap-1.5">
<label className="text-sm font-semibold text-text-secondary">{label}</label>
<div className="flex flex-col gap-2">
<label className="text-sm font-bold text-text-secondary">{label}</label>
<div className="relative">
<input
type={type}
......@@ -34,19 +34,21 @@ export function Input({
disabled={disabled}
onFocus={() => setFocused(true)}
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"
/>
<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 }}
animate={{ scaleX: focused ? 1 : 0 }}
transition={{ type: 'spring', stiffness: 400, damping: 30 }}
transition={{ type: 'spring', stiffness: 500, damping: 30 }}
/>
</div>
{error && (
<motion.span
className="text-xs text-coral"
className="text-xs text-coral font-bold"
initial={{ opacity: 0, y: -4 }}
animate={{ opacity: 1, y: 0 }}
>
......
......@@ -66,11 +66,11 @@ function ToastItem({
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: -30, scale: 0.9 }}
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)}
>
<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>
)
}
@import "tailwindcss";
@theme {
--color-background: #06060E;
--color-surface-1: #141420;
--color-surface-2: #1C1C30;
--color-surface-3: #26263C;
--color-border: #32324E;
--color-border-gold: rgba(212, 168, 67, 0.25);
--color-gold: #D4A843;
--color-gold-light: #E8C55A;
--color-gold-muted: #B8964A;
--color-coral: #E06B4A;
--color-royal-blue: #5B7FD6;
--color-cyan: #4ECDC4;
--color-purple: #7B5EA7;
/* === BACKGROUNDS === */
--color-background: #0B0E1A;
--color-surface-1: #151929;
--color-surface-2: #1E2340;
--color-surface-3: #2A3055;
--color-border: #3D4570;
--color-border-gold: rgba(255, 200, 60, 0.35);
/* === BRAND (vibrant Brawl Stars palette) === */
--color-gold: #FFC83D;
--color-gold-light: #FFE066;
--color-gold-muted: #C9972E;
--color-coral: #FF5252;
--color-royal-blue: #4D8BFF;
--color-cyan: #00E5CC;
--color-purple: #B44DFF;
--color-electric-blue: #3B82F6;
--color-green: #4ADE80;
--color-orange: #FF8C42;
--color-text-primary: #F5F5F5;
--color-text-secondary: #A0A0B8;
--color-text-muted: #6B6B80;
/* === TEXT === */
--color-text-primary: #FFFFFF;
--color-text-secondary: #B8BDD6;
--color-text-muted: #6E748C;
--color-win: #4ECDC4;
--color-loss: #E06B4A;
--color-draw: #A0A0B8;
/* === SEMANTIC === */
--color-win: #00E5CC;
--color-loss: #FF5252;
--color-draw: #B8BDD6;
--font-family-cairo: 'Cairo', sans-serif;
}
......@@ -52,8 +58,9 @@ body {
flex-direction: column;
}
/* === SCROLLBAR === */
::-webkit-scrollbar {
width: 4px;
width: 6px;
}
::-webkit-scrollbar-track {
......@@ -62,9 +69,101 @@ body {
::-webkit-scrollbar-thumb {
background: var(--color-border);
border-radius: 2px;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
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 = [
] as const
export const RARITY_COLORS = {
common: '#6B6B80',
uncommon: '#5B7FD6',
rare: '#7B5EA7',
epic: '#E06B4A',
legendary: '#D4A843',
common: '#6E748C',
uncommon: '#4D8BFF',
rare: '#B44DFF',
epic: '#FF5252',
legendary: '#FFC83D',
} as const
......@@ -8,13 +8,13 @@ import { fetchBots, getBotPortraitUrl, type Bot } from '../lib/stockfish'
import { ChevronRight, Swords } from 'lucide-react'
const DIFFICULTY_COLORS: Record<string, string> = {
beginner: '#4ECDC4',
defensive: '#5B7FD6',
aggressive: '#E06B4A',
positional: '#7B5EA7',
creative: '#D4A843',
solid: '#6B6B80',
near_perfect: '#FFD700',
beginner: '#00E5CC',
defensive: '#4D8BFF',
aggressive: '#FF5252',
positional: '#B44DFF',
creative: '#FFC83D',
solid: '#6E748C',
near_perfect: '#FFE066',
}
const TOTAL_STRENGTH_DOTS = 7
......@@ -39,55 +39,50 @@ export function BotSelectPage() {
if (loading) {
return (
<PageTransition className="px-6 py-8 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" />
<p className="mt-4 text-sm text-text-muted">جاري تحميل الروبوتات...</p>
<PageTransition className="flex flex-col items-center justify-center min-h-[60vh]">
<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 font-bold">جاري تحميل الروبوتات...</p>
</PageTransition>
)
}
return (
<PageTransition className="px-7 py-7 flex flex-col gap-6 pb-36">
{/* Header */}
<PageTransition className="pb-36">
<div className="flex items-center gap-4">
<motion.button
onClick={() => navigate(-1)}
className="w-10 h-10 rounded-full bg-surface-2 border border-border flex items-center justify-center"
whileTap={{ scale: 0.9 }}
className="w-11 h-11 rounded-xl bg-surface-2 border-2 border-border flex items-center justify-center"
whileTap={{ scale: 0.85 }}
>
<ChevronRight size={20} className="text-text-secondary" />
</motion.button>
<div>
<h1 className="text-xl font-bold">العب ضد الروبوت</h1>
<p className="text-xs text-text-muted mt-1">اختر خصمك حسب مستواك</p>
<h1 className="text-xl font-black">العب ضد الروبوت</h1>
<p className="text-xs text-text-muted font-semibold mt-0.5">اختر خصمك حسب مستواك</p>
</div>
</div>
{/* Bot List */}
<div className="flex flex-col gap-3">
{bots.map((bot, i) => {
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)
return (
<motion.div
key={bot.id}
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: i * 0.05, type: 'spring', stiffness: 400, damping: 25 }}
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: i * 0.06, type: 'spring', stiffness: 400, damping: 22 }}
>
<Card
glow={isSelected}
onClick={() => setSelectedBot(bot.id)}
className={`flex items-center gap-4 transition-all ${
isSelected ? 'border-gold/60 scale-[1.01]' : ''
}`}
className={`flex items-center gap-4 ${isSelected ? '!border-gold' : ''}`}
>
{/* Portrait */}
<div
className="relative w-14 h-14 rounded-full overflow-hidden flex-shrink-0 border-2"
style={{ backgroundColor: `${diffColor}15`, borderColor: `${diffColor}40` }}
className="relative w-14 h-14 rounded-xl overflow-hidden flex-shrink-0 border-2"
style={{ backgroundColor: `${diffColor}15`, borderColor: `${diffColor}60` }}
>
<img
src={getBotPortraitUrl(bot.id)}
......@@ -96,45 +91,43 @@ export function BotSelectPage() {
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none' }}
/>
<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>
{/* Info */}
<div className="flex-1 min-w-0 space-y-1.5">
<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
className="px-2 py-0.5 rounded-md text-[9px] font-bold"
style={{ backgroundColor: `${diffColor}20`, color: diffColor }}
className="px-2 py-0.5 rounded-lg text-[9px] font-black"
style={{ backgroundColor: `${diffColor}25`, color: diffColor }}
>
{bot.style_ar}
</span>
</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">
<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]">
{Array.from({ length: TOTAL_STRENGTH_DOTS }).map((_, dotIndex) => (
<div
key={dotIndex}
className="w-[5px] h-[5px] rounded-full"
style={{ backgroundColor: dotIndex < strengthLevel ? diffColor : `${diffColor}25` }}
className="w-[6px] h-[6px] rounded-full"
style={{ backgroundColor: dotIndex < strengthLevel ? diffColor : `${diffColor}20` }}
/>
))}
</div>
</div>
</div>
{/* Check */}
{isSelected && (
<motion.div
className="w-7 h-7 rounded-full bg-gold flex items-center justify-center flex-shrink-0"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ type: 'spring', stiffness: 500, damping: 20 }}
className="w-8 h-8 rounded-full bg-gold flex items-center justify-center flex-shrink-0"
initial={{ scale: 0, rotate: -90 }}
animate={{ scale: 1, rotate: 0 }}
transition={{ type: 'spring', stiffness: 500, damping: 15 }}
>
<Swords size={12} className="text-background" />
<Swords size={14} className="text-background" />
</motion.div>
)}
</Card>
......@@ -143,17 +136,18 @@ export function BotSelectPage() {
})}
</div>
{/* CTA */}
{selectedBot && (
<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"
initial={{ opacity: 0, y: 20 }}
initial={{ opacity: 0, y: 30 }}
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>
</div>
</motion.div>
)}
</PageTransition>
......
import { useState } from 'react'
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 { Card } from '../components/ui/Card'
import { Button } from '../components/ui/Button'
......@@ -28,11 +28,20 @@ function relativeTime(date: string | null): string {
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() {
const { user } = useAuthStore()
const { friends, pendingReceived, loading, sendRequest, acceptRequest, rejectRequest, removeFriend } = useFriends()
const [showSearch, setShowSearch] = useState(false)
const [searchQuery, setSearchQuery] = useState('')
const [searchResults, setSearchResults] = useState<SearchResult[]>([])
const [searching, setSearching] = useState(false)
......@@ -64,79 +73,67 @@ export function FriendsPage() {
const onlineFriends = 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 (
<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">
<h1 className="text-xl font-bold">الاصدقاء</h1>
<motion.button
className="p-2 rounded-lg bg-gold/10 border border-gold/20"
whileTap={{ scale: 0.9 }}
onClick={() => setShowSearch(!showSearch)}
<h1 className="text-xl font-black">الأصدقاء</h1>
<motion.div
className="p-2.5 rounded-xl bg-[#FFC83D]/10 border-3 border-[#FFC83D]/30"
whileTap={{ scale: 0.85 }}
>
{showSearch ? <X size={18} className="text-gold" /> : <UserPlus size={18} className="text-gold" />}
</motion.button>
<UserPlus size={20} className="text-[#FFC83D]" />
</motion.div>
</div>
<AnimatePresence>
{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">
<div className="relative">
<Search size={16} className="absolute right-3 top-1/2 -translate-y-1/2 text-text-muted" />
<input
type="text"
placeholder="بحث عن لاعب..."
value={searchQuery}
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"
autoFocus
/>
</div>
{searching && (
<div className="flex justify-center py-3">
<Loader2 size={20} className="animate-spin text-text-muted" />
<div className="flex justify-center py-4">
<Loader2 size={22} className="animate-spin text-[#FFC83D]" />
</div>
)}
<AnimatePresence>
{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) => {
const alreadyFriend = friends.some((f) => f.profile.id === result.id)
const alreadySent = sentIds.has(result.id)
return (
<motion.div key={result.id} variants={item}>
<Card className="!p-3 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">
<span className="text-sm font-bold text-gold">
<Card className="!p-3.5 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-border flex items-center justify-center shrink-0">
<span className="text-sm font-black text-[#FFC83D]">
{result.display_name?.[0] || result.username[0]}
</span>
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-semibold truncate">{result.display_name || result.username}</p>
<p className="text-xs text-text-muted">{result.elo_blitz}</p>
<p className="text-sm font-black truncate">{result.display_name || result.username}</p>
<p className="text-xs text-text-muted font-bold">{result.elo_blitz}</p>
</div>
{alreadyFriend ? (
<UserCheck size={16} className="text-cyan shrink-0" />
<UserCheck size={18} className="text-[#00E5CC] shrink-0" />
) : 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>
)}
......@@ -146,113 +143,98 @@ export function FriendsPage() {
})}
</motion.div>
)}
</motion.div>
)}
</AnimatePresence>
{loading ? (
<div className="flex-1 flex items-center justify-center">
<Loader2 size={24} className="animate-spin text-text-muted" />
<div className="flex-1 flex items-center justify-center py-12">
<Loader2 size={28} className="animate-spin text-[#FFC83D]" />
</div>
) : (
<>
<motion.div className="flex flex-col gap-5" variants={stagger} initial="hidden" animate="show">
{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">
<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) => (
<motion.div key={req.id} variants={item}>
<Card className="!p-3 flex items-center gap-3">
<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-bold text-gold">
<Card key={req.id} className="!p-3.5 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">
<span className="text-sm font-black text-[#FFC83D]">
{req.profile.display_name?.[0] || req.profile.username[0]}
</span>
</div>
<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}
</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 className="flex gap-1.5">
<motion.button
className="p-1.5 rounded-lg bg-cyan/10 border border-cyan/20"
whileTap={{ scale: 0.85 }}
onClick={() => acceptRequest(req.id)}
>
<UserCheck size={14} className="text-cyan" />
</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 className="flex gap-2">
<Button size="sm" variant="cyan" onClick={() => acceptRequest(req.id)}>
قبول
</Button>
<Button size="sm" variant="coral" onClick={() => rejectRequest(req.id)}>
رفض
</Button>
</div>
</Card>
</motion.div>
))}
</motion.div>
</div>
</motion.div>
)}
{onlineFriends.length > 0 && (
<div className="flex flex-col gap-2">
<h2 className="text-sm font-semibold text-cyan">
<motion.div className="flex flex-col gap-2" variants={item}>
<h2 className="text-sm font-black text-[#00E5CC]">
متصل ({onlineFriends.length})
</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) => (
<motion.div key={friend.id} variants={item}>
<Card className="!p-3 flex items-center gap-3">
<Card key={friend.id} className="!p-3.5 flex items-center gap-3">
<div className="relative">
<div className="w-9 h-9 rounded-full bg-surface-3 border border-border flex items-center justify-center">
<span className="text-sm font-bold text-gold">
<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-black text-[#FFC83D]">
{friend.profile.display_name?.[0] || friend.profile.username[0]}
</span>
</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 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}
</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>
<motion.button
className="p-1.5 rounded-lg bg-coral/10 border border-coral/20 opacity-0 group-hover:opacity-100"
whileTap={{ scale: 0.85 }}
className="p-2 rounded-xl bg-[#FF5252]/10 border-2 border-[#FF5252]/20"
whileTap={{ scale: 0.8 }}
onClick={() => removeFriend(friend.id)}
>
<UserX size={14} className="text-coral" />
<UserX size={14} className="text-[#FF5252]" />
</motion.button>
</Card>
</motion.div>
))}
</motion.div>
</div>
</motion.div>
)}
{offlineFriends.length > 0 && (
<div className="flex flex-col gap-2">
<h2 className="text-sm font-semibold text-text-muted">
<motion.div className="flex flex-col gap-2" variants={item}>
<h2 className="text-sm font-black text-text-muted">
غير متصل ({offlineFriends.length})
</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) => (
<motion.div key={friend.id} variants={item}>
<Card className="!p-3 flex items-center gap-3">
<Card key={friend.id} className="!p-3.5 flex items-center gap-3 opacity-70">
<div className="relative">
<div className="w-9 h-9 rounded-full bg-surface-3 border border-border flex items-center justify-center opacity-60">
<span className="text-sm font-bold text-text-muted">
<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-black text-text-muted">
{friend.profile.display_name?.[0] || friend.profile.username[0]}
</span>
</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 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}
</p>
<p className="text-xs text-text-muted">
......@@ -260,35 +242,40 @@ export function FriendsPage() {
</p>
</div>
<motion.button
className="p-1.5 rounded-lg bg-coral/10 border border-coral/20"
whileTap={{ scale: 0.85 }}
className="p-2 rounded-xl bg-[#FF5252]/10 border-2 border-[#FF5252]/20"
whileTap={{ scale: 0.8 }}
onClick={() => removeFriend(friend.id)}
>
<UserX size={14} className="text-coral" />
<UserX size={14} className="text-[#FF5252]" />
</motion.button>
</Card>
</motion.div>
))}
</motion.div>
</div>
</motion.div>
)}
{friends.length === 0 && pendingReceived.length === 0 && (
<div className="flex-1 flex flex-col items-center justify-center py-12">
<motion.div
className="w-16 h-16 rounded-full bg-surface-2 border border-border flex items-center justify-center mb-4"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ type: 'spring', stiffness: 300, damping: 20 }}
className="flex flex-col items-center justify-center py-16 gap-4"
variants={item}
>
<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>
<p className="text-text-muted text-sm">لا يوجد اصدقاء بعد</p>
<p className="text-text-muted text-xs mt-1">ابحث عن لاعبين لاضافتهم</p>
<div className="text-center">
<p className="text-text-muted font-black text-base">لا يوجد اصدقاء بعد</p>
<p className="text-text-muted text-xs font-bold mt-1">ابحث عن لاعبين لاضافتهم</p>
</div>
</motion.div>
)}
</>
</motion.div>
)}
</div>
</PageTransition>
)
}
import { useState } from 'react'
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 { useAuthStore } from '../stores/authStore'
import { PageTransition } from '../components/layout/PageTransition'
import { Card } from '../components/ui/Card'
import { DailyRewardModal } from '../components/DailyRewardModal'
import { useDailyReward } from '../hooks/useDailyReward'
import { useRecentGames } from '../hooks/useRecentGames'
import { playSound } from '../lib/sounds'
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() {
const navigate = useNavigate()
const { user, profile } = useAuthStore()
const { profile } = useAuthStore()
const todayTip = dailyTips[new Date().getDay()]
const { canClaim, streak, reward, claim, loading: dailyLoading } = useDailyReward()
const [showDailyModal, setShowDailyModal] = useState(true)
const { games, loading: gamesLoading } = useRecentGames(user?.id, 3)
async function handleClaimReward() {
const result = await claim()
......@@ -34,7 +46,7 @@ export function HomePage() {
}
return (
<PageTransition className="px-7 py-7 flex flex-col gap-8">
<PageTransition>
<DailyRewardModal
open={canClaim && showDailyModal}
streak={streak}
......@@ -43,212 +55,160 @@ export function HomePage() {
onClaim={handleClaimReward}
onClose={() => setShowDailyModal(false)}
/>
<motion.div
variants={stagger}
initial="hidden"
animate="show"
className="flex flex-col gap-6"
>
{profile && (
<motion.div
variants={fadeUp}
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">
<span className="text-xl font-bold text-gold">
<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-2xl font-black text-gold">
{profile.display_name?.charAt(0) || 'L'}
</span>
</div>
<div>
<h2 className="text-xl font-bold">اهلا، {profile.display_name}</h2>
<p className="text-xs text-text-muted mt-0.5">المستوى {profile.level}</p>
<h2 className="text-2xl font-black">اهلا، {profile.display_name}</h2>
<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>
</motion.div>
)}
<motion.div variants={fadeUp} className="flex justify-center">
<motion.button
onClick={() => navigate('/play')}
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"
whileTap={{ scale: 0.96 }}
onClick={() => {
playSound('click')
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
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%'] }}
transition={{ duration: 2.5, repeat: Infinity, ease: 'linear' }}
/>
<div className="relative flex flex-col items-center gap-3">
<motion.div
animate={{ scale: [1, 1.08, 1] }}
transition={{ duration: 2, repeat: Infinity, ease: 'easeInOut' }}
animate={{ scale: [1, 1.12, 1] }}
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>
<span className="text-2xl font-black text-background">العب الان</span>
<span className="text-3xl font-black text-background uppercase tracking-wide">العب الان</span>
</div>
</motion.button>
</motion.div>
{profile && (
<div 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 icon={<TrendingUp size={20} className="text-gold" />} value={profile.elo_blitz} label="تقييم" delay={0.3} accent="gold" />
<StatCard icon={<Flame size={20} className="text-coral" />} value={profile.win_streak} label="سلسلة فوز" delay={0.4} accent="coral" />
</div>
<motion.div variants={fadeUp} className="grid grid-cols-3 gap-3">
<StatCard
icon={<TrendingUp size={22} className="text-gold" />}
value={profile.elo_blitz}
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.button
onClick={() => navigate('/bot-select')}
className="flex flex-col items-center gap-3 p-6 rounded-2xl bg-surface-1 border border-border"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
whileTap={{ scale: 0.96 }}
<motion.div variants={fadeUp} className="grid grid-cols-2 gap-3">
<Card
variant="default"
className="flex flex-col items-center gap-3 !p-5"
onClick={() => {
playSound('click')
navigate('/bot-select')
}}
>
<div className="w-12 h-12 rounded-2xl bg-purple/10 border border-purple/20 flex items-center justify-center">
<Bot size={24} className="text-purple" />
<div className="w-14 h-14 rounded-2xl bg-purple/15 border-3 border-purple/30 flex items-center justify-center">
<Bot size={28} className="text-purple" />
</div>
<div className="text-center">
<p className="text-sm font-bold">العب ضد روبوت</p>
<p className="text-[10px] text-text-muted mt-0.5">تدريب وتحسين</p>
<p className="text-sm font-black">العب ضد روبوت</p>
<p className="text-[10px] text-text-muted mt-1">تدريب وتحسين</p>
</div>
</motion.button>
</Card>
<motion.button
onClick={() => navigate('/friends')}
className="flex flex-col items-center gap-3 p-6 rounded-2xl bg-surface-1 border border-border"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.4 }}
whileTap={{ scale: 0.96 }}
<Card
variant="default"
className="flex flex-col items-center gap-3 !p-5"
onClick={() => {
playSound('click')
navigate('/friends')
}}
>
<div className="w-12 h-12 rounded-2xl bg-gold/10 border border-gold/20 flex items-center justify-center">
<Users size={24} className="text-gold" />
<div className="w-14 h-14 rounded-2xl bg-gold/15 border-3 border-gold/30 flex items-center justify-center">
<Users size={28} className="text-gold" />
</div>
<div className="text-center">
<p className="text-sm font-bold">تحدى صديق</p>
<p className="text-[10px] text-text-muted mt-0.5">ارسل دعوة</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>
<p className="text-sm font-black">تحدى صديق</p>
<p className="text-[10px] text-text-muted mt-1">ارسل دعوة</p>
</div>
</div>
<HomeRatingBadge change={game.ratingChange} />
</Card>
</motion.div>
))}
</div>
)}
</div>
<div className="p-5 rounded-2xl bg-surface-1 border border-border relative overflow-hidden">
<div className="absolute top-0 right-0 w-24 h-24 bg-gradient-to-bl from-gold/6 to-transparent rounded-bl-full" />
<motion.div variants={fadeUp}>
<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="w-10 h-10 rounded-xl bg-gold/10 flex items-center justify-center flex-shrink-0">
<Lightbulb size={20} className="text-gold" />
<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={22} className="text-gold" />
</div>
<div className="flex-1 pt-0.5">
<div className="flex items-center gap-2 mb-2">
<h4 className="text-xs font-bold text-gold">نصيحة اليوم</h4>
<Crown size={11} className="text-gold/40" />
</div>
<p className="text-[12px] text-text-secondary leading-[1.7]">{todayTip}</p>
<h4 className="text-xs font-black text-gold uppercase">نصيحة اليوم</h4>
<Crown size={12} className="text-gold/50" />
</div>
<p className="text-[12px] text-text-secondary leading-[1.8]">{todayTip}</p>
</div>
</div>
</Card>
</motion.div>
</motion.div>
</PageTransition>
)
}
function StatCard({ icon, value, label, delay, accent }: { icon: React.ReactNode; value: number; label: string; delay: number; accent: string }) {
const borderMap: Record<string, string> = { cyan: 'border-cyan/20', gold: 'border-gold/20', coral: 'border-coral/20' }
function StatCard({ icon, value, label, accent }: { icon: React.ReactNode; value: number; label: string; accent: string }) {
const borderMap: Record<string, string> = {
cyan: 'border-t-cyan',
gold: 'border-t-gold',
coral: 'border-t-coral',
}
return (
<motion.div
className={`flex flex-col items-center gap-2 p-4 rounded-2xl bg-surface-1 border ${borderMap[accent] || 'border-border'}`}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay }}
<div
className={`flex flex-col items-center gap-2 p-4 rounded-2xl bg-surface-1 border-3 border-border ${borderMap[accent] || ''}`}
style={{ borderTopWidth: '4px' }}
>
{icon}
<span className="text-xl font-black">{value}</span>
<span className="text-[10px] text-text-muted font-medium">{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}
<span className="text-2xl font-black">{value}</span>
<span className="text-[10px] text-text-muted font-bold">{label}</span>
</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 }[] = [
{ 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 (
<div
className="rounded-full bg-gradient-to-br from-surface-3 to-surface-2 flex items-center justify-center font-bold text-text-secondary"
style={{ width: size, height: size, fontSize: size * 0.4 }}
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.38 }}
>
{name?.charAt(0) || '?'}
</div>
......@@ -42,8 +52,11 @@ export function LeaderboardPage() {
const rest = entries.slice(3)
return (
<PageTransition className="px-7 py-7 flex flex-col gap-5">
<h1 className="text-xl font-bold">لوحة المتصدرين</h1>
<PageTransition className="flex flex-col gap-5">
<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">
{TIME_CONTROLS.map((tc) => (
......@@ -51,11 +64,12 @@ export function LeaderboardPage() {
key={tc.value}
whileTap={{ scale: 0.93 }}
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
? 'bg-gold text-bg-primary'
: 'bg-surface-2 text-text-muted border border-border'
? 'bg-[#FFC83D] border-[#FFC83D] text-background font-black'
: 'bg-surface-2 text-text-muted border-border hover:border-[#FFC83D]/40'
}`}
transition={{ type: 'spring', stiffness: 500, damping: 20 }}
>
{tc.label}
</motion.button>
......@@ -68,11 +82,12 @@ export function LeaderboardPage() {
key={p.value}
whileTap={{ scale: 0.93 }}
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
? 'bg-surface-3 text-text-primary border border-gold/40'
: 'bg-surface-1 text-text-muted border border-border'
? 'bg-[#B44DFF]/15 text-[#B44DFF] border-[#B44DFF]/50 font-black'
: 'bg-surface-1 text-text-muted border-border hover:border-[#B44DFF]/30'
}`}
transition={{ type: 'spring', stiffness: 500, damping: 20 }}
>
{p.label}
</motion.button>
......@@ -81,48 +96,53 @@ export function LeaderboardPage() {
{loading ? (
<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>
) : entries.length === 0 ? (
<div className="flex flex-col items-center justify-center py-16 gap-4">
<motion.div
className="w-16 h-16 rounded-full bg-surface-2 border border-border flex items-center justify-center"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ type: 'spring', stiffness: 300, damping: 20 }}
className="w-20 h-20 rounded-2xl bg-surface-2 border-3 border-[#FFC83D]/30 flex items-center justify-center"
initial={{ scale: 0, rotate: -10 }}
animate={{ scale: 1, rotate: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 18 }}
>
<Trophy size={24} className="text-gold" />
<Trophy size={32} className="text-[#FFC83D]" />
</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>
</div>
) : (
<>
{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) => (
<motion.div
key={entry.player_id}
initial={{ opacity: 0, x: -10 }}
initial={{ opacity: 0, x: -12 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.3 + i * 0.03 }}
className={`flex items-center gap-3 p-3 rounded-xl bg-surface-1 border ${
entry.player_id === user?.id ? 'border-gold/50 bg-gold/5' : 'border-border'
transition={{ type: 'spring', stiffness: 400, damping: 24, delay: 0.3 + i * 0.04 }}
className={`flex items-center gap-3 p-3.5 rounded-2xl bg-surface-1 border-3 ${
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}
</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">
<p className="text-sm font-bold truncate">{entry.display_name}</p>
<p className="text-[10px] text-text-muted">
{entry.games_played} مباراة &middot; {entry.win_rate}% فوز
{entry.country_code && ` &middot; ${entry.country_code}`}
<p className="text-sm font-black truncate">{entry.display_name}</p>
<p className="text-[10px] text-text-muted font-bold">
{entry.games_played} مباراة
</p>
</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>
))}
</div>
......@@ -136,19 +156,34 @@ function Podium({
entries,
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
}) {
const ordered = [entries[1], entries[0], entries[2]].filter(Boolean)
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: 'text-[#CD7F32]', bg: 'bg-[#CD7F32]/10', border: 'border-[#CD7F32]/30', size: 48, label: '3' },
{
color: '#C0C0C0',
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 (
<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) => {
if (!entry) return null
const config = podiumConfig[i]
......@@ -158,34 +193,47 @@ function Podium({
<motion.div
key={entry.player_id}
className="flex flex-col items-center gap-2"
initial={{ scale: 0, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ type: 'spring', stiffness: 300, damping: 20, delay: 0.1 + i * 0.12 }}
initial={{ scale: 0, opacity: 0, y: 30 }}
animate={{ scale: 1, opacity: 1, y: 0 }}
transition={{ type: 'spring', stiffness: 350, damping: 20, delay: 0.1 + i * 0.12 }}
>
{i === 1 && (
<motion.div
initial={{ scale: 0, rotate: -20 }}
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>
)}
{i === 0 && <Medal size={16} className="text-[#C0C0C0]" />}
{i === 2 && <Medal size={16} className="text-[#CD7F32]" />}
<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' : ''}`}
style={{ width: config.size, height: config.size, fontSize: config.size * 0.35 }}
className={`rounded-full border-3 flex items-center justify-center font-black ${isMe ? 'ring-3 ring-[#FFC83D]/40' : ''}`}
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) || '?'}
</div>
<div className="text-center">
<p className="text-xs font-bold truncate max-w-[70px]">{entry.display_name}</p>
<p className={`text-sm font-bold ${config.color}`}>{entry.rating}</p>
<p className="text-[9px] text-text-muted">{entry.games_played} مباراة</p>
<p className="text-xs font-black truncate max-w-[75px]">{entry.display_name}</p>
<p className="text-base font-black" style={{ color: config.color }}>
{entry.rating}
</p>
<p className="text-[9px] text-text-muted font-bold">{entry.games_played} مباراة</p>
</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>
)
})}
......
......@@ -32,35 +32,41 @@ export function LoginPage() {
}
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
className="absolute inset-0 opacity-30"
className="absolute inset-0"
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(212,168,67,0.15) 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(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(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
className="relative w-full max-w-[310px]"
initial={{ opacity: 0, y: 30 }}
className="relative w-full max-w-[380px] px-5"
initial={{ opacity: 0, y: 40 }}
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">
<GoldCrown size={64} />
<h1 className="mt-4 text-3xl font-black text-gold">EL3AB</h1>
<p className="mt-1 text-text-secondary">تسجيل الدخول</p>
<GoldCrown size={72} />
<h1 className="mt-4 text-4xl font-black text-gold">EL3AB</h1>
<p className="mt-2 text-lg font-bold text-text-secondary">تسجيل الدخول</p>
</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
label="البريد الالكتروني"
type="email"
......@@ -78,25 +84,26 @@ export function LoginPage() {
{error && (
<motion.p
className="text-sm text-coral text-center"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="text-sm font-bold text-coral text-center"
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ type: 'spring', stiffness: 500, damping: 20 }}
>
{error}
</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>
</form>
</motion.form>
<motion.button
onClick={() => navigate('/register')}
className="mt-4 w-full text-center text-sm text-text-secondary hover:text-gold transition-colors"
whileTap={{ scale: 0.98 }}
className="mt-5 w-full text-center text-sm font-bold text-text-secondary hover:text-gold transition-colors"
whileTap={{ scale: 0.96 }}
>
ليس لديك حساب؟ <span className="text-gold font-semibold">سجل الان</span>
ليس لديك حساب؟ <span className="text-gold font-black">سجل الان</span>
</motion.button>
</motion.div>
</div>
......
......@@ -36,25 +36,25 @@ export function MatchmakingPage() {
}
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">
{matchFound ? (
<motion.div
key="found"
className="flex flex-col items-center"
initial={{ scale: 0.5, opacity: 0 }}
initial={{ scale: 0.3, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ type: 'spring', stiffness: 300, damping: 20 }}
transition={{ type: 'spring', stiffness: 300, damping: 15 }}
>
<motion.div
className="w-24 h-24 rounded-full bg-gold/20 border-2 border-gold flex items-center justify-center"
animate={{ scale: [1, 1.1, 1] }}
transition={{ duration: 0.5, repeat: 2 }}
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.15, 1] }}
transition={{ duration: 0.6, repeat: 2 }}
>
<span className="text-4xl">⚔️</span>
<span className="text-5xl font-black text-gold">VS</span>
</motion.div>
<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 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
......@@ -68,11 +68,11 @@ export function MatchmakingPage() {
className="flex flex-col items-center"
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) => (
<motion.div
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 }}
animate={{ scale: 2.5, opacity: 0 }}
transition={{
......@@ -85,16 +85,16 @@ export function MatchmakingPage() {
))}
<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"
animate={{ scale: [1, 1.05, 1] }}
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.08, 1], rotate: [0, 3, -3, 0] }}
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>
</div>
<motion.h2
className="mt-8 text-xl font-bold"
className="mt-8 text-xl font-black"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.3 }}
......@@ -103,7 +103,7 @@ export function MatchmakingPage() {
</motion.h2>
<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 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.4 }}
......@@ -112,7 +112,7 @@ export function MatchmakingPage() {
</motion.div>
<motion.div
className="mt-2 flex gap-1"
className="mt-3 flex gap-1.5"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.5 }}
......@@ -120,9 +120,9 @@ export function MatchmakingPage() {
{[0, 1, 2].map((i) => (
<motion.span
key={i}
className="w-2 h-2 rounded-full bg-gold"
animate={{ opacity: [0.3, 1, 0.3] }}
transition={{ duration: 1, repeat: Infinity, delay: i * 0.3 }}
className="w-3 h-3 rounded-full bg-gold"
animate={{ opacity: [0.2, 1, 0.2], scale: [0.8, 1.2, 0.8] }}
transition={{ duration: 1.2, repeat: Infinity, delay: i * 0.3 }}
/>
))}
</motion.div>
......@@ -133,7 +133,7 @@ export function MatchmakingPage() {
animate={{ opacity: 1 }}
transition={{ delay: 1 }}
>
<Button variant="ghost" onClick={handleCancel}>
<Button variant="ghost" onClick={handleCancel} size="md">
الغاء
</Button>
</motion.div>
......
......@@ -7,7 +7,7 @@ export function NotFoundPage() {
const navigate = useNavigate()
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
className="text-6xl"
animate={{
......
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 { Button } from '../components/ui/Button'
import { useNotifications } from '../hooks/useNotifications'
function relativeTime(date: string): string {
......@@ -18,104 +19,114 @@ function getIcon(type: string) {
switch (type) {
case 'friend_request':
case 'friend_accepted':
return <UserPlus size={16} className="text-cyan" />
case 'match_result':
case 'win':
return <Trophy size={16} className="text-gold" />
return <UserPlus size={18} className="text-[#00E5CC]" />
case 'achievement':
return <Trophy size={18} className="text-[#FFC83D]" />
case 'match':
case 'match_invite':
case 'challenge':
return <Swords size={16} className="text-coral" />
return <Swords size={18} className="text-[#FF5252]" />
default:
return <Bell size={16} className="text-text-muted" />
return <Bell size={18} className="text-[#B44DFF]" />
}
}
export function NotificationsPage() {
const { notifications, markAsRead, markAllRead, loading } = useNotifications()
const stagger = {
const stagger = {
hidden: { opacity: 0 },
show: { opacity: 1, transition: { staggerChildren: 0.04 } },
}
show: { opacity: 1, transition: { staggerChildren: 0.06 } },
}
const item = {
hidden: { opacity: 0, y: 10 },
show: { opacity: 1, y: 0 },
}
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 NotificationsPage() {
const { notifications, markAsRead, markAllRead, loading } = useNotifications()
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">
<h1 className="text-xl font-bold">الاشعارات</h1>
<h1 className="text-xl font-black">الاشعارات</h1>
{notifications.some((n) => !n.is_read) && (
<motion.button
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} />
<Button size="sm" variant="ghost" onClick={markAllRead}>
قراءة الكل
</motion.button>
</Button>
)}
</div>
{loading ? (
<div className="flex-1 flex items-center justify-center">
<Loader2 size={24} className="animate-spin text-text-muted" />
<div className="flex-1 flex items-center justify-center py-16">
<Loader2 size={28} className="animate-spin text-[#FFC83D]" />
</div>
) : notifications.length === 0 ? (
<div className="flex-1 flex flex-col items-center justify-center py-12">
<motion.div
className="w-16 h-16 rounded-full bg-surface-2 border border-border flex items-center justify-center mb-4"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
className="flex flex-col items-center justify-center py-20 gap-4"
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
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>
<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) => (
<motion.div
key={notification.id}
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
? '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={() => {
if (!notification.is_read) markAsRead(notification.id)
}}
>
<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)}
</div>
<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}
</p>
{(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}
</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)}
</p>
</div>
{!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>
</motion.div>
))}
</motion.div>
)}
</div>
</PageTransition>
)
}
import { useState } from 'react'
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 { PageTransition } from '../components/layout/PageTransition'
import { GAMES, TIME_CONTROLS } from '../lib/constants'
import { useState } from 'react'
import { Card } from '../components/ui/Card'
import { Button } from '../components/ui/Button'
import { GAMES, TIME_CONTROLS } from '../lib/constants'
import { playSound } from '../lib/sounds'
const CATEGORIES = [
{ key: 'bullet', label: 'رصاصة', icon: Zap },
......@@ -13,11 +15,23 @@ const CATEGORIES = [
{ key: 'classical', label: 'كلاسيكي', icon: Hourglass },
] as const
const GAME_ICONS: Record<string, React.ReactNode> = {
backgammon: <span className="text-2xl font-bold text-text-muted">&#x2680;</span>,
dominoes: <span className="text-2xl font-bold text-text-muted">&#x1F0A1;</span>,
ludo: <span className="text-2xl font-bold text-text-muted">&#x2684;</span>,
trivia: <span className="text-lg font-bold text-text-muted">?</span>,
const GAME_ICONS: Record<string, string> = {
backgammon: '⚀',
dominoes: '🂡',
ludo: '⚄',
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() {
......@@ -27,77 +41,84 @@ export function PlayPage() {
const chessGame = GAMES.find((g) => g.key === 'chess')!
const otherGames = GAMES.filter((g) => g.key !== 'chess')
return (
<PageTransition className="px-7 py-7 flex flex-col gap-7">
<h1 className="text-xl font-bold">اختر اللعبة</h1>
const activeCategory = Object.entries(TIME_CONTROLS).find(
([k]) => k === selectedTC
)?.[1].category
{/* Chess Hero Card */}
return (
<PageTransition>
<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"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ type: 'spring', stiffness: 400, damping: 25 }}
whileTap={{ scale: 0.97 }}
variants={stagger}
initial="hidden"
animate="show"
className="flex flex-col gap-6"
>
<div className="w-16 h-16 rounded-2xl bg-gold/10 border border-gold/20 flex items-center justify-center shadow-inner">
<Crown size={32} className="text-gold" />
<motion.h1 variants={fadeUp} className="text-2xl font-black">
اختر اللعبة
</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 className="flex-1">
<span className="text-lg font-bold">{chessGame.nameAr}</span>
<p className="text-xs text-text-secondary mt-1.5 leading-relaxed">العب شطرنج اونلاين ضد لاعبين حقيقيين</p>
<span className="text-xl font-black">{chessGame.nameAr}</span>
<p className="text-xs text-text-secondary mt-2 leading-relaxed">
العب شطرنج اونلاين ضد لاعبين حقيقيين
</p>
</div>
</div>
<motion.div
className="absolute top-3 left-3 w-2 h-2 rounded-full bg-green-500"
animate={{ opacity: [1, 0.4, 1] }}
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], scale: [1, 1.2, 1] }}
transition={{ duration: 2, repeat: Infinity }}
/>
</Card>
</motion.div>
{/* Other Games Grid */}
<div className="grid grid-cols-2 gap-3">
{otherGames.map((game, i) => (
<motion.div
<motion.div variants={fadeUp} className="grid grid-cols-2 gap-3">
{otherGames.map((game) => (
<div
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"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 0.5, y: 0 }}
transition={{ delay: 0.08 + i * 0.08, type: 'spring', stiffness: 400, damping: 25 }}
className="relative rounded-2xl overflow-hidden border-3 border-border bg-surface-1 opacity-50 p-5 flex flex-col items-center gap-3"
>
<div className="w-12 h-12 rounded-xl bg-surface-3/80 flex items-center justify-center">
{GAME_ICONS[game.key] || null}
<div className="w-12 h-12 rounded-xl bg-surface-3/80 border-2 border-border flex items-center justify-center">
<span className="text-2xl font-bold text-text-muted">
{GAME_ICONS[game.key] || ''}
</span>
</div>
<span className="text-sm font-bold text-text-secondary">{game.nameAr}</span>
<div className="absolute inset-0 flex items-center justify-center bg-background/50 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">
<Lock size={12} className="text-text-muted" />
<span className="text-[11px] text-text-muted font-medium">قريبا</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/60 backdrop-blur-[2px]">
<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={13} className="text-text-muted" />
<span className="text-[11px] text-text-muted font-bold">قريبا</span>
</div>
</div>
</motion.div>
))}
</div>
))}
</motion.div>
{/* Time Control */}
<div className="space-y-4">
<h2 className="text-base font-bold">نظام الوقت</h2>
<motion.div variants={fadeUp} className="space-y-4">
<h2 className="text-lg font-black">نظام الوقت</h2>
{/* Category Pills */}
<div className="flex gap-2 overflow-x-auto scrollbar-hide pb-1">
{CATEGORIES.map((cat) => {
const isActive = Object.entries(TIME_CONTROLS).find(
([k]) => k === selectedTC
)?.[1].category === cat.key
const isActive = activeCategory === cat.key
const Icon = cat.icon
return (
<motion.button
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
? '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'
}`}
whileTap={{ scale: 0.93 }}
whileTap={{ scale: 0.9 }}
transition={{ type: 'spring', stiffness: 500, damping: 20 }}
onClick={() => {
playSound('click')
const first = Object.entries(TIME_CONTROLS).find(([, v]) => v.category === cat.key)
if (first) setSelectedTC(first[0])
}}
......@@ -109,42 +130,55 @@ export function PlayPage() {
})}
</div>
{/* Time Buttons */}
<div className="grid grid-cols-3 gap-2.5">
{Object.entries(TIME_CONTROLS)
.filter(([, v]) => {
const activeCategory = Object.entries(TIME_CONTROLS).find(
([k]) => k === selectedTC
)?.[1].category
return v.category === activeCategory
})
.filter(([, v]) => v.category === activeCategory)
.map(([key, tc]) => (
<motion.button
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
? 'bg-gold/15 border-gold text-gold shadow-sm shadow-gold/10'
: 'bg-surface-2 border-border text-text-secondary'
? 'bg-gold/20 border-gold text-gold shadow-md shadow-gold/15 scale-105'
: 'bg-surface-2 border-border text-text-secondary hover:border-border/80'
}`}
whileTap={{ scale: 0.93 }}
onClick={() => setSelectedTC(key)}
whileTap={{ scale: 0.9 }}
transition={{ type: 'spring', stiffness: 500, damping: 20 }}
onClick={() => {
playSound('click')
setSelectedTC(key)
}}
>
{tc.labelAr}
</motion.button>
))}
</div>
</div>
</motion.div>
{/* Action Buttons */}
<div className="flex flex-col items-center gap-3 mt-2 pb-4">
<Button onClick={() => navigate(`/matchmaking?tc=${selectedTC}&game=chess`)} className="w-[85%]" size="lg">
<motion.div variants={fadeUp} className="flex flex-col items-center gap-3 mt-2 pb-4">
<Button
onClick={() => {
playSound('click')
navigate(`/matchmaking?tc=${selectedTC}&game=chess`)
}}
className="w-[80%]"
size="lg"
>
البحث عن خصم
</Button>
<Button onClick={() => navigate('/bot-select')} variant="ghost" className="w-[85%]" size="md">
<Cpu size={18} className="text-gold" />
<Button
onClick={() => {
playSound('click')
navigate('/bot-select')
}}
variant="ghost"
className="w-[80%]"
size="md"
>
<Cpu size={18} className="text-purple" />
العب ضد الروبوت
</Button>
</div>
</motion.div>
</motion.div>
</PageTransition>
)
}
import { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { Settings, TrendingUp, Target, Flame, Trophy, Lock, Pencil, X, Clock, ArrowUpRight, ArrowDownRight, Minus } from 'lucide-react'
import { useNavigate } from 'react-router-dom'
import { Trophy, Target, Flame, TrendingUp, Pencil, X, LogOut } from 'lucide-react'
import { useAuthStore } from '../stores/authStore'
import { useNotificationStore } from '../stores/notificationStore'
import { PageTransition } from '../components/layout/PageTransition'
import { Card } from '../components/ui/Card'
import { Button } from '../components/ui/Button'
import { supabase } from '../lib/supabase'
import { useRecentGames } from '../hooks/useRecentGames'
function SectionDivider() {
return <div className="w-full h-px bg-gradient-to-l from-transparent via-border to-transparent my-1" />
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 ProfilePage() {
const { user, profile, setProfile } = useAuthStore()
const { showToast } = useNotificationStore()
const navigate = useNavigate()
const [editOpen, setEditOpen] = useState(false)
const [editForm, setEditForm] = useState({
display_name: '',
bio: '',
country_code: '',
})
const [editForm, setEditForm] = useState({ display_name: '', bio: '' })
const [saving, setSaving] = useState(false)
const { games, loading: gamesLoading } = useRecentGames(user?.id, 5)
function openEdit() {
if (!profile) return
setEditForm({
display_name: profile.display_name || '',
bio: profile.bio || '',
country_code: profile.country_code || '',
})
setEditOpen(true)
}
......@@ -46,7 +43,6 @@ export function ProfilePage() {
.update({
display_name: editForm.display_name.trim(),
bio: editForm.bio.trim() || null,
country_code: editForm.country_code.trim() || null,
})
.eq('id', user.id)
......@@ -61,180 +57,132 @@ export function ProfilePage() {
...profile,
display_name: editForm.display_name.trim(),
bio: editForm.bio.trim() || null,
country_code: editForm.country_code.trim() || null,
})
showToast({ type: 'success', title: 'تم حفظ التعديلات' })
setEditOpen(false)
}
async function handleLogout() {
await supabase.auth.signOut()
}
if (!profile) return null
const winRate = profile.total_games_played > 0
? Math.round((profile.total_wins / profile.total_games_played) * 100)
: 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 (
<PageTransition className="px-7 py-7 flex flex-col gap-6">
<div className="flex items-start justify-between">
<div className="flex items-center gap-4">
<PageTransition>
<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"
style={{ width: 72, height: 72 }}
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ type: 'spring', stiffness: 300, damping: 20 }}
className="flex flex-col gap-5 py-6"
variants={stagger}
initial="hidden"
animate="show"
>
<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) || '?'}
</span>
</motion.div>
<div className="space-y-1">
<h1 className="text-xl font-bold">{profile.display_name}</h1>
<p className="text-sm text-text-muted">@{profile.username}</p>
{profile.bio && (
<p className="text-xs text-text-secondary mt-1 max-w-[180px] line-clamp-2">{profile.bio}</p>
)}
</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"
<motion.div
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"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ type: 'spring', stiffness: 500, damping: 20, delay: 0.2 }}
>
<Settings size={18} className="text-text-muted" />
</motion.button>
<span className="text-[10px] font-black text-background">{profile.level}</span>
</motion.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>
</motion.div>
<Card className="p-5">
<div className="flex items-center justify-between mb-3">
<h2 className="text-lg font-bold">المستوى {profile.level}</h2>
<span className="text-sm text-gold font-bold px-3 py-1 rounded-full bg-gold/10">{profile.xp} XP</span>
<motion.div variants={item}>
<Card variant="gold" className="!p-4">
<div className="flex items-center justify-between mb-2">
<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 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
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 }}
animate={{ width: `${Math.min((profile.xp % 500) / 5, 100)}%` }}
transition={{ duration: 1, ease: 'easeOut' }}
animate={{ width: `${xpPercent}%` }}
transition={{ duration: 1.2, ease: 'easeOut', delay: 0.3 }}
/>
</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>
</motion.div>
<SectionDivider />
<div>
<h2 className="text-sm font-bold text-text-secondary mb-3">التقييمات</h2>
<motion.div variants={item}>
<div className="grid grid-cols-2 gap-3">
{[
{ 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) => (
{ratings.map((rating, i) => (
<motion.div
key={rating.label}
className="p-4 rounded-xl bg-surface-1 border border-border/80 flex items-center gap-3"
initial={{ opacity: 0, y: 10 }}
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: 14 }}
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-[11px] font-bold text-text-muted">{rating.icon}</span>
</div>
<div>
<p className="text-lg font-bold">{rating.value}</p>
<p className="text-[11px] text-text-muted">{rating.label}</p>
</div>
<span className="text-2xl font-black">{rating.value}</span>
<span className="text-xs font-bold text-text-muted">{rating.label}</span>
</motion.div>
))}
</div>
</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 />
</motion.div>
<div>
<h2 className="text-sm font-bold text-text-secondary mb-3">الانجازات</h2>
<div className="grid grid-cols-3 gap-3">
{[
{ label: 'المحارب' },
{ 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" />
<motion.div variants={item}>
<div className="grid grid-cols-4 gap-2">
<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="انتصارات" />
<StatBox icon={<Target size={18} className="text-[#00E5CC]" />} value={`${winRate}%`} label="نسبة الفوز" />
<StatBox icon={<Flame size={18} className="text-[#FF5252]" />} value={profile.best_win_streak} label="افضل سلسلة" />
</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>
))}
</div>
</div>
<SectionDivider />
<div>
<h2 className="text-sm font-bold text-text-secondary mb-3">اخر المباريات</h2>
{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-8 gap-2 rounded-2xl bg-surface-1 border border-border">
<Clock size={24} className="text-text-muted/40" />
<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 }}
<motion.div className="flex flex-col items-center gap-3 mt-2" variants={item}>
<Button variant="ghost" onClick={openEdit} className="w-[80%]">
<Pencil size={16} />
تعديل الملف الشخصي
</Button>
<motion.button
className="text-sm font-bold text-[#FF5252]/70 flex items-center gap-1.5 py-2"
whileTap={{ scale: 0.9 }}
onClick={handleLogout}
>
<div className="flex items-center gap-3">
<ResultBadge result={game.result} />
<div>
<p className="text-sm font-semibold">{game.opponent}</p>
<p className="text-[10px] text-text-muted">{game.timeControl} &middot; {formatRelativeTime(game.completedAt)}</p>
</div>
</div>
<RatingBadge change={game.ratingChange} />
<LogOut size={14} />
تسجيل الخروج
</motion.button>
</motion.div>
</motion.div>
))}
</div>
)}
</div>
<AnimatePresence>
{editOpen && (
......@@ -245,25 +193,25 @@ export function ProfilePage() {
exit={{ opacity: 0 }}
>
<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)}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
<motion.div
className="relative w-full max-w-[340px] rounded-2xl bg-surface-1 border border-border p-6 flex flex-col gap-5"
initial={{ scale: 0.9, opacity: 0, y: 20 }}
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.85, opacity: 0, y: 30 }}
animate={{ scale: 1, opacity: 1, y: 0 }}
exit={{ scale: 0.9, opacity: 0, y: 20 }}
transition={{ type: 'spring', stiffness: 400, damping: 25 }}
exit={{ scale: 0.85, opacity: 0, y: 30 }}
transition={{ type: 'spring', stiffness: 400, damping: 22 }}
>
<div className="flex items-center justify-between">
<h2 className="text-lg font-bold">تعديل الملف الشخصي</h2>
<h2 className="text-lg font-black">تعديل الملف الشخصي</h2>
<motion.button
whileTap={{ scale: 0.9 }}
whileTap={{ scale: 0.85 }}
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" />
</motion.button>
......@@ -271,35 +219,24 @@ export function ProfilePage() {
<div className="flex flex-col gap-4">
<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
type="text"
value={editForm.display_name}
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}
dir="rtl"
/>
</div>
<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
value={editForm.bio}
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}
/>
</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}
dir="rtl"
/>
</div>
</div>
......@@ -308,6 +245,7 @@ export function ProfilePage() {
onClick={handleSave}
loading={saving}
disabled={!editForm.display_name.trim()}
className="w-[80%] mx-auto"
>
حفظ التعديلات
</Button>
......@@ -319,54 +257,12 @@ export function ProfilePage() {
)
}
function ResultBadge({ result }: { result: 'win' | 'loss' | 'draw' }) {
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 }) {
function StatBox({ icon, value, label }: { icon: React.ReactNode; value: number | string; label: string }) {
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}
<span className="text-base font-bold">{value}</span>
<span className="text-[9px] text-text-muted text-center leading-tight font-medium">{label}</span>
<span className="text-base font-black">{value}</span>
<span className="text-[9px] text-text-muted text-center leading-tight font-bold">{label}</span>
</div>
)
}
......@@ -50,27 +50,41 @@ export function RegisterPage() {
}
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
className="absolute inset-0 opacity-30"
className="absolute inset-0"
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
className="relative w-full max-w-[310px]"
initial={{ opacity: 0, y: 30 }}
className="relative w-full max-w-[380px] px-5"
initial={{ opacity: 0, y: 40 }}
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">
<GoldCrown size={56} />
<h1 className="mt-3 text-2xl font-black text-gold">حساب جديد</h1>
<p className="mt-1 text-sm text-text-secondary">انضم الى مجتمع EL3AB</p>
<GoldCrown size={72} />
<h1 className="mt-4 text-4xl font-black text-gold">EL3AB</h1>
<p className="mt-2 text-lg font-bold text-text-secondary">حساب جديد</p>
</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
label="اسم المستخدم"
value={username}
......@@ -100,25 +114,26 @@ export function RegisterPage() {
{error && (
<motion.p
className="text-sm text-coral text-center"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="text-sm font-bold text-coral text-center"
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ type: 'spring', stiffness: 500, damping: 20 }}
>
{error}
</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>
</form>
</motion.form>
<motion.button
onClick={() => navigate('/login')}
className="mt-4 w-full text-center text-sm text-text-secondary hover:text-gold transition-colors"
whileTap={{ scale: 0.98 }}
className="mt-5 w-full text-center text-sm font-bold text-text-secondary hover:text-gold transition-colors"
whileTap={{ scale: 0.96 }}
>
لديك حساب بالفعل؟ <span className="text-gold font-semibold">سجل دخول</span>
لديك حساب بالفعل؟ <span className="text-gold font-black">سجل دخول</span>
</motion.button>
</motion.div>
</div>
......
......@@ -16,64 +16,63 @@ export function SettingsPage() {
}
return (
<PageTransition className="px-7 py-7 flex flex-col gap-5">
<h1 className="text-xl font-bold">الاعدادات</h1>
<PageTransition>
<h1 className="text-2xl font-black">الاعدادات</h1>
<div className="flex flex-col gap-4">
<Card className="flex items-center justify-between">
<div className="flex items-center gap-3">
{soundEnabled ? <Volume2 size={18} className="text-cyan" /> : <VolumeX size={18} className="text-text-muted" />}
<span className="text-sm font-semibold">الاصوات</span>
{soundEnabled ? <Volume2 size={20} className="text-cyan" /> : <VolumeX size={20} className="text-text-muted" />}
<span className="text-sm font-bold">الاصوات</span>
</div>
<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)}
whileTap={{ scale: 0.9 }}
>
<motion.div
className="w-5 h-5 rounded-full bg-white"
animate={{ x: soundEnabled ? 0 : 24 }}
className={`w-5 h-5 rounded-full ${soundEnabled ? 'bg-cyan' : 'bg-text-muted'}`}
animate={{ x: soundEnabled ? 0 : 28 }}
transition={{ type: 'spring', stiffness: 500, damping: 30 }}
/>
</motion.button>
</Card>
<Card className="flex items-center gap-3">
<Info size={18} className="text-text-muted" />
<Info size={20} className="text-gold" />
<div>
<p className="text-sm font-semibold">EL3AB Player</p>
<p className="text-xs text-text-muted">الاصدار 1.0.0</p>
<p className="text-sm font-bold">EL3AB Player</p>
<p className="text-xs text-text-muted font-semibold">الاصدار 1.0.0</p>
</div>
</Card>
<Card className="flex items-center gap-3">
<Bug size={18} className="text-text-muted" />
<Bug size={20} className="text-purple" />
<div>
<p className="text-sm font-semibold">الابلاغ عن مشكلة</p>
<p className="text-xs text-text-muted">ساعدنا نحسن التطبيق</p>
<p className="text-sm font-bold">الابلاغ عن مشكلة</p>
<p className="text-xs text-text-muted font-semibold">ساعدنا في تحسين التطبيق</p>
</div>
</Card>
</div>
<div className="pt-4">
<div className="flex flex-col gap-3 mt-4">
<motion.button
whileTap={{ scale: 0.95 }}
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} />
<span>تسجيل الخروج</span>
</motion.button>
</div>
<div className="pt-2">
<h3 className="text-xs font-bold text-text-muted mb-3">منطقة الخطر</h3>
<button
<motion.button
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} />
<span>حذف الحساب</span>
<span className="text-[10px] mr-1 px-2 py-0.5 rounded-full bg-surface-3 text-text-muted">قريبا</span>
</button>
<span className="text-[10px] bg-surface-3 px-2 py-0.5 rounded-full">قريبا</span>
</motion.button>
</div>
</PageTransition>
)
......
......@@ -57,20 +57,20 @@ export function ShopPage() {
}
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 gap-2">
<ShoppingBag size={22} className="text-gold" />
<h1 className="text-xl font-bold">المتجر</h1>
<div className="flex items-center gap-2.5">
<ShoppingBag size={24} className="text-[#FFC83D]" />
<h1 className="text-2xl font-black">المتجر</h1>
</div>
<div className="flex items-center gap-3">
<div className="flex items-center gap-1.5 bg-surface-2 border border-border rounded-xl px-3 py-1.5">
<Coins size={14} className="text-gold" />
<span className="text-sm font-bold text-gold">{profile?.coins || 0}</span>
<div className="flex items-center gap-2">
<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-[#FFC83D]" />
<span className="text-sm font-black text-[#FFC83D]">{profile?.coins || 0}</span>
</div>
<div className="flex items-center gap-1.5 bg-surface-2 border border-border rounded-xl px-3 py-1.5">
<Gem size={14} className="text-purple" />
<span className="text-sm font-bold text-purple">{profile?.gems || 0}</span>
<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-[#B44DFF]" />
<span className="text-sm font-black text-[#B44DFF]">{profile?.gems || 0}</span>
</div>
</div>
</div>
......@@ -80,12 +80,13 @@ export function ShopPage() {
<motion.button
key={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
? 'bg-gold/15 border border-gold/40 text-gold'
: 'bg-surface-2 border border-border text-text-muted'
? 'bg-[#FF8C42] border-[#FF8C42] text-white'
: '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}
</motion.button>
......@@ -95,25 +96,25 @@ export function ShopPage() {
{loading ? (
<div className="flex-1 flex items-center justify-center py-20">
<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 }}
transition={{ duration: 1, repeat: Infinity, ease: 'linear' }}
transition={{ duration: 0.8, repeat: Infinity, ease: 'linear' }}
/>
</div>
) : items.length === 0 ? (
<div className="flex-1 flex flex-col items-center justify-center py-16 gap-4">
<motion.div
className="w-16 h-16 rounded-full bg-surface-2 border border-border flex items-center justify-center"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ type: 'spring', stiffness: 300, damping: 20 }}
className="w-20 h-20 rounded-2xl bg-surface-2 border-3 border-border flex items-center justify-center"
initial={{ scale: 0, rotate: -10 }}
animate={{ scale: 1, rotate: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 18 }}
>
<ShoppingBag size={24} className="text-text-muted" />
<ShoppingBag size={32} className="text-text-muted" />
</motion.div>
<p className="text-text-muted text-sm">لا توجد عناصر متاحة</p>
<p className="text-text-muted text-sm font-bold">لا توجد عناصر متاحة</p>
</div>
) : (
<div className="grid grid-cols-2 gap-3">
<div className="grid grid-cols-2 gap-4">
{items.map((item, index) => {
const isOwned = ownedIds.includes(item.id)
const isEquipped = equippedIds.includes(item.id)
......@@ -124,25 +125,35 @@ export function ShopPage() {
<motion.button
key={item.id}
onClick={() => setSelectedItem(item)}
className="relative flex flex-col items-center gap-2 p-4 rounded-2xl bg-surface-1 border overflow-hidden text-center"
style={{ borderColor: `${rarityColor}40` }}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.05 }}
whileTap={{ scale: 0.96 }}
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 }}
initial={{ opacity: 0, y: 16, scale: 0.9 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{ type: 'spring', stiffness: 400, damping: 22, delay: index * 0.06 }}
whileHover={{ y: -6, scale: 1.03, boxShadow: `0 12px 30px ${rarityColor}30` }}
whileTap={{ scale: 0.95 }}
>
{isLegendary && (
<motion.div
className="absolute inset-0 rounded-2xl"
style={{ boxShadow: `inset 0 0 20px ${rarityColor}20, 0 0 15px ${rarityColor}15` }}
animate={{ opacity: [0.5, 1, 0.5] }}
className="absolute inset-0 rounded-2xl pointer-events-none"
style={{ boxShadow: `inset 0 0 24px ${rarityColor}25, 0 0 20px ${rarityColor}20` }}
animate={{ opacity: [0.4, 1, 0.4] }}
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
className="w-full aspect-square rounded-xl bg-surface-2 flex items-center justify-center relative"
style={{ borderColor: `${rarityColor}30` }}
className="w-full aspect-square rounded-xl bg-surface-2 border-2 flex items-center justify-center"
style={{ borderColor: `${rarityColor}40` }}
>
{item.preview_url ? (
<img
......@@ -152,40 +163,33 @@ export function ShopPage() {
/>
) : (
<div
className="w-12 h-12 rounded-xl"
style={{ background: `linear-gradient(135deg, ${rarityColor}40, ${rarityColor}10)` }}
className="w-14 h-14 rounded-full"
style={{ background: `radial-gradient(circle, ${rarityColor}60, ${rarityColor}15)` }}
/>
)}
</div>
<div className="flex items-center gap-1.5">
<div
className="w-2 h-2 rounded-full"
style={{ backgroundColor: rarityColor }}
className="w-2.5 h-2.5 rounded-full border-2"
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}
</span>
</div>
{isOwned ? (
<div className="flex items-center gap-1 bg-green-500/10 border border-green-500/30 rounded-lg px-2 py-0.5">
<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">
{!isOwned && (
<div className="flex items-center gap-1.5 bg-surface-2 border-2 border-border rounded-lg px-2.5 py-1">
{item.price_gems ? (
<>
<Gem size={12} className="text-purple" />
<span className="text-xs font-bold text-purple">{item.price_gems}</span>
<Gem size={12} className="text-[#B44DFF]" />
<span className="text-xs font-black text-[#B44DFF]">{item.price_gems}</span>
</>
) : (
<>
<Coins size={12} className="text-gold" />
<span className="text-xs font-bold text-gold">{item.price_coins}</span>
<Coins size={12} className="text-[#FFC83D]" />
<span className="text-xs font-black text-[#FFC83D]">{item.price_coins}</span>
</>
)}
</div>
......@@ -206,16 +210,16 @@ export function ShopPage() {
onClick={() => !purchasing && setSelectedItem(null)}
>
<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"
initial={{ scale: 0.8, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.8, opacity: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 25 }}
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.7, opacity: 0, rotate: -3 }}
animate={{ scale: 1, opacity: 1, rotate: 0 }}
exit={{ scale: 0.7, opacity: 0, rotate: 3 }}
transition={{ type: 'spring', stiffness: 350, damping: 22 }}
onClick={(e) => e.stopPropagation()}
>
<button
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" />
</button>
......@@ -225,33 +229,22 @@ export function ShopPage() {
className="flex flex-col items-center gap-4 py-8"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ type: 'spring', stiffness: 300, damping: 20 }}
transition={{ type: 'spring', stiffness: 300, damping: 18 }}
>
<motion.div
className="w-16 h-16 rounded-full bg-green-500/10 border-2 border-green-500/40 flex items-center justify-center"
animate={{ scale: [1, 1.1, 1] }}
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.15, 1] }}
transition={{ duration: 0.5 }}
>
<Check size={32} 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>
<Check size={36} className="text-green-400" />
</motion.div>
<p className="text-xl font-black text-green-400">تم الشراء بنجاح</p>
</motion.div>
) : (
<>
<div
className="w-full aspect-[4/3] rounded-2xl bg-surface-2 flex items-center justify-center mt-4"
style={{ borderColor: `${RARITY_COLORS[selectedItem.rarity]}30` }}
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]}40` }}
>
{selectedItem.preview_url ? (
<img
......@@ -261,16 +254,16 @@ export function ShopPage() {
/>
) : (
<div
className="w-20 h-20 rounded-2xl"
className="w-24 h-24 rounded-full"
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 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 && (
<p className="text-xs text-text-muted mt-1">{selectedItem.description}</p>
)}
......@@ -278,26 +271,26 @@ export function ShopPage() {
<div className="flex items-center gap-2">
<div
className="w-2.5 h-2.5 rounded-full"
style={{ backgroundColor: RARITY_COLORS[selectedItem.rarity] }}
className="w-3 h-3 rounded-full border-2"
style={{ backgroundColor: RARITY_COLORS[selectedItem.rarity], borderColor: `${RARITY_COLORS[selectedItem.rarity]}80` }}
/>
<span
className="text-xs font-bold"
className="text-sm font-black"
style={{ color: RARITY_COLORS[selectedItem.rarity] }}
>
{RARITY_LABELS[selectedItem.rarity]}
</span>
{selectedItem.rarity === 'legendary' && (
<Sparkles size={12} style={{ color: RARITY_COLORS.legendary }} />
<Sparkles size={14} style={{ color: RARITY_COLORS.legendary }} />
)}
</div>
{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) ? (
<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" />
<span className="text-sm font-bold text-green-400">مفعّل حاليا</span>
<span className="text-sm font-black text-green-400">مفعّل حاليا</span>
</div>
) : (
<Button
......@@ -312,17 +305,17 @@ export function ShopPage() {
)}
</div>
) : (
<div className="flex flex-col gap-3 w-full">
<div className="flex items-center justify-center gap-2 bg-surface-2 rounded-xl px-4 py-3 border border-border">
<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-2 border-border">
{selectedItem.price_gems ? (
<>
<Gem size={18} className="text-purple" />
<span className="text-lg font-black text-purple">{selectedItem.price_gems}</span>
<Gem size={18} className="text-[#B44DFF]" />
<span className="text-lg font-black text-[#B44DFF]">{selectedItem.price_gems}</span>
</>
) : (
<>
<Coins size={18} className="text-gold" />
<span className="text-lg font-black text-gold">{selectedItem.price_coins}</span>
<Coins size={18} className="text-[#FFC83D]" />
<span className="text-lg font-black text-[#FFC83D]">{selectedItem.price_coins}</span>
</>
)}
</div>
......@@ -343,7 +336,7 @@ export function ShopPage() {
{(selectedItem.price_gems
? (profile?.gems || 0) < (selectedItem.price_gems || 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>
)}
......
......@@ -57,7 +57,7 @@ export function SplashPage() {
</motion.div>
<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 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.6, duration: 0.5 }}
......
import { useState } from 'react'
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 { Card } from '../components/ui/Card'
import { Button } from '../components/ui/Button'
import { useAuthStore } from '../stores/authStore'
import { useTournaments } from '../hooks/useTournaments'
......@@ -32,39 +33,18 @@ const TIME_CONTROL_LABELS: Record<string, string> = {
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 }) {
const config: Record<string, { label: string; classes: string }> = {
registration: { label: 'مفتوحة', classes: 'bg-cyan/10 text-cyan border-cyan/30' },
in_progress: { label: 'جارية', classes: 'bg-gold/10 text-gold border-gold/30 animate-pulse' },
registration: { label: 'مفتوحة', classes: 'bg-[#00E5CC]/15 text-[#00E5CC] border-[#00E5CC]/50' },
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' },
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
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}
</span>
)
......@@ -74,14 +54,16 @@ export function TournamentsPage() {
const [activeFilter, setActiveFilter] = useState<StatusFilter>(null)
const { tournaments, myRegistrations, loading, register, unregister } = useTournaments(activeFilter)
const { user } = useAuthStore()
const [expandedId, setExpandedId] = useState<string | null>(null)
const isRegistered = (tournamentId: string) =>
myRegistrations.some((r) => r.tournament_id === tournamentId)
return (
<PageTransition className="px-7 py-7 flex flex-col gap-5">
<h1 className="text-xl font-bold">البطولات</h1>
<PageTransition className="flex flex-col gap-5">
<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">
{FILTERS.map((f) => (
......@@ -89,11 +71,12 @@ export function TournamentsPage() {
key={f.label}
whileTap={{ scale: 0.93 }}
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
? 'bg-gold text-bg-primary'
: 'bg-surface-2 text-text-muted border border-border'
? 'bg-[#00E5CC] border-[#00E5CC] text-background font-black'
: 'bg-surface-2 text-text-muted border-border hover:border-[#00E5CC]/40'
}`}
transition={{ type: 'spring', stiffness: 500, damping: 20 }}
>
{f.label}
</motion.button>
......@@ -101,125 +84,104 @@ export function TournamentsPage() {
</div>
{loading ? (
<div className="flex flex-col gap-3">
<div className="flex flex-col gap-4">
{[1, 2, 3].map((i) => (
<div key={i} className="rounded-2xl bg-surface-1 border border-border p-5 animate-pulse">
<div className="w-32 h-4 rounded bg-surface-3 mb-3" />
<div className="w-20 h-3 rounded bg-surface-3" />
<div key={i} className="rounded-2xl bg-surface-1 border-3 border-border p-5 animate-pulse">
<div className="w-36 h-5 rounded bg-surface-3 mb-3" />
<div className="w-24 h-3 rounded bg-surface-3" />
</div>
))}
</div>
) : tournaments.length === 0 ? (
<div className="flex flex-col items-center justify-center py-16 gap-4">
<motion.div
className="w-16 h-16 rounded-full bg-surface-2 border border-border flex items-center justify-center"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ type: 'spring', stiffness: 300, damping: 20 }}
className="w-20 h-20 rounded-2xl bg-surface-2 border-3 border-[#FFC83D]/30 flex items-center justify-center"
initial={{ scale: 0, rotate: -10 }}
animate={{ scale: 1, rotate: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 18 }}
>
<Trophy size={24} className="text-gold" />
<Trophy size={32} className="text-[#FFC83D]" />
</motion.div>
<p className="text-text-muted text-sm">لا توجد بطولات حالية</p>
<p className="text-text-muted text-sm font-bold">لا توجد بطولات</p>
</div>
) : (
<div className="flex flex-col gap-3">
<div className="flex flex-col gap-4">
<AnimatePresence>
{tournaments.map((t, i) => (
<motion.div
key={t.id}
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -12 }}
transition={{ delay: i * 0.05 }}
initial={{ opacity: 0, y: 20, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: -16 }}
transition={{ type: 'spring', stiffness: 400, damping: 24, delay: i * 0.06 }}
>
<Card
className="flex flex-col gap-3"
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>
<Card className="flex flex-col gap-3 relative">
<div className="absolute top-4 left-4">
<StatusBadge status={t.status} />
</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">
<Clock size={11} />
<Clock size={12} />
{TIME_CONTROL_LABELS[t.time_control] ?? t.time_control}
</span>
<span className="flex items-center gap-1">
<Users size={11} />
<Users size={12} />
{t.registrations_count}/{t.max_players}
</span>
<span className="flex items-center gap-1">
<Calendar size={11} />
{formatRelativeTime(t.starts_at)}
</span>
</div>
{(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 && (
<span className="flex items-center gap-1 text-gold font-bold">
<Coins size={12} className="text-gold" />
<span className="flex items-center gap-1.5 text-[#FFC83D] font-black text-sm">
<Coins size={14} className="text-[#FFC83D]" />
{t.prize_pool_coins.toLocaleString('ar-EG')}
</span>
)}
{t.prize_pool_gems > 0 && (
<span className="flex items-center gap-1 text-purple font-bold">
<Gem size={12} className="text-purple" />
<span className="flex items-center gap-1.5 text-[#B44DFF] font-black text-sm">
<Gem size={14} className="text-[#B44DFF]" />
{t.prize_pool_gems.toLocaleString('ar-EG')}
</span>
)}
</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 && (
<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
whileTap={{ scale: 0.95 }}
onClick={(e) => {
e.stopPropagation()
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'
}`}
whileTap={{ scale: 0.93 }}
onClick={() => unregister(t.id)}
className="px-3 py-2 rounded-xl bg-[#FF5252]/10 border-2 border-[#FF5252]/30 text-[#FF5252] text-xs font-bold"
>
{isRegistered(t.id) ? 'إلغاء التسجيل' : 'سجّل الآن'}
إلغاء
</motion.button>
</div>
) : (
<Button
variant="cyan"
size="sm"
onClick={() => register(t.id)}
className="w-[80%] mx-auto"
>
سجل
</Button>
)}
</div>
</motion.div>
)}
</AnimatePresence>
</Card>
</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