Commit 4d37e0d3 authored by Mahmoud Aglan's avatar Mahmoud Aglan

go

parent 6a8bf93c
import { chromium } from 'playwright'
import { mkdirSync } from 'fs'
const PAGES = [
{ name: 'login', url: '/login' },
{ name: 'register', url: '/register' },
{ name: 'home', url: '/' },
{ name: 'play', url: '/play' },
{ name: 'profile', url: '/profile' },
{ name: 'friends', url: '/friends' },
{ name: 'tournaments', url: '/tournaments' },
{ name: 'leaderboard', url: '/leaderboard' },
{ name: 'shop', url: '/shop' },
{ name: 'notifications', url: '/notifications' },
{ name: 'settings', url: '/settings' },
{ name: 'bot-select', url: '/bot-select' },
{ name: 'matchmaking', url: '/matchmaking' },
]
const BASE = 'https://el3ab-player.caprover.al-arcade.com'
async function main() {
mkdirSync('screenshots', { recursive: true })
const browser = await chromium.launch()
// Mobile viewport
const mobile = await browser.newContext({
viewport: { width: 390, height: 844 },
deviceScaleFactor: 2,
locale: 'ar',
})
// Desktop viewport
const desktop = await browser.newContext({
viewport: { width: 1440, height: 900 },
deviceScaleFactor: 1,
locale: 'ar',
})
// Login first to get session for protected pages
const loginPage = await mobile.newPage()
await loginPage.goto(`${BASE}/login`, { waitUntil: 'networkidle', timeout: 20000 })
await loginPage.waitForTimeout(1000)
await loginPage.fill('input[type="email"]', 'testplayer1@el3ab.com')
await loginPage.fill('input[type="password"]', 'test123456')
await loginPage.click('button[type="submit"]')
await loginPage.waitForTimeout(3000)
await loginPage.close()
// Also login on desktop context
const loginDesktop = await desktop.newPage()
await loginDesktop.goto(`${BASE}/login`, { waitUntil: 'networkidle', timeout: 20000 })
await loginDesktop.waitForTimeout(1000)
await loginDesktop.fill('input[type="email"]', 'testplayer1@el3ab.com')
await loginDesktop.fill('input[type="password"]', 'test123456')
await loginDesktop.click('button[type="submit"]')
await loginDesktop.waitForTimeout(3000)
await loginDesktop.close()
for (const { name, url } of PAGES) {
// Mobile screenshot
const mPage = await mobile.newPage()
await mPage.goto(`${BASE}${url}`, { waitUntil: 'networkidle', timeout: 15000 })
await mPage.waitForTimeout(1500)
await mPage.screenshot({ path: `screenshots/${name}-mobile.png` })
console.log(`captured ${name}-mobile`)
await mPage.close()
// Desktop screenshot
const dPage = await desktop.newPage()
await dPage.goto(`${BASE}${url}`, { waitUntil: 'networkidle', timeout: 15000 })
await dPage.waitForTimeout(1500)
await dPage.screenshot({ path: `screenshots/${name}-desktop.png` })
console.log(`captured ${name}-desktop`)
await dPage.close()
}
await browser.close()
console.log('Done! All screenshots captured.')
}
main()
import { Outlet } from 'react-router-dom'
import { Header } from './Header'
import { BottomNav } from './BottomNav'
import { DecorativeBackground } from './DecorativeBackground'
import { ToastContainer } from '../ui/ToastContainer'
import { usePresence } from '../../hooks/usePresence'
import { useNotifications } from '../../hooks/useNotifications'
......@@ -10,12 +11,15 @@ export function AppShell() {
useNotifications()
return (
<div className="flex flex-col min-h-dvh bg-background">
<Header />
<main className="flex-1 pb-28 overflow-y-auto">
<Outlet />
</main>
<BottomNav />
<div className="relative flex flex-col min-h-dvh overflow-hidden">
<DecorativeBackground />
<div className="relative z-10 flex flex-col min-h-dvh">
<Header />
<main className="flex-1 pb-28 overflow-y-auto">
<Outlet />
</main>
<BottomNav />
</div>
<ToastContainer />
</div>
)
......
......@@ -10,52 +10,119 @@ const NAV_ITEMS = [
{ path: '/profile', icon: User, label: 'حسابي' },
]
const CENTER_INDEX = 2
export function BottomNav() {
const location = useLocation()
const navigate = useNavigate()
return (
<nav className="fixed bottom-0 left-0 right-0 z-50 px-4 pb-3 pt-1 md:px-6">
<nav className="fixed bottom-0 left-0 right-0 z-50 px-4 pb-3 pt-1">
<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
return (
<motion.button
key={item.path}
onClick={() => navigate(item.path)}
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={22}
className={isActive ? 'text-gold' : 'text-text-muted'}
strokeWidth={isActive ? 2.8 : 2}
/>
</motion.div>
<span className={`text-[10px] font-bold ${isActive ? 'text-gold' : 'text-text-muted'}`}>
{item.label}
</span>
{isActive && (
<motion.div
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>
)
})}
{/* Decorative corner accents */}
<div className="relative">
{/* Left L-accent */}
<div className="absolute -top-1 left-1 w-3 h-3 border-t-2 border-l-2 border-gold/40 rounded-tl-sm pointer-events-none" />
{/* Right L-accent */}
<div className="absolute -top-1 right-1 w-3 h-3 border-t-2 border-r-2 border-gold/40 rounded-tr-sm pointer-events-none" />
{/* Main bar */}
<div className="game-panel border-4 border-border bg-surface-2 shadow-[0_8px_32px_rgba(0,0,0,0.6),0_2px_8px_rgba(0,0,0,0.4)]">
<div className="flex items-center justify-around px-2 py-2 relative">
{NAV_ITEMS.map((item, index) => {
const isActive = location.pathname === item.path
const isCenter = index === CENTER_INDEX
const Icon = item.icon
return (
<motion.button
key={item.path}
onClick={() => navigate(item.path)}
className="relative flex flex-col items-center gap-0.5 px-2 py-1"
whileTap={{ scale: 0.8 }}
>
{/* Slot background */}
<div
className={`relative flex items-center justify-center rounded-xl transition-colors duration-200 ${
isCenter ? 'w-12 h-12' : 'w-10 h-10'
} ${
isActive
? 'bg-gold/15'
: 'bg-surface-3/60'
} ${
isCenter ? 'border-2 border-gold/20' : 'border border-border/50'
}`}
>
{/* Hex outline for center item */}
{isCenter && (
<div
className="absolute inset-0 border border-gold/15"
style={{
clipPath: 'polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%)',
}}
/>
)}
<motion.div
animate={
isActive
? { y: -14, scale: 1.15 }
: { y: 0, scale: 1 }
}
transition={{ type: 'spring', stiffness: 400, damping: 22 }}
>
{/* Glowing badge behind active icon */}
{isActive && (
<motion.div
className="absolute inset-0 -m-2 rounded-full bg-gold/20 blur-sm"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.2 }}
/>
)}
<Icon
size={isCenter ? 24 : 20}
className={
isActive
? 'text-gold drop-shadow-[0_0_6px_rgba(255,200,60,0.6)]'
: 'text-text-muted'
}
strokeWidth={isActive ? 2.8 : 2}
/>
</motion.div>
</div>
{/* Label: shows when active */}
<motion.span
className="text-[9px] font-bold text-gold"
initial={false}
animate={{
opacity: isActive ? 1 : 0,
y: isActive ? 0 : 4,
}}
transition={{ duration: 0.2 }}
>
{item.label}
</motion.span>
{/* Active glow indicator */}
{isActive && (
<motion.div
className="absolute -bottom-1 w-5 h-[3px] rounded-full bg-gold"
layoutId="nav-indicator"
style={{ boxShadow: '0 0 8px rgba(255, 200, 60, 0.7)' }}
transition={{ type: 'spring', stiffness: 500, damping: 30 }}
/>
)}
</motion.button>
)
})}
</div>
</div>
{/* Bottom L-accents */}
<div className="absolute -bottom-1 left-1 w-3 h-3 border-b-2 border-l-2 border-gold/40 rounded-bl-sm pointer-events-none" />
<div className="absolute -bottom-1 right-1 w-3 h-3 border-b-2 border-r-2 border-gold/40 rounded-br-sm pointer-events-none" />
</div>
</div>
</nav>
......
import { motion } from 'framer-motion'
const particles = [
{ x: '12%', delay: 0, duration: 4.5 },
{ x: '28%', delay: 1.2, duration: 5.2 },
{ x: '45%', delay: 0.5, duration: 3.8 },
{ x: '62%', delay: 2.1, duration: 4.8 },
{ x: '78%', delay: 0.8, duration: 5.5 },
{ x: '88%', delay: 1.6, duration: 4.2 },
{ x: '35%', delay: 2.8, duration: 5.0 },
]
function generateRayLines() {
const lines = []
for (let i = 0; i < 12; i++) {
const angle = (i * 30 * Math.PI) / 180
const x2 = 50 + 45 * Math.cos(angle)
const y2 = 50 + 45 * Math.sin(angle)
lines.push(
<line
key={i}
x1="50"
y1="50"
x2={x2}
y2={y2}
stroke="rgba(255, 200, 60, 0.04)"
strokeWidth="0.5"
/>
)
}
return lines
}
export function DecorativeBackground() {
return (
<div className="fixed inset-0 pointer-events-none z-0 overflow-hidden">
{/* Arena gradient base */}
<div className="absolute inset-0 bg-arena" />
{/* Rotating ray pattern */}
<motion.div
className="absolute inset-0 flex items-center justify-center"
animate={{ rotate: 360 }}
transition={{
duration: 90,
repeat: Infinity,
ease: 'linear',
}}
>
<svg
viewBox="0 0 100 100"
className="w-[140vmax] h-[140vmax] opacity-60"
xmlns="http://www.w3.org/2000/svg"
>
{generateRayLines()}
</svg>
</motion.div>
{/* Floating gold particles */}
{particles.map((particle, i) => (
<motion.div
key={i}
className="absolute w-1 h-1 rounded-full bg-gold/30"
style={{
left: particle.x,
top: '80%',
}}
animate={{
y: [0, -window.innerHeight * 0.7, -window.innerHeight],
opacity: [0, 0.8, 0],
}}
transition={{
duration: particle.duration,
repeat: Infinity,
delay: particle.delay,
ease: 'linear',
}}
/>
))}
</div>
)
}
......@@ -11,55 +11,99 @@ export function Header() {
const navigate = useNavigate()
return (
<header className="sticky top-0 z-50 px-4 pt-3 pb-1 md:px-6">
<header className="sticky top-0 z-50 px-4 pt-3 pb-2">
<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>
{/* HUD Bar with diagonal bottom edge */}
<div
className="game-panel relative border-4 border-border bg-surface-2"
style={{
clipPath: 'polygon(0 0, 100% 0, 100% 85%, 98% 100%, 2% 100%, 0% 85%)',
}}
>
<div className="flex items-center justify-between px-4 py-2.5">
{/* Logo section (right side in RTL) */}
<div className="flex items-center gap-2.5">
{/* Shield-shaped container */}
<div
className="relative flex items-center justify-center w-11 h-11 bg-surface-3 border-2 border-gold/40"
style={{
clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)',
}}
>
<GoldCrown size={22} animate={false} />
</div>
<span
className="text-lg font-black text-gold tracking-widest"
style={{
textShadow: '0 2px 4px rgba(0,0,0,0.5), 0 0 12px rgba(255,200,60,0.3)',
}}
>
EL3AB
</span>
</div>
<div className="flex items-center gap-2">
{/* Center: Level indicator */}
{profile && (
<>
<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>
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">
<div className="w-8 h-8 rounded-full bg-surface-3 border-2 border-gold/50 flex items-center justify-center shadow-[0_0_8px_rgba(255,200,60,0.2)]">
<span className="text-[11px] font-black text-gold">
{profile.level || 1}
</span>
</div>
</div>
)}
{profile.gems > 0 && (
{/* Resources section (left side in RTL) */}
<div className="flex items-center gap-2">
{profile && (
<>
{/* Coins capsule */}
<motion.div
className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-purple/15 border-2 border-purple/40"
className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-gold/10 border-2 border-gold/40 shadow-[inset_0_0_8px_rgba(255,200,60,0.15)]"
whileTap={{ scale: 0.9 }}
>
<Gem size={12} className="text-purple" />
<span className="text-xs font-black text-purple">{profile.gems}</span>
<Coins size={14} className="text-gold" />
<span className="text-xs font-black text-gold">{profile.coins}</span>
</motion.div>
)}
</>
)}
<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-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-[9px] font-black text-white">{unreadCount > 9 ? '9+' : unreadCount}</span>
</motion.div>
{/* Gems capsule */}
{profile.gems > 0 && (
<motion.div
className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-purple/10 border-2 border-purple/40 shadow-[inset_0_0_8px_rgba(180,77,255,0.15)]"
whileTap={{ scale: 0.9 }}
>
<Gem size={12} className="text-purple" />
<span className="text-xs font-black text-purple">{profile.gems}</span>
</motion.div>
)}
</>
)}
</motion.button>
{/* Bell: hexagonal button */}
<motion.button
className="relative flex items-center justify-center w-10 h-10 bg-surface-3 border-2 border-border"
style={{
clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)',
}}
onClick={() => navigate('/notifications')}
whileTap={{ scale: 0.85 }}
whileHover={{ scale: 1.05 }}
>
<Bell size={16} className="text-text-secondary" />
{unreadCount > 0 && (
<motion.div
className="absolute -top-1 -right-1 w-5 h-5 rounded-full bg-coral border-2 border-surface-2 flex items-center justify-center animate-pulse-glow"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ type: 'spring', stiffness: 500, damping: 15 }}
>
<span className="text-[9px] font-black text-white">
{unreadCount > 9 ? '9+' : unreadCount}
</span>
</motion.div>
)}
</motion.button>
</div>
</div>
</div>
</div>
......
......@@ -9,10 +9,10 @@ interface PageTransitionProps {
export function PageTransition({ children, className = '' }: PageTransitionProps) {
return (
<motion.div
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 }}
className={`app-container py-8 flex flex-col gap-7 ${className}`}
initial={{ opacity: 0, y: 24, scale: 0.97 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: -16, scale: 0.98 }}
transition={{ type: 'spring', stiffness: 300, damping: 28 }}
>
{children}
......
export { AppShell } from './AppShell'
export { DecorativeBackground } from './DecorativeBackground'
export { Header } from './Header'
export { BottomNav } from './BottomNav'
export { PageTransition } from './PageTransition'
import { motion } from 'framer-motion'
import type { ReactNode } from 'react'
interface GamePanelProps {
children: ReactNode
className?: string
variant?: 'default' | 'gold' | 'legendary' | 'recessed'
rivets?: boolean
onClick?: () => void
}
export function GamePanel({
children,
className = '',
variant = 'default',
rivets = false,
onClick,
}: GamePanelProps) {
const baseClass = variant === 'recessed' ? 'game-panel-recessed' : 'game-panel'
const goldClass = variant === 'gold' || variant === 'legendary' ? 'game-panel-gold' : ''
return (
<motion.div
className={`${baseClass} ${goldClass} p-5 relative overflow-hidden ${onClick ? 'cursor-pointer' : ''} ${className}`}
whileHover={onClick ? { y: -4, scale: 1.01 } : undefined}
whileTap={onClick ? { scale: 0.97 } : undefined}
onClick={onClick}
transition={{ type: 'spring', stiffness: 400, damping: 22 }}
>
{/* Legendary shimmer overlay */}
{variant === 'legendary' && (
<motion.div
className="absolute inset-0 pointer-events-none"
style={{
background:
'linear-gradient(105deg, transparent 40%, rgba(255, 200, 60, 0.12) 45%, rgba(255, 200, 60, 0.2) 50%, rgba(255, 200, 60, 0.12) 55%, transparent 60%)',
}}
animate={{
x: ['-100%', '200%'],
}}
transition={{
duration: 3,
repeat: Infinity,
ease: 'easeInOut',
repeatDelay: 1.5,
}}
/>
)}
{/* Rivets at 4 corners */}
{rivets && (
<>
<span className="absolute top-3 left-3 w-[6px] h-[6px] rounded-full bg-surface-3 shadow-[inset_0_1px_2px_rgba(0,0,0,0.6),0_1px_0_rgba(255,255,255,0.05)]" />
<span className="absolute top-3 right-3 w-[6px] h-[6px] rounded-full bg-surface-3 shadow-[inset_0_1px_2px_rgba(0,0,0,0.6),0_1px_0_rgba(255,255,255,0.05)]" />
<span className="absolute bottom-3 left-3 w-[6px] h-[6px] rounded-full bg-surface-3 shadow-[inset_0_1px_2px_rgba(0,0,0,0.6),0_1px_0_rgba(255,255,255,0.05)]" />
<span className="absolute bottom-3 right-3 w-[6px] h-[6px] rounded-full bg-surface-3 shadow-[inset_0_1px_2px_rgba(0,0,0,0.6),0_1px_0_rgba(255,255,255,0.05)]" />
</>
)}
{/* Content */}
<div className="relative z-10">{children}</div>
</motion.div>
)
}
interface GameProgressBarProps {
value: number
max: number
color?: 'gold' | 'cyan' | 'green' | 'purple'
showLabel?: boolean
}
const fillColorClass: Record<string, string> = {
gold: 'progress-game-fill',
cyan: 'progress-game-fill progress-game-fill-cyan',
green: 'progress-game-fill progress-game-fill-green',
purple: 'progress-game-fill progress-game-fill-purple',
}
export function GameProgressBar({
value,
max,
color = 'gold',
showLabel = false,
}: GameProgressBarProps) {
const percentage = Math.min(100, Math.max(0, (value / max) * 100))
return (
<div className="w-full">
<div className="progress-game relative">
{/* Fill bar */}
<div
className={`${fillColorClass[color]} relative`}
style={{ width: `${percentage}%` }}
>
{/* Segmented overlay */}
<div
className="absolute inset-0 opacity-20"
style={{
background:
'repeating-linear-gradient(90deg, transparent 0px, transparent 8px, rgba(0,0,0,0.3) 8px, rgba(0,0,0,0.3) 10px)',
}}
/>
</div>
</div>
{showLabel && (
<div className="flex justify-between mt-1">
<span className="text-xs text-text-muted font-bold">{value}</span>
<span className="text-xs text-text-muted">{max}</span>
</div>
)}
</div>
)
}
import type { ComponentType } from 'react'
interface RibbonHeaderProps {
text: string
icon?: ComponentType<{ size?: number; className?: string }>
color?: 'gold' | 'cyan' | 'purple' | 'coral'
size?: 'sm' | 'md'
}
const colorStyles = {
gold: {
gradient: 'linear-gradient(135deg, #FFC83D 0%, #C9972E 100%)',
border: 'rgba(255, 200, 60, 0.6)',
text: '#1a1a2e',
},
cyan: {
gradient: 'linear-gradient(135deg, #00E5CC 0%, #009E8C 100%)',
border: 'rgba(0, 229, 204, 0.6)',
text: '#0a1a1a',
},
purple: {
gradient: 'linear-gradient(135deg, #B44DFF 0%, #7B2EBF 100%)',
border: 'rgba(180, 77, 255, 0.6)',
text: '#ffffff',
},
coral: {
gradient: 'linear-gradient(135deg, #FF5252 0%, #BF2E2E 100%)',
border: 'rgba(255, 82, 82, 0.6)',
text: '#ffffff',
},
}
const sizeStyles = {
sm: {
padding: '6px 24px',
fontSize: '0.8rem',
iconSize: 14,
},
md: {
padding: '10px 36px',
fontSize: '0.95rem',
iconSize: 18,
},
}
export function RibbonHeader({ text, icon: Icon, color = 'gold', size = 'md' }: RibbonHeaderProps) {
const colors = colorStyles[color]
const sizes = sizeStyles[size]
return (
<div className="flex justify-center w-full">
<div
className="ribbon-banner inline-flex items-center gap-2 font-bold"
style={{
background: colors.gradient,
color: colors.text,
padding: sizes.padding,
fontSize: sizes.fontSize,
borderBottom: `3px solid ${colors.border}`,
boxShadow: `0 4px 12px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.2)`,
}}
>
{Icon && <Icon size={sizes.iconSize} className="flex-shrink-0" />}
<span>{text}</span>
</div>
</div>
)
}
import type { ReactNode } from 'react'
interface ShieldBadgeProps {
children: ReactNode
size?: 'sm' | 'md' | 'lg' | 'xl'
color?: 'gold' | 'cyan' | 'purple' | 'coral' | 'default'
glow?: boolean
className?: string
}
const sizeMap = {
sm: 'w-10 h-10',
md: 'w-14 h-14',
lg: 'w-20 h-20',
xl: 'w-28 h-28',
}
const colorMap = {
default: {
bg: 'bg-surface-2',
ring: 'rgba(61, 69, 112, 0.8)',
glow: 'none',
},
gold: {
bg: 'bg-gradient-to-br from-[#FFC83D] to-[#C9972E]',
ring: 'rgba(255, 200, 60, 0.8)',
glow: '0 0 20px rgba(255, 200, 60, 0.4)',
},
cyan: {
bg: 'bg-gradient-to-br from-[#00E5CC] to-[#009E8C]',
ring: 'rgba(0, 229, 204, 0.8)',
glow: '0 0 20px rgba(0, 229, 204, 0.4)',
},
purple: {
bg: 'bg-gradient-to-br from-[#B44DFF] to-[#7B2EBF]',
ring: 'rgba(180, 77, 255, 0.8)',
glow: '0 0 20px rgba(180, 77, 255, 0.4)',
},
coral: {
bg: 'bg-gradient-to-br from-[#FF5252] to-[#BF2E2E]',
ring: 'rgba(255, 82, 82, 0.8)',
glow: '0 0 20px rgba(255, 82, 82, 0.4)',
},
}
export function ShieldBadge({
children,
size = 'md',
color = 'default',
glow = false,
className = '',
}: ShieldBadgeProps) {
const sizeClass = sizeMap[size]
const colorConfig = colorMap[color]
return (
<div
className={`relative inline-flex items-center justify-center ${sizeClass} ${className}`}
style={{
boxShadow: glow ? colorConfig.glow : 'none',
}}
>
{/* Outer ring */}
<div
className="absolute inset-0 clip-shield"
style={{
background: colorConfig.ring,
}}
/>
{/* Inner shield body */}
<div
className={`absolute clip-shield ${colorConfig.bg} flex items-center justify-center`}
style={{
inset: '3px',
}}
/>
{/* Content */}
<div className="relative z-10 flex items-center justify-center text-white font-bold">
{children}
</div>
</div>
)
}
export { Button } from './Button'
export { Card } from './Card'
export { GamePanel } from './GamePanel'
export { GameProgressBar } from './GameProgressBar'
export { Input } from './Input'
export { RibbonHeader } from './RibbonHeader'
export { ShieldBadge } from './ShieldBadge'
export { ToastContainer } from './ToastContainer'
......@@ -76,56 +76,224 @@ body {
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;
/* ============================================================
ARENA BACKGROUND SYSTEM
============================================================ */
.bg-arena {
background:
repeating-conic-gradient(from 0deg at 50% 50%, rgba(255, 200, 60, 0.012) 0deg 10deg, transparent 10deg 20deg),
radial-gradient(ellipse at center, transparent 40%, rgba(0, 0, 0, 0.6) 100%),
linear-gradient(180deg, #0B0E1A 0%, #0F1324 50%, #0B0E1A 100%);
}
/* ============================================================
DECORATIVE CLIP-PATH SHAPES
============================================================ */
.clip-shield {
clip-path: polygon(50% 0%, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%);
}
.clip-diamond {
clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
}
.game-card:hover {
transform: translateY(-3px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
.clip-nameplate {
clip-path: polygon(5% 0%, 100% 0%, 95% 100%, 0% 100%);
}
.game-card-gold {
.ribbon-banner {
clip-path: polygon(8% 0%, 92% 0%, 100% 100%, 0% 100%);
}
/* ============================================================
GAME PANEL SYSTEM
============================================================ */
.game-panel {
position: relative;
border-radius: 20px;
background: linear-gradient(160deg, var(--color-surface-2) 0%, var(--color-surface-1) 100%);
border: 3px solid var(--color-border);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.05),
inset 0 -3px 6px rgba(0, 0, 0, 0.3),
0 6px 20px rgba(0, 0, 0, 0.4);
}
.game-panel-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);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.05),
inset 0 -3px 6px rgba(0, 0, 0, 0.3),
0 6px 20px rgba(0, 0, 0, 0.4),
0 0 24px rgba(255, 200, 60, 0.25);
}
.game-panel-recessed {
position: relative;
border-radius: 16px;
background: var(--color-surface-1);
border: 2px solid var(--color-border);
box-shadow:
inset 0 3px 8px rgba(0, 0, 0, 0.5),
inset 0 1px 2px rgba(0, 0, 0, 0.3);
}
/* === PLAYFUL BUTTON PUSH EFFECT === */
.btn-push {
/* ============================================================
3D BUTTON SYSTEM
============================================================ */
.btn-3d {
position: relative;
border-bottom: 4px solid rgba(0, 0, 0, 0.3);
transition: all 0.1s;
border-radius: 16px;
box-shadow:
0 6px 0 var(--btn-shadow-color, rgba(0, 0, 0, 0.4)),
0 8px 16px rgba(0, 0, 0, 0.3),
inset 0 2px 0 rgba(255, 255, 255, 0.15);
transform: translateY(0);
transition: transform 0.08s, box-shadow 0.08s;
}
.btn-3d:active {
transform: translateY(4px);
box-shadow:
0 2px 0 var(--btn-shadow-color, rgba(0, 0, 0, 0.4)),
0 3px 8px rgba(0, 0, 0, 0.2),
inset 0 2px 0 rgba(255, 255, 255, 0.1);
}
.btn-3d-gold {
--btn-shadow-color: #8B6914;
background: linear-gradient(180deg, var(--color-gold-light) 0%, var(--color-gold) 100%);
color: #1a1a2e;
}
.btn-3d-cyan {
--btn-shadow-color: #006B5E;
background: linear-gradient(180deg, #33FFDD 0%, var(--color-cyan) 100%);
color: #0a1a1a;
}
.btn-3d-coral {
--btn-shadow-color: #8B1A1A;
background: linear-gradient(180deg, #FF7575 0%, var(--color-coral) 100%);
color: #fff;
}
.btn-3d-purple {
--btn-shadow-color: #4A1A8B;
background: linear-gradient(180deg, #C97AFF 0%, var(--color-purple) 100%);
color: #fff;
}
/* ============================================================
GAME PROGRESS BAR
============================================================ */
.progress-game {
height: 16px;
border-radius: 8px;
background: var(--color-surface-3);
border: 2px solid var(--color-border);
overflow: hidden;
}
.progress-game-fill {
height: 100%;
border-radius: 6px;
background: linear-gradient(90deg, var(--color-gold), var(--color-gold-light));
box-shadow: 0 0 12px rgba(255, 200, 60, 0.5);
}
.progress-game-fill-cyan {
background: linear-gradient(90deg, #00B8A5, var(--color-cyan));
box-shadow: 0 0 12px rgba(0, 229, 204, 0.5);
}
.progress-game-fill-green {
background: linear-gradient(90deg, #2DBF60, var(--color-green));
box-shadow: 0 0 12px rgba(74, 222, 128, 0.5);
}
.progress-game-fill-purple {
background: linear-gradient(90deg, #9333EA, var(--color-purple));
box-shadow: 0 0 12px rgba(180, 77, 255, 0.5);
}
/* ============================================================
ANIMATIONS
============================================================ */
@keyframes shimmer {
0% {
transform: translateX(-100%) skewX(-15deg);
}
100% {
transform: translateX(200%) skewX(-15deg);
}
}
@keyframes badge-bounce {
0%, 100% {
transform: scale(1);
}
25% {
transform: scale(1.15);
}
50% {
transform: scale(0.95);
}
75% {
transform: scale(1.05);
}
}
@keyframes rays-rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@keyframes float-gentle {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-8px);
}
}
@keyframes pulse-soft {
0%, 100% {
opacity: 0.6;
}
50% {
opacity: 1;
}
}
.btn-push:active {
border-bottom-width: 1px;
transform: translateY(3px);
.animate-shimmer {
animation: shimmer 3s ease-in-out infinite;
}
/* === 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); }
.animate-badge-bounce {
animation: badge-bounce 0.5s ease-in-out;
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-6px); }
.animate-rays-rotate {
animation: rays-rotate 90s linear infinite;
}
.animate-pulse-glow {
animation: pulse-glow 2s ease-in-out infinite;
.animate-float-gentle {
animation: float-gentle 3s ease-in-out infinite;
}
.animate-float {
animation: float 3s ease-in-out infinite;
.animate-pulse-soft {
animation: pulse-soft 2s ease-in-out infinite;
}
/* === RESPONSIVE CONTAINER === */
/* ============================================================
RESPONSIVE CONTAINER
============================================================ */
.app-container {
width: 100%;
max-width: 480px;
......
......@@ -2,10 +2,8 @@ import { motion } from 'framer-motion'
import { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { PageTransition } from '../components/layout/PageTransition'
import { Card } from '../components/ui/Card'
import { Button } from '../components/ui/Button'
import { fetchBots, getBotPortraitUrl, type Bot } from '../lib/stockfish'
import { ChevronRight, Swords } from 'lucide-react'
import { ChevronRight, Swords, Check } from 'lucide-react'
const DIFFICULTY_COLORS: Record<string, string> = {
beginner: '#00E5CC',
......@@ -17,7 +15,7 @@ const DIFFICULTY_COLORS: Record<string, string> = {
near_perfect: '#FFE066',
}
const TOTAL_STRENGTH_DOTS = 7
const TOTAL_STRENGTH_SEGMENTS = 7
export function BotSelectPage() {
const navigate = useNavigate()
......@@ -40,7 +38,7 @@ export function BotSelectPage() {
if (loading) {
return (
<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" />
<div className="w-12 h-12 border-3 border-[#FFC83D]/30 border-t-[#FFC83D] rounded-full animate-spin" />
<p className="mt-4 text-sm text-text-muted font-bold">جاري تحميل الروبوتات...</p>
</PageTransition>
)
......@@ -48,13 +46,19 @@ export function BotSelectPage() {
return (
<PageTransition className="pb-36">
{/* Header */}
<div className="flex items-center gap-4">
<motion.button
onClick={() => navigate(-1)}
className="w-11 h-11 rounded-xl bg-surface-2 border-2 border-border flex items-center justify-center"
className="w-12 h-12 flex items-center justify-center"
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)' }}
whileTap={{ scale: 0.85 }}
>
<ChevronRight size={20} className="text-text-secondary" />
<div className="w-full h-full bg-surface-2 border-2 border-border flex items-center justify-center"
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)' }}
>
<ChevronRight size={20} className="text-text-secondary" />
</div>
</motion.button>
<div>
<h1 className="text-xl font-black">العب ضد الروبوت</h1>
......@@ -62,27 +66,48 @@ export function BotSelectPage() {
</div>
</div>
{/* Bot cards */}
<div className="flex flex-col gap-3">
{bots.map((bot, i) => {
const isSelected = selectedBot === bot.id
const diffColor = DIFFICULTY_COLORS[bot.style] || '#6E748C'
const strengthLevel = Math.min(i + 1, TOTAL_STRENGTH_DOTS)
const strengthLevel = Math.min(i + 1, TOTAL_STRENGTH_SEGMENTS)
return (
<motion.div
key={bot.id}
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
animate={{
opacity: 1,
x: 0,
y: isSelected ? -4 : 0,
}}
transition={{ delay: i * 0.06, type: 'spring', stiffness: 400, damping: 22 }}
>
<Card
glow={isSelected}
<motion.div
onClick={() => setSelectedBot(bot.id)}
className={`flex items-center gap-4 ${isSelected ? '!border-gold' : ''}`}
className={`game-panel !p-4 flex items-center gap-4 relative overflow-hidden cursor-pointer transition-all ${
isSelected ? '!border-[#FFC83D]' : ''
}`}
style={{
boxShadow: isSelected
? `0 0 16px rgba(255,200,61,0.3), inset 0 1px 0 rgba(255,255,255,0.05)`
: undefined,
}}
>
{/* Colored left accent */}
<div
className="absolute right-0 top-0 bottom-0 w-[5px] rounded-r-full"
style={{ backgroundColor: diffColor }}
/>
{/* Portrait frame */}
<div
className="relative w-14 h-14 rounded-xl overflow-hidden flex-shrink-0 border-2"
style={{ backgroundColor: `${diffColor}15`, borderColor: `${diffColor}60` }}
className="relative w-[60px] h-[60px] rounded-xl overflow-hidden flex-shrink-0 border-3 mr-1"
style={{
borderColor: diffColor,
boxShadow: `inset 0 0 12px ${diffColor}30`,
}}
>
<img
src={getBotPortraitUrl(bot.id)}
......@@ -91,51 +116,71 @@ 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-black">{bot.name_ar?.charAt(0) || bot.name.charAt(0)}</span>
<span className="text-2xl 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-black truncate">{bot.name_ar}</h3>
<h3 className="text-base font-black truncate">{bot.name_ar}</h3>
{/* Style badge - shield shape */}
<span
className="px-2 py-0.5 rounded-lg text-[9px] font-black"
style={{ backgroundColor: `${diffColor}25`, color: diffColor }}
className="px-2.5 py-0.5 text-[9px] font-black rounded-md"
style={{
backgroundColor: `${diffColor}20`,
color: diffColor,
border: `2px solid ${diffColor}40`,
}}
>
{bot.style_ar}
</span>
</div>
<p className="text-[11px] text-text-muted truncate font-semibold">{bot.bio_ar}</p>
<div className="flex items-center gap-3">
{/* Strength - segmented game bar */}
<div className="flex items-center gap-2">
<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) => (
{Array.from({ length: TOTAL_STRENGTH_SEGMENTS }).map((_, segIndex) => (
<div
key={dotIndex}
className="w-[6px] h-[6px] rounded-full"
style={{ backgroundColor: dotIndex < strengthLevel ? diffColor : `${diffColor}20` }}
key={segIndex}
className="w-[14px] h-[7px] rounded-sm"
style={{
backgroundColor: segIndex < strengthLevel ? diffColor : `${diffColor}15`,
boxShadow: segIndex < strengthLevel ? `0 0 4px ${diffColor}40` : 'none',
}}
/>
))}
</div>
</div>
{/* Bio */}
<p className="text-[11px] text-text-muted truncate font-semibold">{bot.bio_ar}</p>
</div>
{/* Selected state - checkmark in shield */}
{isSelected && (
<motion.div
className="w-8 h-8 rounded-full bg-gold flex items-center justify-center flex-shrink-0"
className="w-10 h-10 flex items-center justify-center flex-shrink-0"
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)' }}
initial={{ scale: 0, rotate: -90 }}
animate={{ scale: 1, rotate: 0 }}
transition={{ type: 'spring', stiffness: 500, damping: 15 }}
>
<Swords size={14} className="text-background" />
<div className="w-full h-full bg-[#FFC83D] flex items-center justify-center"
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)' }}
>
<Check size={16} className="text-background" />
</div>
</motion.div>
)}
</Card>
</motion.div>
</motion.div>
)
})}
</div>
{/* Bottom sticky button */}
{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"
......@@ -143,10 +188,16 @@ export function BotSelectPage() {
animate={{ opacity: 1, y: 0 }}
transition={{ type: 'spring', stiffness: 400, damping: 22 }}
>
<div className="app-container !p-0">
<Button onClick={startGame} className="w-[80%] mx-auto block" size="lg">
ابدا المباراة
</Button>
<div className="flex justify-center">
<motion.button
onClick={startGame}
whileTap={{ scale: 0.95 }}
className="btn-3d w-[80%] py-4 rounded-2xl bg-gradient-to-b from-[#FFC83D] to-[#E5A800] text-background text-lg font-black flex items-center justify-center gap-2"
style={{ boxShadow: '0 6px 0 #B8860B, 0 8px 20px rgba(255,200,61,0.3)' }}
>
<Swords size={20} />
<span>ابدا المباراة</span>
</motion.button>
</div>
</motion.div>
)}
......
import { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { UserPlus, UserCheck, UserX, Search, Loader2 } from 'lucide-react'
import { UserPlus, UserCheck, UserX, Search, Loader2, Shield } from 'lucide-react'
import { PageTransition } from '../components/layout/PageTransition'
import { Card } from '../components/ui/Card'
import { Button } from '../components/ui/Button'
import { useFriends } from '../hooks/useFriends'
import { supabase } from '../lib/supabase'
import { useAuthStore } from '../stores/authStore'
......@@ -75,207 +73,283 @@ export function FriendsPage() {
return (
<PageTransition>
<div className="flex flex-col gap-5 py-6">
<div className="flex items-center justify-between">
<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 }}
>
<UserPlus size={20} className="text-[#FFC83D]" />
</motion.div>
{/* Header */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<h1 className="text-2xl font-black text-text-primary">الأصدقاء</h1>
</div>
<motion.div
className="w-11 h-11 flex items-center justify-center"
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)' }}
whileTap={{ scale: 0.85 }}
>
<div className="w-full h-full bg-gradient-to-br from-[#FFC83D]/20 to-[#FFC83D]/5 border-2 border-[#FFC83D]/40 flex items-center justify-center"
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)' }}
>
<UserPlus size={18} className="text-[#FFC83D]" />
</div>
</motion.div>
</div>
<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-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"
/>
</div>
{/* Decorative accent line */}
<div className="h-[3px] rounded-full bg-gradient-to-l from-[#FFC83D] via-[#FFC83D]/40 to-transparent" />
{searching && (
<div className="flex justify-center py-4">
<Loader2 size={22} className="animate-spin text-[#FFC83D]" />
</div>
)}
{/* Search Input - recessed game style */}
<div className="relative">
<Search size={16} className="absolute right-4 top-1/2 -translate-y-1/2 text-text-muted z-10" />
<input
type="text"
placeholder="بحث عن لاعب..."
value={searchQuery}
onChange={(e) => handleSearch(e.target.value)}
className="w-full pr-11 pl-4 py-3.5 rounded-2xl bg-surface-2 border-3 border-border text-sm font-bold placeholder:text-text-muted outline-none focus:border-[#FFC83D]/50 transition-colors"
style={{ boxShadow: 'inset 0 3px 8px rgba(0,0,0,0.4), inset 0 1px 2px rgba(0,0,0,0.3)' }}
dir="rtl"
/>
</div>
<AnimatePresence>
{searchResults.length > 0 && (
<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.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">
{searching && (
<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"
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}>
<div className="game-panel !p-3.5 flex items-center gap-3">
<div className="w-10 h-10 flex items-center justify-center shrink-0"
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)' }}
>
<div className="w-full h-full bg-gradient-to-br from-[#FFC83D]/20 to-[#B44DFF]/10 flex items-center justify-center"
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)' }}
>
<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-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={18} className="text-[#00E5CC] shrink-0" />
) : alreadySent ? (
<span className="text-xs font-bold text-text-muted">تم الارسال</span>
) : (
<Button size="sm" variant="gold" onClick={() => handleSendRequest(result.id)}>
اضافة
</Button>
)}
</Card>
</motion.div>
)
})}
</motion.div>
)}
</AnimatePresence>
</div>
<div className="flex-1 min-w-0">
<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={18} className="text-[#00E5CC] shrink-0" />
) : alreadySent ? (
<span className="text-xs font-bold text-text-muted">تم الارسال</span>
) : (
<motion.button
whileTap={{ scale: 0.9 }}
onClick={() => handleSendRequest(result.id)}
className="btn-3d px-4 py-2 rounded-xl bg-[#FFC83D] text-background text-xs font-black"
>
اضافة
</motion.button>
)}
</div>
</motion.div>
)
})}
</motion.div>
)}
</AnimatePresence>
{loading ? (
<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">
{pendingReceived.map((req) => (
<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">
{loading ? (
<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">
{/* Friend Requests */}
{pendingReceived.length > 0 && (
<motion.div className="flex flex-col gap-2.5" variants={item}>
<h2 className="text-sm font-black text-[#FFC83D] flex items-center gap-2">
<div className="w-1.5 h-4 rounded-full bg-[#FFC83D]" />
طلبات الصداقة ({pendingReceived.length})
</h2>
<div className="flex flex-col gap-2.5">
{pendingReceived.map((req) => (
<div key={req.id} className="game-panel-gold !p-3.5 flex items-center gap-3">
<div className="w-11 h-11 flex items-center justify-center shrink-0"
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)' }}
>
<div className="w-full h-full bg-gradient-to-br from-[#FFC83D]/25 to-[#B44DFF]/15 flex items-center justify-center"
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)' }}
>
<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-black truncate">
{req.profile.display_name || req.profile.username}
</p>
<p className="text-xs text-text-muted font-bold">{req.profile.elo_blitz}</p>
</div>
<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>
))}
</div>
</motion.div>
)}
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-black truncate">
{req.profile.display_name || req.profile.username}
</p>
<p className="text-xs text-text-muted font-bold">{req.profile.elo_blitz}</p>
</div>
<div className="flex gap-2">
<motion.button
whileTap={{ scale: 0.85 }}
onClick={() => acceptRequest(req.id)}
className="btn-3d px-3.5 py-2 rounded-xl bg-[#00E5CC] text-background text-xs font-black"
>
قبول
</motion.button>
<motion.button
whileTap={{ scale: 0.85 }}
onClick={() => rejectRequest(req.id)}
className="btn-3d px-3.5 py-2 rounded-xl bg-[#FF5252] text-white text-xs font-black"
>
رفض
</motion.button>
</div>
</div>
))}
</div>
</motion.div>
)}
{/* Online Friends */}
{onlineFriends.length > 0 && (
<motion.div className="flex flex-col gap-2.5" variants={item}>
<h2 className="text-sm font-black text-[#00E5CC] flex items-center gap-2">
<div className="w-1.5 h-4 rounded-full bg-[#00E5CC]" />
متصل ({onlineFriends.length})
</h2>
<div className="flex flex-col gap-2.5">
{onlineFriends.map((friend) => (
<div key={friend.id} className="game-panel !p-3 flex items-center gap-3 relative overflow-hidden">
{/* Green accent stripe */}
<div className="absolute right-0 top-0 bottom-0 w-[4px] bg-[#4ADE80] rounded-r-full" />
{onlineFriends.length > 0 && (
<motion.div className="flex flex-col gap-2" variants={item}>
<h2 className="text-sm font-black text-[#00E5CC]">
متصل ({onlineFriends.length})
</h2>
<div className="flex flex-col gap-2">
{onlineFriends.map((friend) => (
<Card key={friend.id} className="!p-3.5 flex items-center gap-3">
<div className="relative">
<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">
<div className="relative mr-1">
<div className="w-11 h-11 flex items-center justify-center"
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)' }}
>
<div className="w-full h-full bg-gradient-to-br from-[#00E5CC]/15 to-surface-3 flex items-center justify-center"
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)' }}
>
<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.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-black truncate">
{friend.profile.display_name || friend.profile.username}
</p>
<p className="text-xs text-text-muted font-bold">{friend.profile.elo_blitz}</p>
{/* Online pulse ring */}
<div className="absolute -bottom-0.5 -left-0.5 w-4 h-4 rounded-full bg-[#4ADE80] border-2 border-surface-1">
<motion.div
className="absolute inset-0 rounded-full bg-[#4ADE80]"
animate={{ scale: [1, 1.8], opacity: [0.6, 0] }}
transition={{ duration: 1.5, repeat: Infinity }}
/>
</div>
<motion.button
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-[#FF5252]" />
</motion.button>
</Card>
))}
</div>
</motion.div>
)}
</div>
{offlineFriends.length > 0 && (
<motion.div className="flex flex-col gap-2" variants={item}>
<h2 className="text-sm font-black text-text-muted">
غير متصل ({offlineFriends.length})
</h2>
<div className="flex flex-col gap-2">
{offlineFriends.map((friend) => (
<Card key={friend.id} className="!p-3.5 flex items-center gap-3 opacity-70">
<div className="relative">
<div className="w-10 h-10 rounded-full bg-surface-3 border-3 border-border flex items-center justify-center">
<div className="flex-1 min-w-0">
<p className="text-sm font-black truncate">
{friend.profile.display_name || friend.profile.username}
</p>
<p className="text-xs text-text-muted font-bold">{friend.profile.elo_blitz}</p>
</div>
<motion.button
className="w-9 h-9 rounded-xl bg-[#FF5252]/10 border-2 border-[#FF5252]/25 flex items-center justify-center"
whileTap={{ scale: 0.8 }}
onClick={() => removeFriend(friend.id)}
>
<UserX size={14} className="text-[#FF5252]" />
</motion.button>
</div>
))}
</div>
</motion.div>
)}
{/* Offline Friends */}
{offlineFriends.length > 0 && (
<motion.div className="flex flex-col gap-2.5" variants={item}>
<h2 className="text-sm font-black text-text-muted flex items-center gap-2">
<div className="w-1.5 h-4 rounded-full bg-text-muted/40" />
غير متصل ({offlineFriends.length})
</h2>
<div className="flex flex-col gap-2.5">
{offlineFriends.map((friend) => (
<div key={friend.id} className="game-panel !p-3 flex items-center gap-3 opacity-60 relative overflow-hidden">
{/* Gray accent stripe */}
<div className="absolute right-0 top-0 bottom-0 w-[4px] bg-text-muted/30 rounded-r-full" />
<div className="relative mr-1">
<div className="w-11 h-11 flex items-center justify-center"
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)' }}
>
<div className="w-full h-full bg-surface-3 flex items-center justify-center"
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)' }}
>
<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.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-bold truncate">
{friend.profile.display_name || friend.profile.username}
</p>
<p className="text-xs text-text-muted">
آخر ظهور {relativeTime(friend.profile.last_seen_at)}
</p>
</div>
<motion.button
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-[#FF5252]" />
</motion.button>
</Card>
))}
</div>
</motion.div>
)}
<div className="absolute -bottom-0.5 -left-0.5 w-4 h-4 rounded-full bg-text-muted/40 border-2 border-surface-1" />
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-bold truncate">
{friend.profile.display_name || friend.profile.username}
</p>
<p className="text-xs text-text-muted">
آخر ظهور {relativeTime(friend.profile.last_seen_at)}
</p>
</div>
<motion.button
className="w-9 h-9 rounded-xl bg-[#FF5252]/10 border-2 border-[#FF5252]/20 flex items-center justify-center"
whileTap={{ scale: 0.8 }}
onClick={() => removeFriend(friend.id)}
>
<UserX size={14} className="text-[#FF5252]" />
</motion.button>
</div>
))}
</div>
</motion.div>
)}
{friends.length === 0 && pendingReceived.length === 0 && (
{/* Empty State */}
{friends.length === 0 && pendingReceived.length === 0 && (
<motion.div
className="flex flex-col items-center justify-center py-16 gap-5"
variants={item}
>
<motion.div
className="flex flex-col items-center justify-center py-16 gap-4"
variants={item}
className="w-24 h-24 flex items-center justify-center"
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)' }}
initial={{ scale: 0, rotate: -15 }}
animate={{ scale: 1, rotate: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 18 }}
>
<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 }}
<div className="w-full h-full bg-gradient-to-br from-surface-2 to-surface-3 border-3 border-border flex items-center justify-center"
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)' }}
>
<UserPlus size={32} className="text-text-muted" />
</motion.div>
<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>
<Shield size={36} className="text-text-muted" />
</div>
</motion.div>
)}
</motion.div>
)}
</div>
<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.5">ابحث عن لاعبين لاضافتهم</p>
</div>
</motion.div>
)}
</motion.div>
)}
</PageTransition>
)
}
......@@ -4,7 +4,6 @@ import { Play, TrendingUp, Swords, Flame, Bot, Users, Lightbulb, Crown } from 'l
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 { playSound } from '../lib/sounds'
......@@ -22,13 +21,13 @@ const dailyTips = [
const stagger = {
hidden: {},
show: {
transition: { staggerChildren: 0.06 },
transition: { staggerChildren: 0.08 },
},
}
const fadeUp = {
hidden: { opacity: 0, y: 20 },
show: { opacity: 1, y: 0, transition: { type: 'spring', stiffness: 500, damping: 22 } },
hidden: { opacity: 0, y: 24 },
show: { opacity: 1, y: 0, transition: { type: 'spring', stiffness: 400, damping: 24 } },
}
export function HomePage() {
......@@ -60,155 +59,266 @@ export function HomePage() {
variants={stagger}
initial="hidden"
animate="show"
className="flex flex-col gap-6"
className="flex flex-col gap-7"
>
{/* === 1. PLAYER NAMEPLATE — Angled Banner === */}
{profile && (
<motion.div
variants={fadeUp}
className="flex items-center gap-4"
>
<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-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">
<motion.div variants={fadeUp} className="relative">
<div
className="relative flex items-center gap-4 px-5 py-4"
style={{
clipPath: 'polygon(3% 0%, 100% 0%, 97% 50%, 100% 100%, 3% 100%, 0% 50%)',
background: 'linear-gradient(135deg, rgba(255,200,60,0.12) 0%, rgba(180,77,255,0.12) 100%)',
}}
>
{/* Decorative left accent */}
<div
className="absolute right-0 top-0 bottom-0 w-[3px]"
style={{ background: 'linear-gradient(to bottom, #FFC83D, #B44DFF)' }}
/>
{/* Hexagonal Avatar Shield */}
<div className="relative flex-shrink-0">
<div
className="w-14 h-14 flex items-center justify-center border-[3px] border-gold"
style={{
clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)',
background: 'linear-gradient(135deg, rgba(255,200,60,0.2), rgba(255,200,60,0.05))',
}}
>
<span className="text-xl font-black text-gold">
{profile.display_name?.charAt(0) || 'L'}
</span>
</div>
</div>
{/* Name + Level */}
<div className="flex-1">
<h2 className="text-xl font-black text-text-primary leading-tight">
{profile.display_name}
</h2>
<span className="inline-block mt-1 px-2 py-0.5 rounded-md bg-purple/20 border border-purple/40 text-[10px] font-bold text-purple">
المستوى {profile.level}
</span>
</div>
{/* Decorative left accent (RTL mirror) */}
<div
className="absolute left-0 top-0 bottom-0 w-[3px]"
style={{ background: 'linear-gradient(to bottom, #B44DFF, #FFC83D)' }}
/>
</div>
</motion.div>
)}
{/* === 2. PLAY BUTTON — The Hero Crest === */}
<motion.div variants={fadeUp} className="flex justify-center">
<motion.button
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 }}
className="relative w-[85%] cursor-pointer"
whileTap={{ y: 4, scale: 0.97 }}
transition={{ type: 'spring', stiffness: 600, damping: 20 }}
>
<motion.div
className="absolute inset-0 rounded-[22px] border-[3px] border-gold/60 animate-pulse-glow"
{/* Shadow layer (3D depth) */}
<div
className="absolute inset-0 top-[6px]"
style={{
clipPath: 'polygon(8% 0%, 92% 0%, 100% 50%, 92% 100%, 8% 100%, 0% 50%)',
background: '#9B6B1A',
}}
/>
{/* Main body */}
<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">
className="relative overflow-hidden py-10"
style={{
clipPath: 'polygon(8% 0%, 92% 0%, 100% 50%, 92% 100%, 8% 100%, 0% 50%)',
background: 'linear-gradient(180deg, #FFE066 0%, #FFC83D 60%, #C9972E 100%)',
borderBottom: '4px solid #9B6B1A',
}}
animate={{ scale: [1, 1.015, 1] }}
transition={{ duration: 2.2, repeat: Infinity, ease: 'easeInOut' }}
>
{/* Animated shimmer */}
<motion.div
animate={{ scale: [1, 1.12, 1] }}
transition={{ duration: 1.8, repeat: Infinity, ease: 'easeInOut' }}
>
<Play size={48} className="text-background" fill="currentColor" />
</motion.div>
<span className="text-3xl font-black text-background uppercase tracking-wide">العب الان</span>
</div>
className="absolute inset-0"
style={{
background: 'linear-gradient(105deg, transparent 30%, rgba(255,255,255,0.3) 50%, transparent 70%)',
}}
animate={{ x: ['-100%', '200%'] }}
transition={{ duration: 2.8, repeat: Infinity, ease: 'linear' }}
/>
{/* Content */}
<div className="relative flex flex-col items-center gap-2">
<motion.div
animate={{ scale: [1, 1.1, 1] }}
transition={{ duration: 1.6, repeat: Infinity, ease: 'easeInOut' }}
>
<Play size={52} className="text-background" fill="currentColor" />
</motion.div>
<span className="text-3xl font-black text-background tracking-wide">
العب الان
</span>
</div>
</motion.div>
</motion.button>
</motion.div>
{/* === 3. STATS AS SHIELD BADGES === */}
{profile && (
<motion.div variants={fadeUp} className="grid grid-cols-3 gap-3">
<StatCard
icon={<TrendingUp size={22} className="text-gold" />}
<motion.div variants={fadeUp} className="relative flex justify-center items-center gap-4 py-3">
{/* Decorative connecting line */}
<div className="absolute top-1/2 left-[15%] right-[15%] h-[2px] bg-gradient-to-r from-transparent via-border to-transparent -translate-y-1/2" />
<StatBadge
icon={<TrendingUp size={18} className="text-gold" />}
value={profile.elo_blitz}
label="تقييم"
accent="gold"
gradient="linear-gradient(135deg, rgba(255,200,60,0.15), rgba(255,200,60,0.05))"
borderColor="#FFC83D"
/>
<StatCard
icon={<Swords size={22} className="text-cyan" />}
<StatBadge
icon={<Swords size={18} className="text-cyan" />}
value={profile.total_games_played}
label="مباراة"
accent="cyan"
gradient="linear-gradient(135deg, rgba(0,229,204,0.15), rgba(0,229,204,0.05))"
borderColor="#00E5CC"
isCenter
/>
<StatCard
icon={<Flame size={22} className="text-coral" />}
<StatBadge
icon={<Flame size={18} className="text-coral" />}
value={profile.win_streak}
label="سلسلة فوز"
accent="coral"
gradient="linear-gradient(135deg, rgba(255,82,82,0.15), rgba(255,82,82,0.05))"
borderColor="#FF5252"
/>
</motion.div>
)}
<motion.div variants={fadeUp} className="grid grid-cols-2 gap-3">
<Card
variant="default"
className="flex flex-col items-center gap-3 !p-5"
{/* === 4. QUICK ACTIONS — Chunky 3D Game Tiles === */}
<motion.div variants={fadeUp} className="grid grid-cols-2 gap-4 px-2">
<motion.button
className="relative cursor-pointer"
style={{ transform: 'rotate(-1deg)' }}
whileTap={{ y: 4, boxShadow: 'none' }}
onClick={() => {
playSound('click')
navigate('/bot-select')
}}
>
<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-black">العب ضد روبوت</p>
<p className="text-[10px] text-text-muted mt-1">تدريب وتحسين</p>
<div className="relative flex flex-col items-center gap-3 p-5 rounded-2xl bg-surface-1 border-3 border-border"
style={{ boxShadow: '0 6px 0 0 rgba(61,69,112,0.6)' }}
>
<div
className="w-12 h-12 rounded-full flex items-center justify-center"
style={{ background: 'linear-gradient(135deg, rgba(180,77,255,0.25), rgba(180,77,255,0.08))' }}
>
<Bot size={26} className="text-purple" />
</div>
<div className="text-center">
<p className="text-sm font-black text-text-primary">العب ضد روبوت</p>
<p className="text-[10px] text-text-muted mt-0.5">تدريب وتحسين</p>
</div>
</div>
</Card>
</motion.button>
<Card
variant="default"
className="flex flex-col items-center gap-3 !p-5"
<motion.button
className="relative cursor-pointer"
style={{ transform: 'rotate(1deg)' }}
whileTap={{ y: 4, boxShadow: 'none' }}
onClick={() => {
playSound('click')
navigate('/friends')
}}
>
<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-black">تحدى صديق</p>
<p className="text-[10px] text-text-muted mt-1">ارسل دعوة</p>
<div className="relative flex flex-col items-center gap-3 p-5 rounded-2xl bg-surface-1 border-3 border-border"
style={{ boxShadow: '0 6px 0 0 rgba(61,69,112,0.6)' }}
>
<div
className="w-12 h-12 rounded-full flex items-center justify-center"
style={{ background: 'linear-gradient(135deg, rgba(255,200,60,0.25), rgba(255,200,60,0.08))' }}
>
<Users size={26} className="text-gold" />
</div>
<div className="text-center">
<p className="text-sm font-black text-text-primary">تحدى صديق</p>
<p className="text-[10px] text-text-muted mt-0.5">ارسل دعوة</p>
</div>
</div>
</Card>
</motion.button>
</motion.div>
<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-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-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>
{/* === 5. DAILY TIP — Parchment/Scroll Style === */}
<motion.div variants={fadeUp} className="relative overflow-hidden rounded-2xl border-2 border-gold/20 p-5"
style={{
background: 'linear-gradient(160deg, rgba(30,35,64,1) 0%, rgba(40,35,30,0.4) 100%)',
}}
>
{/* Watermark crown */}
<div className="absolute bottom-2 left-3 opacity-[0.06]">
<Crown size={64} className="text-gold" />
</div>
{/* Ribbon header */}
<div className="inline-block mb-3">
<div
className="px-4 py-1 text-[11px] font-black text-background"
style={{
clipPath: 'polygon(0% 0%, 92% 0%, 100% 50%, 92% 100%, 0% 100%, 4% 50%)',
background: 'linear-gradient(90deg, #FFC83D, #FFE066)',
}}
>
نصيحة اليوم
</div>
</Card>
</div>
{/* Tip content */}
<div className="flex items-start gap-3">
<div className="w-9 h-9 flex-shrink-0 rounded-lg bg-gold/10 border border-gold/25 flex items-center justify-center">
<Lightbulb size={18} className="text-gold" />
</div>
<p className="text-[12px] text-text-secondary leading-[2] pt-1.5">
{todayTip}
</p>
</div>
</motion.div>
</motion.div>
</PageTransition>
)
}
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',
}
/* --- Hexagonal Stat Badge --- */
function StatBadge({
icon,
value,
label,
gradient,
borderColor,
isCenter = false,
}: {
icon: React.ReactNode
value: number
label: string
gradient: string
borderColor: string
isCenter?: boolean
}) {
return (
<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' }}
className="relative flex flex-col items-center justify-center z-10"
style={{ transform: isCenter ? 'scale(1.08)' : 'scale(1)' }}
>
{icon}
<span className="text-2xl font-black">{value}</span>
<span className="text-[10px] text-text-muted font-bold">{label}</span>
<div
className="w-20 h-20 flex flex-col items-center justify-center border-[2.5px]"
style={{
clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)',
background: gradient,
borderColor: borderColor,
}}
>
{icon}
<span className="text-lg font-black text-text-primary mt-0.5">{value}</span>
</div>
<span className="text-[9px] text-text-muted font-bold mt-1">{label}</span>
</div>
)
}
import { useState } from 'react'
import { motion } from 'framer-motion'
import { Crown, Medal, Trophy } from 'lucide-react'
import { Crown, Medal, Trophy, Zap, Clock, Timer, Hourglass } from 'lucide-react'
import { PageTransition } from '../components/layout/PageTransition'
import { useAuthStore } from '../stores/authStore'
import { useLeaderboard } from '../hooks/useLeaderboard'
......@@ -8,11 +8,11 @@ import { useLeaderboard } from '../hooks/useLeaderboard'
type TimeControlType = 'bullet' | 'blitz' | 'rapid' | 'classical'
type Period = 'weekly' | 'monthly' | 'all_time'
const TIME_CONTROLS: { label: string; value: TimeControlType }[] = [
{ label: 'رصاصة', value: 'bullet' },
{ label: 'خاطف', value: 'blitz' },
{ label: 'سريع', value: 'rapid' },
{ label: 'كلاسيكي', value: 'classical' },
const TIME_CONTROLS: { label: string; value: TimeControlType; icon: React.ReactNode }[] = [
{ label: 'رصاصة', value: 'bullet', icon: <Zap size={14} /> },
{ label: 'خاطف', value: 'blitz', icon: <Timer size={14} /> },
{ label: 'سريع', value: 'rapid', icon: <Clock size={14} /> },
{ label: 'كلاسيكي', value: 'classical', icon: <Hourglass size={14} /> },
]
const PERIODS: { label: string; value: Period }[] = [
......@@ -21,23 +21,45 @@ const PERIODS: { label: string; value: Period }[] = [
{ label: 'الكل', value: 'all_time' },
]
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 }}
/>
)
}
const HEX_CLIP = 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)'
function HexAvatar({ name, url, size = 44 }: { name: string; url?: string | null; size?: number }) {
return (
<div
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 }}
className="relative flex items-center justify-center"
style={{ width: size, height: size }}
>
{name?.charAt(0) || '?'}
{/* Gold hex border */}
<div
className="absolute inset-0"
style={{
clipPath: HEX_CLIP,
background: 'linear-gradient(135deg, #FFC83D, #B44DFF)',
}}
/>
{/* Inner content */}
<div
className="flex items-center justify-center"
style={{
width: size - 4,
height: size - 4,
clipPath: HEX_CLIP,
background: url ? 'transparent' : 'linear-gradient(135deg, var(--color-surface-2), var(--color-surface-3))',
}}
>
{url ? (
<img
src={url}
alt={name}
className="w-full h-full object-cover"
style={{ clipPath: HEX_CLIP }}
/>
) : (
<span className="font-black text-text-secondary" style={{ fontSize: size * 0.35 }}>
{name?.charAt(0) || '?'}
</span>
)}
</div>
</div>
)
}
......@@ -53,47 +75,97 @@ export function LeaderboardPage() {
return (
<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>
{/* === PAGE HEADER === */}
<motion.div
className="relative flex items-center justify-center py-3"
style={{
clipPath: 'polygon(5% 0%, 95% 0%, 100% 100%, 0% 100%)',
background: 'linear-gradient(to bottom, rgba(255,200,60,0.15), transparent)',
borderBottom: '2px solid rgba(255,200,60,0.2)',
}}
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 25 }}
>
<Trophy size={28} className="text-[#FFC83D] ml-2.5" />
<h1 className="text-2xl font-black tracking-tight">لوحة المتصدرين</h1>
</motion.div>
<div className="flex gap-2 overflow-x-auto no-scrollbar pb-1">
{TIME_CONTROLS.map((tc) => (
<motion.button
key={tc.value}
whileTap={{ scale: 0.93 }}
onClick={() => setTimeControl(tc.value)}
className={`px-4 py-2 rounded-xl text-xs font-bold whitespace-nowrap border-2 transition-colors ${
timeControl === tc.value
? '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>
))}
</div>
{/* === FILTER TABS (Trapezoidal) === */}
<motion.div
className="flex gap-2 overflow-x-auto no-scrollbar pb-1"
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.1 }}
>
{TIME_CONTROLS.map((tc) => {
const isActive = timeControl === tc.value
return (
<motion.button
key={tc.value}
whileTap={{ scale: 0.93 }}
onClick={() => setTimeControl(tc.value)}
className="relative flex items-center gap-1.5 px-4 py-2.5 text-xs font-black whitespace-nowrap transition-all"
style={{
clipPath: 'polygon(8% 0%, 92% 0%, 100% 100%, 0% 100%)',
background: isActive
? 'linear-gradient(to bottom, #FFC83D, #E0A800)'
: 'var(--color-surface-2)',
color: isActive ? '#0B0E1A' : 'var(--color-text-muted)',
boxShadow: isActive
? '0 4px 12px rgba(255,200,60,0.35), inset 0 1px 0 rgba(255,255,255,0.2)'
: 'inset 0 2px 4px rgba(0,0,0,0.3)',
minWidth: 72,
}}
transition={{ type: 'spring', stiffness: 500, damping: 20 }}
>
{tc.icon}
{tc.label}
{isActive && (
<motion.div
className="absolute -bottom-1.5 left-1/2 -translate-x-1/2 w-0 h-0"
style={{
borderLeft: '5px solid transparent',
borderRight: '5px solid transparent',
borderTop: '5px solid #FFC83D',
}}
layoutId="tabIndicator"
/>
)}
</motion.button>
)
})}
</motion.div>
<div className="flex gap-2 overflow-x-auto no-scrollbar">
{PERIODS.map((p) => (
<motion.button
key={p.value}
whileTap={{ scale: 0.93 }}
onClick={() => setPeriod(p.value)}
className={`px-3.5 py-1.5 rounded-xl text-[11px] font-bold whitespace-nowrap border-2 transition-colors ${
period === p.value
? '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>
))}
</div>
{/* === PERIOD SELECTOR === */}
<motion.div
className="flex gap-2"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.15 }}
>
{PERIODS.map((p) => {
const isActive = period === p.value
return (
<motion.button
key={p.value}
whileTap={{ scale: 0.93 }}
onClick={() => setPeriod(p.value)}
className="px-4 py-1.5 rounded-full text-[11px] font-bold whitespace-nowrap transition-all"
style={{
background: isActive ? 'rgba(180,77,255,0.15)' : 'var(--color-surface-1)',
color: isActive ? '#B44DFF' : 'var(--color-text-muted)',
border: isActive ? '2px solid rgba(180,77,255,0.5)' : '2px solid var(--color-border)',
boxShadow: isActive ? 'none' : 'inset 0 1px 3px rgba(0,0,0,0.2)',
}}
>
{p.label}
</motion.button>
)
})}
</motion.div>
{/* === CONTENT === */}
{loading ? (
<div className="flex flex-col items-center py-16">
<motion.div
......@@ -105,7 +177,12 @@ export function LeaderboardPage() {
) : entries.length === 0 ? (
<div className="flex flex-col items-center justify-center py-16 gap-4">
<motion.div
className="w-20 h-20 rounded-2xl bg-surface-2 border-3 border-[#FFC83D]/30 flex items-center justify-center"
className="w-20 h-20 flex items-center justify-center"
style={{
clipPath: HEX_CLIP,
background: 'linear-gradient(135deg, rgba(255,200,60,0.1), var(--color-surface-2))',
border: '3px solid rgba(255,200,60,0.3)',
}}
initial={{ scale: 0, rotate: -10 }}
animate={{ scale: 1, rotate: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 18 }}
......@@ -117,41 +194,108 @@ export function LeaderboardPage() {
</div>
) : (
<>
{/* === THE PODIUM === */}
{top3.length > 0 && <Podium entries={top3} currentUserId={user?.id} />}
<div className="flex flex-col gap-2.5">
{rest.map((entry, i) => (
<motion.div
key={entry.player_id}
initial={{ opacity: 0, x: -12 }}
animate={{ opacity: 1, x: 0 }}
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-8 text-center text-sm font-black text-text-muted">
{entry.rank}
</span>
<Avatar name={entry.display_name} url={entry.avatar_url} size={38} />
<div className="flex-1 min-w-0">
<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-black text-[#FFC83D]">{entry.rating}</span>
</motion.div>
))}
</div>
{/* === PLAYER LIST === */}
{rest.length > 0 && (
<div className="flex flex-col gap-0">
{rest.map((entry, i) => {
const isMe = entry.player_id === user?.id
const rankColor = i < 7 ? getRankColor(entry.rank) : 'var(--color-text-muted)'
return (
<div key={entry.player_id}>
<motion.div
initial={{ opacity: 0, x: -12 }}
animate={{ opacity: 1, x: 0 }}
transition={{ type: 'spring', stiffness: 400, damping: 24, delay: 0.3 + i * 0.03 }}
className="flex items-center gap-3 py-3 px-3"
style={{
borderRadius: 14,
border: isMe ? '2px solid rgba(255,200,60,0.5)' : '2px solid transparent',
background: isMe ? 'rgba(255,200,60,0.06)' : 'transparent',
boxShadow: isMe ? '0 0 16px rgba(255,200,60,0.12)' : 'none',
}}
>
{/* Rank shield */}
{entry.rank <= 10 ? (
<div
className="flex items-center justify-center flex-shrink-0"
style={{
width: 24,
height: 28,
clipPath: 'polygon(50% 0%, 100% 20%, 100% 80%, 50% 100%, 0% 80%, 0% 20%)',
background: `linear-gradient(to bottom, ${rankColor}30, ${rankColor}10)`,
border: `2px solid ${rankColor}`,
}}
>
<span className="text-[10px] font-black" style={{ color: rankColor }}>{entry.rank}</span>
</div>
) : (
<div
className="flex items-center justify-center flex-shrink-0 rounded-full"
style={{
width: 24,
height: 24,
background: 'var(--color-surface-3)',
border: '2px solid var(--color-border)',
}}
>
<span className="text-[10px] font-bold text-text-muted">{entry.rank}</span>
</div>
)}
{/* Hex avatar */}
<HexAvatar name={entry.display_name} url={entry.avatar_url} size={36} />
{/* Name + games */}
<div className="flex-1 min-w-0">
<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>
{/* Rating capsule */}
<div
className="px-3 py-1 rounded-full flex-shrink-0"
style={{
background: `linear-gradient(135deg, ${rankColor}15, ${rankColor}08)`,
border: `2px solid ${rankColor}40`,
boxShadow: `0 0 8px ${rankColor}10`,
}}
>
<span className="text-xs font-black" style={{ color: rankColor }}>{entry.rating}</span>
</div>
</motion.div>
{/* Decorative separator */}
{i < rest.length - 1 && (
<div
className="mx-8 h-[1px]"
style={{
background: 'linear-gradient(to left, transparent, var(--color-border), transparent)',
}}
/>
)}
</div>
)
})}
</div>
)}
</>
)}
</PageTransition>
)
}
function getRankColor(rank: number): string {
if (rank <= 3) return '#FFC83D'
if (rank <= 5) return '#00E5CC'
if (rank <= 7) return '#B44DFF'
if (rank <= 10) return '#4D8BFF'
return '#6E748C'
}
function Podium({
entries,
currentUserId,
......@@ -159,84 +303,135 @@ function Podium({
entries: { rank: number; player_id: string; rating: number; games_played: number; display_name: string; avatar_url?: string | null }[]
currentUserId?: string
}) {
// Order: 2nd | 1st | 3rd (RTL: right to left visually)
const ordered = [entries[1], entries[0], entries[2]].filter(Boolean)
const podiumConfig = [
{
color: '#C0C0C0',
height: 'h-24',
avatarSize: 52,
rankIcon: <Medal size={16} className="text-[#C0C0C0]" />,
podiumHeight: 90,
avatarSize: 64,
rankIcon: <Medal size={20} className="text-[#C0C0C0]" />,
label: '2',
},
{
color: '#FFC83D',
height: 'h-32',
avatarSize: 68,
rankIcon: <Crown size={22} className="text-[#FFC83D] fill-[#FFC83D]/20" />,
podiumHeight: 120,
avatarSize: 80,
rankIcon: <Crown size={26} className="text-[#FFC83D] animate-float" />,
label: '1',
},
{
color: '#CD7F32',
height: 'h-20',
avatarSize: 48,
rankIcon: <Medal size={16} className="text-[#CD7F32]" />,
podiumHeight: 70,
avatarSize: 64,
rankIcon: <Medal size={20} className="text-[#CD7F32]" />,
label: '3',
},
]
return (
<div className="flex items-end justify-center gap-3 py-4">
<motion.div
className="relative flex items-end justify-center gap-2 pt-8 pb-2"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.2 }}
>
{/* Spotlight radial glow */}
<div
className="absolute inset-0 pointer-events-none"
style={{
background: 'radial-gradient(ellipse at 50% 80%, rgba(255,200,60,0.08) 0%, transparent 60%)',
}}
/>
{ordered.map((entry, i) => {
if (!entry) return null
const config = podiumConfig[i]
const isMe = entry.player_id === currentUserId
const isFirst = i === 1
return (
<motion.div
key={entry.player_id}
className="flex flex-col items-center gap-2"
initial={{ scale: 0, opacity: 0, y: 30 }}
className="relative flex flex-col items-center"
style={{ flex: 1, maxWidth: isFirst ? 130 : 110 }}
initial={{ scale: 0, opacity: 0, y: 40 }}
animate={{ scale: 1, opacity: 1, y: 0 }}
transition={{ type: 'spring', stiffness: 350, damping: 20, delay: 0.1 + i * 0.12 }}
transition={{ type: 'spring', stiffness: 350, damping: 20, delay: 0.15 + i * 0.12 }}
>
{/* Crown / Medal above avatar */}
<motion.div
className="mb-1"
initial={{ scale: 0, rotate: -20 }}
animate={{ scale: 1, rotate: 0 }}
transition={{ delay: 0.4 + i * 0.1, type: 'spring', stiffness: 400, damping: 15 }}
transition={{ delay: 0.5 + i * 0.1, type: 'spring', stiffness: 400, damping: 15 }}
>
{config.rankIcon}
{isFirst ? (
<div className="relative">
{config.rankIcon}
{/* Gold glow for first place */}
<div
className="absolute inset-0 animate-pulse-glow rounded-full"
style={{ filter: 'blur(6px)', background: 'rgba(255,200,60,0.3)' }}
/>
</div>
) : (
config.rankIcon
)}
</motion.div>
{/* Hex Avatar */}
<div className={`relative ${isMe ? 'ring-2 ring-[#FFC83D]/50 rounded-full' : ''}`}>
<HexAvatar name={entry.display_name} url={entry.avatar_url} size={config.avatarSize} />
</div>
{/* Name */}
<p className="text-xs font-black truncate max-w-[80px] text-center mt-1.5">{entry.display_name}</p>
{/* Rating capsule */}
<div
className={`rounded-full border-3 flex items-center justify-center font-black ${isMe ? 'ring-3 ring-[#FFC83D]/40' : ''}`}
className="mt-1 px-3 py-0.5 rounded-full"
style={{
width: config.avatarSize,
height: config.avatarSize,
borderColor: config.color,
backgroundColor: `${config.color}15`,
color: config.color,
fontSize: config.avatarSize * 0.35,
background: `linear-gradient(135deg, ${config.color}25, ${config.color}10)`,
border: `2px solid ${config.color}60`,
}}
>
{entry.display_name?.charAt(0) || '?'}
</div>
<div className="text-center">
<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>
<span className="text-sm font-black" style={{ color: config.color }}>{entry.rating}</span>
</div>
{/* 3D Podium step */}
<div
className={`w-full ${config.height} rounded-t-xl border-3 border-b-0`}
className="w-full mt-2 relative overflow-hidden"
style={{
borderColor: config.color,
background: `linear-gradient(to top, ${config.color}20, transparent)`,
height: config.podiumHeight,
borderRadius: '12px 12px 0 0',
border: `3px solid ${config.color}`,
borderBottom: `6px solid ${config.color}80`,
background: `linear-gradient(to bottom, ${config.color}20 0%, ${config.color}08 50%, ${config.color}02 100%)`,
boxShadow: `inset 0 2px 8px ${config.color}15, 0 4px 12px rgba(0,0,0,0.3)`,
}}
/>
>
{/* Inner gradient lit from top */}
<div
className="absolute inset-0"
style={{
background: `linear-gradient(to bottom, ${config.color}12, transparent 60%)`,
}}
/>
{/* Rank number centered */}
<div className="absolute inset-0 flex items-center justify-center">
<span
className="text-3xl font-black opacity-20"
style={{ color: config.color }}
>
{config.label}
</span>
</div>
</div>
</motion.div>
)
})}
</div>
</motion.div>
)
}
import { useEffect } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { useNavigate, useSearchParams } from 'react-router-dom'
import { Button } from '../components/ui/Button'
import { useMatchmaking } from '../hooks/useMatchmaking'
import { useMatchStore } from '../stores/matchStore'
import type { TIME_CONTROLS } from '../lib/constants'
......@@ -36,7 +35,9 @@ export function MatchmakingPage() {
}
return (
<div className="flex-1 flex flex-col items-center justify-center px-6 py-12 relative overflow-hidden min-h-dvh bg-background">
<div className="flex-1 flex flex-col items-center justify-center px-6 py-12 relative overflow-hidden min-h-dvh"
style={{ background: 'radial-gradient(ellipse at center, rgba(255,200,61,0.08) 0%, transparent 60%), var(--color-background)' }}
>
<AnimatePresence mode="wait">
{matchFound ? (
<motion.div
......@@ -46,15 +47,63 @@ export function MatchmakingPage() {
animate={{ scale: 1, opacity: 1 }}
transition={{ type: 'spring', stiffness: 300, damping: 15 }}
>
<motion.div
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-5xl font-black text-gold">VS</span>
</motion.div>
{/* VS Badge - hex/shield shape with gold border */}
<div className="relative">
<motion.div
className="w-32 h-32 flex items-center justify-center"
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)' }}
animate={{ scale: [1, 1.08, 1] }}
transition={{ duration: 1.2, repeat: Infinity }}
>
<div className="w-full h-full bg-gradient-to-br from-[#FFC83D]/30 to-[#FFC83D]/10 border-3 border-[#FFC83D] flex items-center justify-center"
style={{
clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)',
boxShadow: '0 0 30px rgba(255,200,61,0.4)',
}}
>
<span className="text-5xl font-black text-[#FFC83D]">VS</span>
</div>
</motion.div>
{/* Pulsing glow ring */}
<motion.div
className="absolute inset-[-8px]"
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)' }}
animate={{ opacity: [0.3, 0.7, 0.3], scale: [0.95, 1.05, 0.95] }}
transition={{ duration: 1.5, repeat: Infinity }}
>
<div className="w-full h-full border-2 border-[#FFC83D]/40"
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)' }}
/>
</motion.div>
{/* Sparkle particles */}
{[...Array(8)].map((_, i) => (
<motion.div
key={i}
className="absolute w-2 h-2 rounded-full bg-[#FFC83D]"
style={{
top: '50%',
left: '50%',
}}
animate={{
x: [0, Math.cos((i * Math.PI * 2) / 8) * 80],
y: [0, Math.sin((i * Math.PI * 2) / 8) * 80],
opacity: [1, 0],
scale: [1, 0.3],
}}
transition={{
duration: 1.2,
repeat: Infinity,
delay: i * 0.15,
ease: 'easeOut',
}}
/>
))}
</div>
<motion.h2
className="mt-6 text-2xl font-black text-gold"
className="mt-8 text-2xl font-black text-[#FFC83D]"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
......@@ -68,31 +117,58 @@ export function MatchmakingPage() {
className="flex flex-col items-center"
exit={{ opacity: 0, scale: 0.8 }}
>
<div className="relative w-44 h-44 flex items-center justify-center">
{/* Hexagonal radar area */}
<div className="relative w-48 h-48 flex items-center justify-center">
{/* Concentric hexagonal rings pulsing outward */}
{[0, 1, 2].map((i) => (
<motion.div
key={i}
className="absolute inset-0 rounded-full border-3 border-gold/30"
initial={{ scale: 0.5, opacity: 0.8 }}
animate={{ scale: 2.5, opacity: 0 }}
className="absolute inset-0 flex items-center justify-center"
initial={{ scale: 0.4, opacity: 0.8 }}
animate={{ scale: [0.5 + i * 0.2, 1.4 + i * 0.3], opacity: [0.6, 0] }}
transition={{
duration: 2.5,
repeat: Infinity,
delay: i * 0.8,
ease: 'easeOut',
}}
/>
>
<div className="w-full h-full border-2 border-[#FFC83D]/30"
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)' }}
/>
</motion.div>
))}
{/* Rotating sweep line */}
<motion.div
className="absolute inset-0 flex items-center justify-center"
animate={{ rotate: 360 }}
transition={{ duration: 3, repeat: Infinity, ease: 'linear' }}
>
<div className="w-[2px] h-1/2 origin-bottom bg-gradient-to-t from-[#FFC83D]/60 to-transparent absolute top-0" />
</motion.div>
{/* Static outer hex border */}
<div className="absolute inset-2 border-3 border-[#FFC83D]/20"
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)' }}
/>
{/* Center "?" in shield shape */}
<motion.div
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] }}
className="w-20 h-20 flex items-center justify-center z-10"
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)' }}
animate={{ scale: [1, 1.06, 1], rotate: [0, 2, -2, 0] }}
transition={{ duration: 2, repeat: Infinity, ease: 'easeInOut' }}
>
<span className="text-4xl font-black text-gold">?</span>
<div className="w-full h-full bg-gradient-to-br from-[#FFC83D]/25 to-surface-2 border-3 border-[#FFC83D]/50 flex items-center justify-center"
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)' }}
>
<span className="text-4xl font-black text-[#FFC83D]">?</span>
</div>
</motion.div>
</div>
{/* Title */}
<motion.h2
className="mt-8 text-xl font-black"
initial={{ opacity: 0 }}
......@@ -102,17 +178,21 @@ export function MatchmakingPage() {
جاري البحث عن خصم
</motion.h2>
{/* Timer in a game-panel mini-bar */}
<motion.div
className="mt-3 text-2xl font-black font-mono text-gold tabular-nums"
className="mt-4 game-panel !py-2 !px-6 inline-flex items-center justify-center"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.4 }}
>
{formatElapsed(elapsed)}
<span className="text-2xl font-black font-mono text-[#FFC83D] tabular-nums">
{formatElapsed(elapsed)}
</span>
</motion.div>
{/* Bouncing dots */}
<motion.div
className="mt-3 flex gap-1.5"
className="mt-4 flex gap-2"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.5 }}
......@@ -120,22 +200,28 @@ export function MatchmakingPage() {
{[0, 1, 2].map((i) => (
<motion.span
key={i}
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 }}
className="w-3 h-3 rounded-full bg-[#FFC83D]"
animate={{ y: [0, -8, 0], opacity: [0.4, 1, 0.4] }}
transition={{ duration: 1, repeat: Infinity, delay: i * 0.2 }}
/>
))}
</motion.div>
{/* Cancel - 3D ghost button */}
<motion.div
className="mt-12"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1 }}
>
<Button variant="ghost" onClick={handleCancel} size="md">
<motion.button
whileTap={{ scale: 0.93 }}
onClick={handleCancel}
className="btn-3d px-8 py-3 rounded-xl bg-surface-2 border-3 border-border text-text-muted text-sm font-black"
style={{ boxShadow: '0 4px 0 rgba(0,0,0,0.3)' }}
>
الغاء
</Button>
</motion.button>
</motion.div>
</motion.div>
)}
......
import { useState } from 'react'
import { motion } from 'framer-motion'
import { Zap, Timer, Clock, Hourglass, Lock, Cpu, Crown } from 'lucide-react'
import { Zap, Timer, Clock, Hourglass, Lock, Cpu, Crown, Swords } from 'lucide-react'
import { useNavigate } from 'react-router-dom'
import { PageTransition } from '../components/layout/PageTransition'
import { Card } from '../components/ui/Card'
import { Button } from '../components/ui/Button'
import { GAMES, TIME_CONTROLS } from '../lib/constants'
import { playSound } from '../lib/sounds'
......@@ -15,23 +13,16 @@ const CATEGORIES = [
{ key: 'classical', label: 'كلاسيكي', icon: Hourglass },
] as const
const GAME_ICONS: Record<string, string> = {
backgammon: '⚀',
dominoes: '🂡',
ludo: '⚄',
trivia: '?',
}
const stagger = {
hidden: {},
show: {
transition: { staggerChildren: 0.06 },
transition: { staggerChildren: 0.07 },
},
}
const fadeUp = {
hidden: { opacity: 0, y: 20 },
show: { opacity: 1, y: 0, transition: { type: 'spring', stiffness: 500, damping: 22 } },
hidden: { opacity: 0, y: 24 },
show: { opacity: 1, y: 0, transition: { type: 'spring', stiffness: 400, damping: 24 } },
}
export function PlayPage() {
......@@ -46,76 +37,160 @@ export function PlayPage() {
)?.[1].category
return (
<PageTransition>
<PageTransition className="!py-6">
<motion.div
variants={stagger}
initial="hidden"
animate="show"
className="flex flex-col gap-6"
>
<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" />
{/* === 1. PAGE HEADER — Ribbon Banner === */}
<motion.div variants={fadeUp} className="flex justify-center">
<div className="relative flex items-center gap-3">
<Swords size={18} className="text-gold/60" style={{ transform: 'scaleX(-1)' }} />
<div
className="px-7 py-2.5"
style={{
clipPath: 'polygon(6% 0%, 94% 0%, 100% 50%, 94% 100%, 6% 100%, 0% 50%)',
background: 'linear-gradient(90deg, rgba(255,200,60,0.2), rgba(255,200,60,0.08))',
}}
>
<h1 className="text-xl font-black text-text-primary">اختر اللعبة</h1>
</div>
<Swords size={18} className="text-gold/60" />
</div>
</motion.div>
{/* === 2. CHESS ARENA CARD — Featured Game === */}
<motion.div variants={fadeUp} className="relative">
{/* Double-border panel */}
<div
className="relative rounded-2xl border-[3px] border-gold/50 overflow-hidden"
style={{
boxShadow: '0 0 24px rgba(255,200,60,0.15), inset 0 0 30px rgba(255,200,60,0.03)',
background: 'linear-gradient(170deg, var(--color-surface-1) 0%, rgba(30,35,64,0.95) 100%)',
}}
>
{/* Chess grid pattern (subtle) */}
<div
className="absolute inset-0 opacity-[0.03]"
style={{
backgroundImage: 'linear-gradient(rgba(255,255,255,0.5) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.5) 1px, transparent 1px)',
backgroundSize: '28px 28px',
}}
/>
{/* FEATURED corner ribbon */}
<div className="absolute top-0 left-0 z-10">
<div
className="px-3 py-1 text-[8px] font-black text-background tracking-wider"
style={{
background: 'linear-gradient(135deg, #FFC83D, #FF8C42)',
transform: 'rotate(-0deg)',
borderBottomRightRadius: '8px',
}}
>
FEATURED
</div>
</div>
{/* Content */}
<div className="relative p-6 flex items-center gap-5">
{/* Crown with radial glow */}
<div className="relative flex-shrink-0">
<div
className="absolute inset-0 rounded-full blur-xl opacity-30"
style={{ background: 'radial-gradient(circle, #FFC83D, transparent 70%)' }}
/>
<div
className="relative w-[68px] h-[68px] rounded-2xl flex items-center justify-center border-[3px] border-gold/40"
style={{ background: 'linear-gradient(135deg, rgba(255,200,60,0.2), rgba(255,200,60,0.05))' }}
>
<Crown size={36} className="text-gold" />
</div>
</div>
<div className="flex-1">
<span className="text-xl font-black">{chessGame.nameAr}</span>
<p className="text-xs text-text-secondary mt-2 leading-relaxed">
<span className="text-xl font-black text-text-primary">{chessGame.nameAr}</span>
<p className="text-xs text-text-secondary mt-1.5 leading-relaxed">
العب شطرنج اونلاين ضد لاعبين حقيقيين
</p>
{/* Online indicator */}
<div className="flex items-center gap-2 mt-2.5">
<motion.div
className="w-2.5 h-2.5 rounded-full bg-green"
animate={{ opacity: [1, 0.4, 1], scale: [1, 1.3, 1] }}
transition={{ duration: 1.8, repeat: Infinity }}
/>
<span className="text-[11px] font-bold text-green">اونلاين</span>
</div>
</div>
</div>
<motion.div
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>
{/* Gold bottom accent */}
<div className="h-[3px] w-full bg-gradient-to-r from-transparent via-gold to-transparent" />
</div>
</motion.div>
<motion.div variants={fadeUp} className="grid grid-cols-2 gap-3">
{/* === 3. OTHER GAMES — Compact Locked Tiles === */}
<motion.div variants={fadeUp} className="grid grid-cols-2 gap-2.5">
{otherGames.map((game) => (
<div
key={game.key}
className="relative rounded-2xl overflow-hidden border-3 border-border bg-surface-1 opacity-50 p-5 flex flex-col items-center gap-3"
className="relative h-[56px] rounded-xl overflow-hidden border-2 border-border/40 flex items-center justify-center"
style={{
background: 'var(--color-surface-1)',
boxShadow: 'inset 0 3px 8px rgba(0,0,0,0.4)',
}}
>
<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-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>
{/* Frosted overlay */}
<div className="absolute inset-0 bg-background/40 backdrop-blur-[1px]" />
{/* Lock badge */}
<div className="relative z-10 flex items-center gap-2">
<div
className="w-6 h-6 flex items-center justify-center"
style={{
clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)',
background: 'rgba(61,69,112,0.6)',
}}
>
<Lock size={10} className="text-text-muted" />
</div>
<div>
<span className="text-[11px] font-bold text-text-muted block">{game.nameAr}</span>
<span className="text-[9px] text-text-muted/60">قريبا</span>
</div>
</div>
</div>
))}
</motion.div>
{/* === 4. TIME CONTROL SECTION === */}
<motion.div variants={fadeUp} className="space-y-4">
<h2 className="text-lg font-black">نظام الوقت</h2>
{/* Section ribbon header */}
<div className="flex justify-center">
<div
className="px-5 py-1.5 text-[11px] font-black text-gold"
style={{
clipPath: 'polygon(4% 0%, 96% 0%, 100% 50%, 96% 100%, 4% 100%, 0% 50%)',
background: 'linear-gradient(90deg, rgba(255,200,60,0.12), rgba(255,200,60,0.05))',
border: '1.5px solid rgba(255,200,60,0.25)',
}}
>
نظام الوقت
</div>
</div>
<div className="flex gap-2 overflow-x-auto scrollbar-hide pb-1">
{/* Category Tabs — Trapezoidal */}
<div className="flex gap-2 justify-center overflow-x-auto scrollbar-hide pb-1">
{CATEGORIES.map((cat) => {
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-black border-2 whitespace-nowrap transition-all ${
isActive
? 'bg-gold/20 border-gold text-gold shadow-md shadow-gold/15'
: 'bg-surface-2 border-border text-text-muted'
}`}
whileTap={{ scale: 0.9 }}
className="relative cursor-pointer"
whileTap={{ scale: 0.92 }}
transition={{ type: 'spring', stiffness: 500, damping: 20 }}
onClick={() => {
playSound('click')
......@@ -123,60 +198,128 @@ export function PlayPage() {
if (first) setSelectedTC(first[0])
}}
>
<Icon size={14} />
{cat.label}
<div
className={`flex items-center gap-1.5 px-4 py-2.5 text-[11px] font-black whitespace-nowrap transition-all ${
isActive
? 'text-background'
: 'text-text-muted'
}`}
style={{
clipPath: 'polygon(8% 0%, 92% 0%, 100% 100%, 0% 100%)',
background: isActive
? 'linear-gradient(180deg, #FFE066, #FFC83D)'
: 'var(--color-surface-2)',
boxShadow: isActive
? '0 4px 12px rgba(255,200,60,0.25)'
: 'inset 0 2px 4px rgba(0,0,0,0.3)',
}}
>
<Icon size={13} />
{cat.label}
</div>
</motion.button>
)
})}
</div>
{/* Time Options Grid */}
<div className="grid grid-cols-3 gap-2.5">
{Object.entries(TIME_CONTROLS)
.filter(([, v]) => v.category === activeCategory)
.map(([key, tc]) => (
<motion.button
key={key}
className={`py-4 rounded-xl text-center font-black text-sm border-3 transition-all ${
selectedTC === key
? '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.9 }}
transition={{ type: 'spring', stiffness: 500, damping: 20 }}
onClick={() => {
playSound('click')
setSelectedTC(key)
}}
>
{tc.labelAr}
</motion.button>
))}
.map(([key, tc]) => {
const isSelected = selectedTC === key
return (
<motion.button
key={key}
className="relative cursor-pointer"
whileTap={{ scale: 0.9 }}
animate={isSelected ? { y: -2 } : { y: 0 }}
transition={{ type: 'spring', stiffness: 500, damping: 20 }}
onClick={() => {
playSound('click')
setSelectedTC(key)
}}
>
<div
className={`py-4 px-2 text-center font-black text-sm transition-all rounded-xl border-[3px] ${
isSelected
? 'border-gold text-gold'
: 'border-border/60 text-text-secondary'
}`}
style={{
background: isSelected
? 'linear-gradient(180deg, rgba(255,200,60,0.15), rgba(255,200,60,0.04))'
: 'var(--color-surface-1)',
boxShadow: isSelected
? '0 0 16px rgba(255,200,60,0.2), 0 4px 0 0 rgba(201,151,46,0.4)'
: 'inset 0 2px 6px rgba(0,0,0,0.3)',
}}
>
{tc.labelAr}
</div>
</motion.button>
)
})}
</div>
</motion.div>
<motion.div variants={fadeUp} className="flex flex-col items-center gap-3 mt-2 pb-4">
<Button
{/* === 5. ACTION BUTTONS === */}
<motion.div variants={fadeUp} className="flex flex-col items-center gap-4 pt-3 pb-4">
{/* Primary: Find opponent — massive 3D gold */}
<motion.button
className="relative w-[80%] cursor-pointer"
whileTap={{ y: 4 }}
transition={{ type: 'spring', stiffness: 600, damping: 20 }}
onClick={() => {
playSound('click')
navigate(`/matchmaking?tc=${selectedTC}&game=chess`)
}}
className="w-[80%]"
size="lg"
>
البحث عن خصم
</Button>
<Button
<div
className="relative flex items-center justify-center gap-3 py-5 rounded-2xl font-black text-lg text-background overflow-hidden"
style={{
background: 'linear-gradient(180deg, #FFE066 0%, #FFC83D 70%, #C9972E 100%)',
boxShadow: '0 6px 0 0 #9B6B1A, 0 8px 24px rgba(255,200,60,0.3)',
}}
>
{/* Animated glow */}
<motion.div
className="absolute inset-0 rounded-2xl"
animate={{
boxShadow: [
'0 0 10px rgba(255,200,60,0.2)',
'0 0 25px rgba(255,200,60,0.4)',
'0 0 10px rgba(255,200,60,0.2)',
],
}}
transition={{ duration: 2, repeat: Infinity }}
/>
<Swords size={22} className="text-background" />
<span>البحث عن خصم</span>
</div>
</motion.button>
{/* Secondary: Play vs bot — purple 3D */}
<motion.button
className="relative w-[72%] cursor-pointer"
whileTap={{ y: 3 }}
transition={{ type: 'spring', stiffness: 600, damping: 20 }}
onClick={() => {
playSound('click')
navigate('/bot-select')
}}
variant="ghost"
className="w-[80%]"
size="md"
>
<Cpu size={18} className="text-purple" />
العب ضد الروبوت
</Button>
<div
className="flex items-center justify-center gap-3 py-4 rounded-2xl font-black text-sm text-text-primary border-2 border-purple/40"
style={{
background: 'linear-gradient(180deg, rgba(180,77,255,0.15) 0%, rgba(180,77,255,0.05) 100%)',
boxShadow: '0 5px 0 0 rgba(120,40,180,0.4)',
}}
>
<Cpu size={18} className="text-purple" />
<span>العب ضد الروبوت</span>
</div>
</motion.button>
</motion.div>
</motion.div>
</PageTransition>
......
import { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { Trophy, Target, Flame, TrendingUp, Pencil, X, LogOut } from 'lucide-react'
import { Trophy, Target, Flame, TrendingUp, Pencil, X, LogOut, Star, Shield } 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'
const stagger = {
hidden: { opacity: 0 },
show: { opacity: 1, transition: { staggerChildren: 0.06 } },
show: { opacity: 1, transition: { staggerChildren: 0.08 } },
}
const item = {
hidden: { opacity: 0, y: 12, scale: 0.95 },
show: { opacity: 1, y: 0, scale: 1, transition: { type: 'spring', stiffness: 400, damping: 22 } },
hidden: { opacity: 0, y: 16, scale: 0.93 },
show: { opacity: 1, y: 0, scale: 1, transition: { type: 'spring', stiffness: 380, damping: 22 } },
}
const HEX_CLIP = 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)'
export function ProfilePage() {
const { user, profile, setProfile } = useAuthStore()
const { showToast } = useNotificationStore()
......@@ -74,116 +75,272 @@ export function ProfilePage() {
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]' },
{ label: 'رصاصة', value: profile.elo_bullet, color: '#FF5252', borderColor: 'border-[#FF5252]' },
{ label: 'خاطف', value: profile.elo_blitz, color: '#FFC83D', borderColor: 'border-[#FFC83D]' },
{ label: 'سريع', value: profile.elo_rapid, color: '#00E5CC', borderColor: 'border-[#00E5CC]' },
{ label: 'كلاسيكي', value: profile.elo_classical, color: '#B44DFF', borderColor: 'border-[#B44DFF]' },
]
const stats = [
{ icon: <TrendingUp size={20} className="text-[#B44DFF]" />, value: profile.total_games_played, label: 'مباريات', ringColor: '#B44DFF' },
{ icon: <Trophy size={20} className="text-[#FFC83D]" />, value: profile.total_wins, label: 'انتصارات', ringColor: '#FFC83D' },
{ icon: <Target size={20} className="text-[#00E5CC]" />, value: `${winRate}%`, label: 'نسبة الفوز', ringColor: '#00E5CC' },
{ icon: <Flame size={20} className="text-[#FF5252]" />, value: profile.best_win_streak, label: 'افضل سلسلة', ringColor: '#FF5252' },
]
return (
<PageTransition>
<motion.div
className="flex flex-col gap-5 py-6"
className="flex flex-col gap-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-4xl font-black text-[#FFC83D]">
{profile.display_name?.charAt(0) || '?'}
</span>
</motion.div>
<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 }}
>
<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>
{/* === PLAYER CARD / NAMEPLATE === */}
<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-4 rounded-full bg-surface-3 border-2 border-border overflow-hidden">
<motion.div
className="h-full rounded-full bg-gradient-to-l from-[#FFC83D] to-[#FFE082]"
initial={{ width: 0 }}
animate={{ width: `${xpPercent}%` }}
transition={{ duration: 1.2, ease: 'easeOut', delay: 0.3 }}
/>
<div
className="relative overflow-hidden rounded-[20px] p-5"
style={{
border: '3px solid var(--color-border)',
background: 'linear-gradient(135deg, rgba(255,200,60,0.05) 0%, rgba(180,77,255,0.05) 100%)',
boxShadow: 'inset 0 2px 4px rgba(255,255,255,0.04), inset 0 -2px 6px rgba(0,0,0,0.3), 0 6px 20px rgba(0,0,0,0.4)',
}}
>
{/* Decorative corner stars */}
<Star size={24} className="absolute top-3 left-3 text-[#FFC83D] opacity-[0.08]" />
<Star size={18} className="absolute top-5 left-10 text-[#FFC83D] opacity-[0.06]" />
<Star size={20} className="absolute bottom-4 right-4 text-[#B44DFF] opacity-[0.07]" />
<Star size={14} className="absolute bottom-6 right-10 text-[#B44DFF] opacity-[0.05]" />
{/* Subtle background pattern */}
<div
className="absolute inset-0 opacity-[0.02]"
style={{
backgroundImage: 'repeating-linear-gradient(45deg, transparent, transparent 10px, rgba(255,200,60,0.5) 10px, rgba(255,200,60,0.5) 11px)',
}}
/>
<div className="relative flex flex-col items-center gap-3">
{/* Hexagonal Avatar */}
<div className="relative">
<motion.div
className="flex items-center justify-center"
style={{
width: 80,
height: 80,
clipPath: HEX_CLIP,
background: 'linear-gradient(135deg, rgba(255,200,60,0.25), rgba(180,77,255,0.15))',
border: 'none',
}}
initial={{ scale: 0, rotate: -30 }}
animate={{ scale: 1, rotate: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 18 }}
>
<div
className="flex items-center justify-center"
style={{
width: 74,
height: 74,
clipPath: HEX_CLIP,
background: 'linear-gradient(135deg, var(--color-surface-2), var(--color-surface-3))',
}}
>
<span className="text-3xl font-black text-[#FFC83D]">
{profile.display_name?.charAt(0) || '?'}
</span>
</div>
</motion.div>
{/* Gold border hex overlay */}
<div
className="absolute inset-0 pointer-events-none"
style={{
clipPath: HEX_CLIP,
border: '3px solid #FFC83D',
width: 80,
height: 80,
}}
/>
{/* Level badge */}
<motion.div
className="absolute -bottom-2 left-1/2 -translate-x-1/2 w-7 h-7 rounded-full flex items-center justify-center"
style={{
background: 'linear-gradient(to bottom, #FFE066, #FFC83D)',
border: '2px solid var(--color-background)',
boxShadow: '0 2px 8px rgba(255,200,60,0.4)',
}}
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ type: 'spring', stiffness: 500, damping: 20, delay: 0.2 }}
>
<span className="text-[10px] font-black text-[#0B0E1A]">{profile.level}</span>
</motion.div>
</div>
{/* Player name + username */}
<div className="text-center mt-1">
<h1 className="text-2xl font-black tracking-tight">{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.5 max-w-[240px] mx-auto line-clamp-2 leading-relaxed">{profile.bio}</p>
)}
</div>
{/* XP Progress bar */}
<div className="w-full mt-2">
<div className="flex items-center justify-between mb-1.5">
<span className="text-xs font-black text-text-secondary">المستوى {profile.level}</span>
<span className="text-xs font-black text-[#FFC83D]">XP {currentXpInLevel}</span>
</div>
<div
className="w-full overflow-hidden"
style={{
height: 16,
borderRadius: 10,
border: '2px solid var(--color-border)',
background: 'var(--color-surface-3)',
boxShadow: 'inset 0 2px 4px rgba(0,0,0,0.4)',
}}
>
<motion.div
className="h-full relative"
style={{
borderRadius: 8,
background: 'linear-gradient(to left, #FFC83D, #FFE066)',
backgroundImage: 'repeating-linear-gradient(90deg, transparent, transparent 8px, rgba(255,255,255,0.1) 8px, rgba(255,255,255,0.1) 9px)',
}}
initial={{ width: 0 }}
animate={{ width: `${xpPercent}%` }}
transition={{ duration: 1.2, ease: 'easeOut', delay: 0.4 }}
/>
</div>
</div>
</div>
<p className="text-xs text-text-muted font-bold mt-2">{xpRemaining} XP للمستوى التالي</p>
</Card>
</div>
</motion.div>
<motion.div variants={item}>
{/* === RATINGS SECTION === */}
<motion.div variants={item} className="flex flex-col gap-3">
{/* Section header */}
<div className="flex items-center gap-3">
<h2 className="text-lg font-black">التقييمات</h2>
<div className="flex-1 h-[2px]" style={{ background: 'linear-gradient(to left, transparent, rgba(255,200,60,0.4))' }} />
</div>
{/* 2x2 Rating grid */}
<div className="grid grid-cols-2 gap-3">
{ratings.map((rating, i) => (
<motion.div
key={rating.label}
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={{ type: 'spring', stiffness: 400, damping: 22, delay: 0.15 + i * 0.06 }}
className="relative flex flex-col items-center justify-center overflow-hidden"
style={{
height: 90,
clipPath: HEX_CLIP,
border: `3px solid ${rating.color}`,
background: `linear-gradient(to bottom, ${rating.color}10, var(--color-surface-1))`,
boxShadow: `inset 0 2px 6px rgba(0,0,0,0.3), 0 0 12px ${rating.color}15`,
}}
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ type: 'spring', stiffness: 400, damping: 22, delay: 0.2 + i * 0.07 }}
>
<span className="text-2xl font-black">{rating.value}</span>
<span className="text-xs font-bold text-text-muted">{rating.label}</span>
{/* Inner hex to create border effect */}
<div
className="absolute inset-0 flex flex-col items-center justify-center"
style={{
borderTop: `4px solid ${rating.color}`,
}}
>
<span className="text-2xl font-black mt-2">{rating.value}</span>
<span className="text-[10px] font-bold text-text-muted">{rating.label}</span>
</div>
</motion.div>
))}
</div>
</motion.div>
<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="افضل سلسلة" />
{/* === STATS SECTION === */}
<motion.div variants={item} className="flex flex-col gap-3">
{/* Section header */}
<div className="flex items-center gap-3">
<h2 className="text-lg font-black">الاحصائيات</h2>
<div className="flex-1 h-[2px]" style={{ background: 'linear-gradient(to left, transparent, rgba(0,229,204,0.4))' }} />
</div>
{/* 4 circular medal items */}
<div className="flex items-center justify-between gap-2">
{stats.map((stat, i) => (
<motion.div
key={stat.label}
className="flex flex-col items-center gap-1.5"
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
transition={{ type: 'spring', stiffness: 400, damping: 22, delay: 0.25 + i * 0.07 }}
>
{/* Circular badge with ring */}
<div
className="relative flex items-center justify-center"
style={{
width: 64,
height: 64,
borderRadius: '50%',
border: `3px solid ${stat.ringColor}`,
background: `linear-gradient(135deg, ${stat.ringColor}12, var(--color-surface-2))`,
boxShadow: `inset 0 2px 6px rgba(0,0,0,0.3), 0 0 8px ${stat.ringColor}15`,
}}
>
<div className="flex flex-col items-center gap-0.5">
{stat.icon}
<span className="text-xs font-black mt-0.5">{stat.value}</span>
</div>
</div>
<span className="text-[9px] text-text-muted font-bold text-center leading-tight">{stat.label}</span>
</motion.div>
))}
</div>
</motion.div>
<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>
{/* === ACTION BUTTONS === */}
<motion.div className="flex flex-col items-center gap-3 mt-1" variants={item}>
{/* Edit profile button */}
<motion.button
className="text-sm font-bold text-[#FF5252]/70 flex items-center gap-1.5 py-2"
whileTap={{ scale: 0.9 }}
className="w-[75%] flex items-center justify-center gap-2.5 py-3.5 rounded-2xl font-black text-sm"
style={{
border: '3px solid var(--color-border)',
background: 'var(--color-surface-2)',
boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.04), inset 0 -2px 4px rgba(0,0,0,0.2), 0 4px 12px rgba(0,0,0,0.3)',
}}
whileTap={{ scale: 0.94, y: 2 }}
whileHover={{ scale: 1.02, borderColor: 'rgba(255,200,60,0.5)' }}
onClick={openEdit}
>
<Pencil size={16} className="text-[#FFC83D]" />
<span>تعديل الملف الشخصي</span>
</motion.button>
{/* Logout button */}
<motion.button
className="w-[60%] flex items-center justify-center gap-2 py-2.5 rounded-xl font-bold text-xs"
style={{
border: '2px solid rgba(255,82,82,0.4)',
background: 'rgba(255,82,82,0.08)',
color: '#FF5252',
boxShadow: '0 2px 8px rgba(255,82,82,0.15)',
}}
whileTap={{ scale: 0.92, y: 1 }}
onClick={handleLogout}
>
<LogOut size={14} />
تسجيل الخروج
<span>تسجيل الخروج</span>
</motion.button>
</motion.div>
</motion.div>
{/* === EDIT MODAL === */}
<AnimatePresence>
{editOpen && (
<motion.div
......@@ -200,31 +357,58 @@ export function ProfilePage() {
exit={{ opacity: 0 }}
/>
<motion.div
className="relative w-full max-w-[360px] rounded-2xl bg-surface-1 border-3 border-border p-6 flex flex-col gap-5"
className="relative w-full max-w-[360px] flex flex-col gap-5 overflow-hidden"
style={{
borderRadius: 20,
border: '3px solid var(--color-border)',
background: 'var(--color-surface-1)',
boxShadow: 'inset 0 2px 4px rgba(255,255,255,0.03), inset 0 -2px 8px rgba(0,0,0,0.3), 0 16px 48px rgba(0,0,0,0.6)',
}}
initial={{ scale: 0.85, opacity: 0, y: 30 }}
animate={{ scale: 1, opacity: 1, y: 0 }}
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-black">تعديل الملف الشخصي</h2>
<motion.button
whileTap={{ scale: 0.85 }}
onClick={() => setEditOpen(false)}
className="p-2 rounded-xl bg-surface-3 border-2 border-border"
>
<X size={16} className="text-text-muted" />
</motion.button>
{/* Ribbon banner header */}
<div
className="relative flex items-center justify-center py-3"
style={{
background: 'linear-gradient(to bottom, rgba(255,200,60,0.12), transparent)',
borderBottom: '2px solid var(--color-border)',
clipPath: 'polygon(0 0, 100% 0, 95% 100%, 5% 100%)',
}}
>
<Shield size={18} className="text-[#FFC83D] ml-2" />
<h2 className="text-lg font-black">تعديل الملف</h2>
</div>
<div className="flex flex-col gap-4">
{/* Close button */}
<motion.button
whileTap={{ scale: 0.85 }}
onClick={() => setEditOpen(false)}
className="absolute top-3 left-3 p-2 rounded-xl"
style={{
background: 'var(--color-surface-3)',
border: '2px solid var(--color-border)',
}}
>
<X size={14} className="text-text-muted" />
</motion.button>
{/* Form */}
<div className="flex flex-col gap-4 px-5 pb-5">
<div className="flex flex-col gap-1.5">
<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-3 border-border focus:border-[#FFC83D]/60 text-sm font-bold outline-none transition-colors"
className="px-4 py-3 rounded-xl text-sm font-bold outline-none transition-colors"
style={{
background: 'var(--color-surface-3)',
border: '3px solid var(--color-border)',
boxShadow: 'inset 0 2px 4px rgba(0,0,0,0.3)',
}}
maxLength={30}
dir="rtl"
/>
......@@ -234,21 +418,26 @@ export function ProfilePage() {
<textarea
value={editForm.bio}
onChange={(e) => setEditForm({ ...editForm, bio: e.target.value })}
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"
className="px-4 py-3 rounded-xl text-sm font-bold outline-none transition-colors resize-none h-20"
style={{
background: 'var(--color-surface-3)',
border: '3px solid var(--color-border)',
boxShadow: 'inset 0 2px 4px rgba(0,0,0,0.3)',
}}
maxLength={150}
dir="rtl"
/>
</div>
</div>
<Button
onClick={handleSave}
loading={saving}
disabled={!editForm.display_name.trim()}
className="w-[80%] mx-auto"
>
حفظ التعديلات
</Button>
<Button
onClick={handleSave}
loading={saving}
disabled={!editForm.display_name.trim()}
className="w-[80%] mx-auto mt-1"
>
حفظ التعديلات
</Button>
</div>
</motion.div>
</motion.div>
)}
......@@ -256,13 +445,3 @@ export function ProfilePage() {
</PageTransition>
)
}
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-2xl bg-surface-1 border-3 border-border">
{icon}
<span className="text-base font-black">{value}</span>
<span className="text-[9px] text-text-muted text-center leading-tight font-bold">{label}</span>
</div>
)
}
import { Volume2, VolumeX, Info, LogOut, Trash2, Bug } from 'lucide-react'
import { Volume2, VolumeX, Info, LogOut, Trash2, Bug, Settings } from 'lucide-react'
import { motion } from 'framer-motion'
import { useNavigate } from 'react-router-dom'
import { PageTransition } from '../components/layout/PageTransition'
import { Card } from '../components/ui/Card'
import { useUIStore } from '../stores/uiStore'
import { supabase } from '../lib/supabase'
......@@ -17,49 +16,83 @@ export function SettingsPage() {
return (
<PageTransition>
<h1 className="text-2xl font-black">الاعدادات</h1>
{/* Header */}
<div className="flex items-center gap-3">
<Settings size={22} className="text-[#FFC83D]" />
<h1 className="text-2xl font-black text-text-primary">الاعدادات</h1>
</div>
<div className="h-[3px] rounded-full bg-gradient-to-l from-[#FFC83D] via-[#FFC83D]/40 to-transparent" />
<div className="flex flex-col gap-4">
<Card className="flex items-center justify-between">
<div className="flex flex-col gap-3">
{/* Sound toggle */}
<div className="game-panel !p-4 flex items-center justify-between">
<div className="flex items-center gap-3">
{soundEnabled ? <Volume2 size={20} className="text-cyan" /> : <VolumeX size={20} className="text-text-muted" />}
<span className="text-sm font-bold">الاصوات</span>
{soundEnabled ? (
<div className="w-10 h-10 rounded-xl bg-[#00E5CC]/15 border-2 border-[#00E5CC]/30 flex items-center justify-center">
<Volume2 size={18} className="text-[#00E5CC]" />
</div>
) : (
<div className="w-10 h-10 rounded-xl bg-surface-3 border-2 border-border flex items-center justify-center">
<VolumeX size={18} className="text-text-muted" />
</div>
)}
<div>
<span className="text-sm font-black block">الاصوات</span>
<span className="text-[11px] text-text-muted font-bold">تشغيل المؤثرات الصوتية</span>
</div>
</div>
{/* Game-style toggle */}
<motion.button
className={`w-14 h-7 rounded-full p-1 border-2 ${soundEnabled ? 'bg-cyan/20 border-cyan' : 'bg-surface-3 border-border'}`}
className={`w-[48px] h-[26px] rounded-full p-[3px] border-3 transition-colors ${
soundEnabled
? 'bg-[#00E5CC]/20 border-[#00E5CC]'
: 'bg-surface-3 border-border'
}`}
style={{ boxShadow: soundEnabled ? '0 0 8px rgba(0,229,204,0.3)' : 'inset 0 2px 4px rgba(0,0,0,0.3)' }}
onClick={() => setSoundEnabled(!soundEnabled)}
whileTap={{ scale: 0.9 }}
>
<motion.div
className={`w-5 h-5 rounded-full ${soundEnabled ? 'bg-cyan' : 'bg-text-muted'}`}
animate={{ x: soundEnabled ? 0 : 28 }}
className={`w-[18px] h-[18px] rounded-full shadow-md ${
soundEnabled ? 'bg-[#00E5CC]' : 'bg-text-muted'
}`}
animate={{ x: soundEnabled ? 0 : 20 }}
transition={{ type: 'spring', stiffness: 500, damping: 30 }}
/>
</motion.button>
</Card>
</div>
<Card className="flex items-center gap-3">
<Info size={20} className="text-gold" />
{/* Report bug */}
<div className="game-panel !p-4 flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-[#B44DFF]/15 border-2 border-[#B44DFF]/30 flex items-center justify-center">
<Bug size={18} className="text-[#B44DFF]" />
</div>
<div>
<p className="text-sm font-bold">EL3AB Player</p>
<p className="text-xs text-text-muted font-semibold">الاصدار 1.0.0</p>
<p className="text-sm font-black">الابلاغ عن مشكلة</p>
<p className="text-[11px] text-text-muted font-bold">ساعدنا في تحسين التطبيق</p>
</div>
</Card>
</div>
<Card className="flex items-center gap-3">
<Bug size={20} className="text-purple" />
{/* Version info - recessed panel */}
<div className="game-panel !p-4 flex items-center gap-3"
style={{ boxShadow: 'inset 0 3px 8px rgba(0,0,0,0.3), inset 0 1px 2px rgba(0,0,0,0.2)' }}
>
<div className="w-10 h-10 rounded-xl bg-[#FFC83D]/15 border-2 border-[#FFC83D]/30 flex items-center justify-center">
<Info size={18} className="text-[#FFC83D]" />
</div>
<div>
<p className="text-sm font-bold">الابلاغ عن مشكلة</p>
<p className="text-xs text-text-muted font-semibold">ساعدنا في تحسين التطبيق</p>
<p className="text-sm font-bold">EL3AB Player</p>
<p className="text-[11px] text-text-muted font-semibold">الاصدار 1.0.0</p>
</div>
</Card>
</div>
</div>
<div className="flex flex-col gap-3 mt-4">
{/* Logout and Delete */}
<div className="flex flex-col gap-3 mt-4 items-center">
<motion.button
whileTap={{ scale: 0.95 }}
whileTap={{ scale: 0.93 }}
onClick={handleLogout}
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"
className="btn-3d flex items-center justify-center gap-2.5 w-[70%] px-6 py-3.5 rounded-2xl bg-[#FF5252] text-white font-black text-sm"
>
<LogOut size={16} />
<span>تسجيل الخروج</span>
......@@ -67,11 +100,11 @@ export function SettingsPage() {
<motion.button
disabled
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"
className="flex items-center justify-center gap-2 w-[70%] px-6 py-3 rounded-2xl bg-surface-2 border-2 border-border text-text-muted font-bold text-sm opacity-40 cursor-not-allowed"
>
<Trash2 size={16} />
<span>حذف الحساب</span>
<span className="text-[10px] bg-surface-3 px-2 py-0.5 rounded-full">قريبا</span>
<span className="text-[10px] bg-surface-3 px-2 py-0.5 rounded-full mr-1">قريبا</span>
</motion.button>
</div>
</PageTransition>
......
import { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { Coins, Gem, ShoppingBag, Sparkles, Check, X } from 'lucide-react'
import { Coins, Gem, ShoppingBag, Sparkles, Check, X, Shield } from 'lucide-react'
import { PageTransition } from '../components/layout/PageTransition'
import { Button } from '../components/ui/Button'
import { useAuthStore } from '../stores/authStore'
import { useShop, type Cosmetic } from '../hooks/useShop'
import { RARITY_COLORS } from '../lib/constants'
......@@ -26,6 +25,14 @@ const RARITY_LABELS: Record<string, string> = {
legendary: 'اسطوري',
}
const RARITY_GLOW: Record<string, string> = {
common: 'none',
uncommon: '0 0 8px rgba(77,139,255,0.3)',
rare: '0 0 12px rgba(180,77,255,0.4)',
epic: '0 0 16px rgba(255,82,82,0.4)',
legendary: '0 0 20px rgba(255,200,61,0.5), 0 0 40px rgba(255,200,61,0.2)',
}
export function ShopPage() {
const { profile } = useAuthStore()
const [filter, setFilter] = useState<FilterType>('all')
......@@ -58,33 +65,41 @@ export function ShopPage() {
return (
<PageTransition className="flex flex-col gap-5">
{/* Header */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-2.5">
<ShoppingBag size={24} className="text-[#FFC83D]" />
<ShoppingBag size={22} className="text-[#FFC83D]" />
<h1 className="text-2xl font-black">المتجر</h1>
</div>
{/* Currency capsules */}
<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">
<div className="flex items-center gap-1.5 border-3 border-[#FFC83D]/50 rounded-full px-3 py-1.5 bg-[#FFC83D]/10"
style={{ boxShadow: 'inset 0 2px 4px rgba(0,0,0,0.2)' }}
>
<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 border-3 border-[#B44DFF]/50 rounded-full px-3 py-1.5 bg-[#B44DFF]/10">
<div className="flex items-center gap-1.5 border-3 border-[#B44DFF]/50 rounded-full px-3 py-1.5 bg-[#B44DFF]/10"
style={{ boxShadow: 'inset 0 2px 4px rgba(0,0,0,0.2)' }}
>
<Gem size={14} className="text-[#B44DFF]" />
<span className="text-sm font-black text-[#B44DFF]">{profile?.gems || 0}</span>
</div>
</div>
</div>
<div className="flex gap-2 overflow-x-auto pb-1 -mx-1 px-1 scrollbar-hide">
{/* Category filter - thick bordered tabs */}
<div className="flex gap-2 overflow-x-auto pb-1 no-scrollbar">
{FILTER_OPTIONS.map((opt) => (
<motion.button
key={opt.key}
onClick={() => setFilter(opt.key)}
className={`px-4 py-2 rounded-xl text-xs font-bold whitespace-nowrap border-2 transition-colors ${
className={`px-4 py-2.5 rounded-xl text-xs font-black whitespace-nowrap border-3 transition-all ${
filter === opt.key
? 'bg-[#FF8C42] border-[#FF8C42] text-white'
: 'bg-surface-2 border-border text-text-muted hover:border-[#FF8C42]/40'
? 'bg-gradient-to-b from-[#FF8C42] to-[#E06820] border-[#FF8C42] text-white shadow-[0_3px_0_#A84510]'
: 'bg-surface-2 border-border text-text-muted'
}`}
style={filter !== opt.key ? { boxShadow: 'inset 0 2px 4px rgba(0,0,0,0.3)' } : {}}
whileTap={{ scale: 0.93 }}
transition={{ type: 'spring', stiffness: 500, damping: 20 }}
>
......@@ -102,16 +117,21 @@ export function ShopPage() {
/>
</div>
) : items.length === 0 ? (
<div className="flex-1 flex flex-col items-center justify-center py-16 gap-4">
<div className="flex-1 flex flex-col items-center justify-center py-16 gap-5">
<motion.div
className="w-20 h-20 rounded-2xl bg-surface-2 border-3 border-border flex items-center justify-center"
className="w-24 h-24 flex items-center justify-center"
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)' }}
initial={{ scale: 0, rotate: -10 }}
animate={{ scale: 1, rotate: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 18 }}
>
<ShoppingBag size={32} className="text-text-muted" />
<div className="w-full h-full bg-gradient-to-br from-surface-2 to-surface-3 flex items-center justify-center"
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)' }}
>
<ShoppingBag size={36} className="text-text-muted" />
</div>
</motion.div>
<p className="text-text-muted text-sm font-bold">لا توجد عناصر متاحة</p>
<p className="text-text-muted text-sm font-black">لا توجد عناصر</p>
</div>
) : (
<div className="grid grid-cols-2 gap-4">
......@@ -120,37 +140,48 @@ export function ShopPage() {
const isEquipped = equippedIds.includes(item.id)
const rarityColor = RARITY_COLORS[item.rarity]
const isLegendary = item.rarity === 'legendary'
const rotation = index % 2 === 0 ? '-1deg' : '1deg'
return (
<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-3 overflow-hidden text-center ${isLegendary ? 'animate-pulse-glow' : ''}`}
style={{ borderColor: rarityColor }}
className="relative flex flex-col items-center gap-2 p-4 rounded-2xl bg-surface-1 border-3 overflow-hidden text-center"
style={{
borderColor: rarityColor,
transform: `rotate(${rotation})`,
boxShadow: RARITY_GLOW[item.rarity],
}}
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` }}
whileHover={{ y: -6, scale: 1.03, rotate: 0 }}
whileTap={{ scale: 0.95 }}
>
{/* Legendary shimmer overlay */}
{isLegendary && (
<motion.div
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 }}
<div className="absolute inset-0 rounded-2xl pointer-events-none animate-shimmer"
style={{ background: 'linear-gradient(110deg, transparent 25%, rgba(255,200,61,0.1) 50%, transparent 75%)', backgroundSize: '200% 100%' }}
/>
)}
{/* Owned stamp - shield overlay */}
{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 className="absolute top-2 left-2 z-10 w-10 h-10 flex items-center justify-center"
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)' }}
>
<div className="w-full h-full bg-green-500/20 border border-green-500/50 flex items-center justify-center flex-col"
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)' }}
>
<Check size={10} className="text-green-400" />
<span className="text-[7px] font-black text-green-400 leading-none mt-0.5">
{isEquipped ? 'مفعّل' : 'مملوك'}
</span>
</div>
</div>
)}
{/* Preview */}
<div
className="w-full aspect-square rounded-xl bg-surface-2 border-2 flex items-center justify-center"
style={{ borderColor: `${rarityColor}40` }}
......@@ -169,16 +200,16 @@ export function ShopPage() {
)}
</div>
<div className="flex items-center gap-1.5">
<div
className="w-2.5 h-2.5 rounded-full border-2"
style={{ backgroundColor: rarityColor, borderColor: `${rarityColor}80` }}
/>
<span className="text-xs font-black text-text-primary truncate max-w-[90px]">
{/* Name - ribbon style */}
<div className="w-[110%] -mx-[5%] py-1.5 bg-surface-2 border-t-2 border-b-2"
style={{ borderColor: `${rarityColor}30` }}
>
<span className="text-xs font-black text-text-primary truncate block px-2">
{item.name_ar}
</span>
</div>
{/* Price ribbon */}
{!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 ? (
......@@ -200,6 +231,7 @@ export function ShopPage() {
</div>
)}
{/* Purchase Modal */}
<AnimatePresence>
{selectedItem && (
<motion.div
......@@ -210,138 +242,143 @@ export function ShopPage() {
onClick={() => !purchasing && setSelectedItem(null)}
>
<motion.div
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"
className="w-full max-w-[340px] game-panel !p-0 flex flex-col items-center 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-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>
{/* Rarity-colored header */}
<div className="w-full h-2 rounded-t-2xl" style={{ backgroundColor: RARITY_COLORS[selectedItem.rarity] }} />
{purchaseSuccess ? (
<motion.div
className="flex flex-col items-center gap-4 py-8"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ type: 'spring', stiffness: 300, damping: 18 }}
<div className="p-6 flex flex-col items-center gap-4 w-full">
<button
onClick={() => !purchasing && setSelectedItem(null)}
className="absolute top-5 left-5 w-9 h-9 rounded-full bg-surface-2 border-2 border-border flex items-center justify-center hover:border-[#FF5252]/50"
>
<X size={16} className="text-text-muted" />
</button>
{purchaseSuccess ? (
<motion.div
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 }}
className="flex flex-col items-center gap-4 py-8"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ type: 'spring', stiffness: 300, damping: 18 }}
>
<Check size={36} className="text-green-400" />
<motion.div
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={36} className="text-green-400" />
</motion.div>
<p className="text-xl font-black text-green-400">تم الشراء بنجاح</p>
</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 border-2 flex items-center justify-center mt-6"
style={{ borderColor: `${RARITY_COLORS[selectedItem.rarity]}40` }}
>
{selectedItem.preview_url ? (
<img
src={selectedItem.preview_url}
alt={selectedItem.name_ar}
className="w-full h-full object-cover rounded-2xl"
/>
) : (
<div
className="w-24 h-24 rounded-full"
style={{
background: `radial-gradient(circle, ${RARITY_COLORS[selectedItem.rarity]}60, ${RARITY_COLORS[selectedItem.rarity]}15)`,
}}
/>
)}
</div>
<div className="text-center">
<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>
)}
</div>
<div className="flex items-center gap-2">
) : (
<>
<div
className="w-3 h-3 rounded-full border-2"
style={{ backgroundColor: RARITY_COLORS[selectedItem.rarity], borderColor: `${RARITY_COLORS[selectedItem.rarity]}80` }}
/>
<span
className="text-sm font-black"
style={{ color: RARITY_COLORS[selectedItem.rarity] }}
className="w-full aspect-[4/3] rounded-2xl bg-surface-2 border-2 flex items-center justify-center mt-4"
style={{ borderColor: `${RARITY_COLORS[selectedItem.rarity]}40` }}
>
{RARITY_LABELS[selectedItem.rarity]}
</span>
{selectedItem.rarity === 'legendary' && (
<Sparkles size={14} style={{ color: RARITY_COLORS.legendary }} />
)}
</div>
{ownedIds.includes(selectedItem.id) ? (
<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-3 border-green-500/40">
<Check size={16} className="text-green-400" />
<span className="text-sm font-black text-green-400">مفعّل حاليا</span>
</div>
{selectedItem.preview_url ? (
<img
src={selectedItem.preview_url}
alt={selectedItem.name_ar}
className="w-full h-full object-cover rounded-2xl"
/>
) : (
<Button
variant="cyan"
size="md"
onClick={handleEquip}
loading={purchasing}
className="w-full"
>
تفعيل
</Button>
<div
className="w-24 h-24 rounded-full"
style={{
background: `radial-gradient(circle, ${RARITY_COLORS[selectedItem.rarity]}60, ${RARITY_COLORS[selectedItem.rarity]}15)`,
}}
/>
)}
</div>
) : (
<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-[#B44DFF]" />
<span className="text-lg font-black text-[#B44DFF]">{selectedItem.price_gems}</span>
</>
) : (
<>
<Coins size={18} className="text-[#FFC83D]" />
<span className="text-lg font-black text-[#FFC83D]">{selectedItem.price_coins}</span>
</>
)}
</div>
<Button
variant="gold"
size="md"
onClick={handlePurchase}
loading={purchasing}
disabled={
selectedItem.price_gems
? (profile?.gems || 0) < (selectedItem.price_gems || 0)
: (profile?.coins || 0) < (selectedItem.price_coins || 0)
}
className="w-full"
<div className="text-center">
<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>
)}
</div>
<div className="flex items-center gap-2">
<div
className="w-3 h-3 rounded-full border-2"
style={{ backgroundColor: RARITY_COLORS[selectedItem.rarity], borderColor: `${RARITY_COLORS[selectedItem.rarity]}80` }}
/>
<span
className="text-sm font-black"
style={{ color: RARITY_COLORS[selectedItem.rarity] }}
>
شراء
</Button>
{(selectedItem.price_gems
? (profile?.gems || 0) < (selectedItem.price_gems || 0)
: (profile?.coins || 0) < (selectedItem.price_coins || 0)) && (
<p className="text-[11px] text-[#FF5252] text-center font-bold">رصيد غير كافي</p>
{RARITY_LABELS[selectedItem.rarity]}
</span>
{selectedItem.rarity === 'legendary' && (
<Sparkles size={14} style={{ color: RARITY_COLORS.legendary }} />
)}
</div>
)}
</>
)}
{ownedIds.includes(selectedItem.id) ? (
<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-3 border-green-500/40">
<Check size={16} className="text-green-400" />
<span className="text-sm font-black text-green-400">مفعّل حاليا</span>
</div>
) : (
<motion.button
whileTap={{ scale: 0.93 }}
onClick={handleEquip}
disabled={purchasing}
className="btn-3d w-full py-3 rounded-xl bg-[#00E5CC] text-background text-sm font-black disabled:opacity-50"
>
{purchasing ? 'جاري...' : 'تفعيل'}
</motion.button>
)}
</div>
) : (
<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"
style={{ boxShadow: 'inset 0 2px 4px rgba(0,0,0,0.2)' }}
>
{selectedItem.price_gems ? (
<>
<Gem size={18} className="text-[#B44DFF]" />
<span className="text-lg font-black text-[#B44DFF]">{selectedItem.price_gems}</span>
</>
) : (
<>
<Coins size={18} className="text-[#FFC83D]" />
<span className="text-lg font-black text-[#FFC83D]">{selectedItem.price_coins}</span>
</>
)}
</div>
<motion.button
whileTap={{ scale: 0.93 }}
onClick={handlePurchase}
disabled={
purchasing ||
(selectedItem.price_gems
? (profile?.gems || 0) < (selectedItem.price_gems || 0)
: (profile?.coins || 0) < (selectedItem.price_coins || 0))
}
className="btn-3d w-full py-3 rounded-xl bg-gradient-to-b from-[#FFC83D] to-[#E5A800] text-background text-sm font-black disabled:opacity-50 disabled:grayscale"
>
{purchasing ? 'جاري...' : 'شراء'}
</motion.button>
{(selectedItem.price_gems
? (profile?.gems || 0) < (selectedItem.price_gems || 0)
: (profile?.coins || 0) < (selectedItem.price_coins || 0)) && (
<p className="text-[11px] text-[#FF5252] text-center font-bold">رصيد غير كافي</p>
)}
</div>
)}
</>
)}
</div>
</motion.div>
</motion.div>
)}
......
import { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { Trophy, Users, Clock, Coins, Gem } from 'lucide-react'
import { Trophy, Users, Clock, Coins, Gem, Shield } 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'
......@@ -33,21 +31,11 @@ const TIME_CONTROL_LABELS: Record<string, string> = {
classical: 'كلاسيكي',
}
function StatusBadge({ status }: { status: string }) {
const config: Record<string, { label: string; classes: string }> = {
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-[#FF5252]/15 text-[#FF5252] border-[#FF5252]/50' },
}
const c = config[status] ?? config.completed
return (
<span className={`px-3 py-1 rounded-full text-[10px] font-black border-2 ${c.classes}`}>
{c.label}
</span>
)
const STATUS_CONFIG: Record<string, { label: string; color: string; bg: string }> = {
registration: { label: 'مفتوحة', color: '#00E5CC', bg: 'rgba(0,229,204,0.15)' },
in_progress: { label: 'جارية', color: '#FFC83D', bg: 'rgba(255,200,61,0.15)' },
completed: { label: 'منتهية', color: '#6E748C', bg: 'rgba(110,116,140,0.15)' },
cancelled: { label: 'ملغاة', color: '#FF5252', bg: 'rgba(255,82,82,0.15)' },
}
export function TournamentsPage() {
......@@ -60,22 +48,32 @@ export function TournamentsPage() {
return (
<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>
{/* Header - Banner shape */}
<div className="flex items-center gap-3">
<div className="w-12 h-12 flex items-center justify-center rounded-xl bg-[#FFC83D]/15 border-3 border-[#FFC83D]/40"
style={{ clipPath: 'polygon(10% 0%, 90% 0%, 100% 50%, 90% 100%, 10% 100%, 0% 50%)' }}
>
<Trophy size={22} className="text-[#FFC83D]" />
</div>
<div>
<h1 className="text-2xl font-black text-text-primary">البطولات</h1>
<div className="h-[3px] mt-1 w-16 rounded-full bg-gradient-to-l from-[#FFC83D] to-transparent" />
</div>
</div>
{/* Filter tabs - thick bordered buttons */}
<div className="flex gap-2 overflow-x-auto no-scrollbar pb-1">
{FILTERS.map((f) => (
<motion.button
key={f.label}
whileTap={{ scale: 0.93 }}
onClick={() => setActiveFilter(f.value)}
className={`px-4 py-2 rounded-xl text-xs font-bold whitespace-nowrap border-2 transition-colors ${
className={`px-5 py-2.5 rounded-xl text-xs font-black whitespace-nowrap border-3 transition-all ${
activeFilter === f.value
? 'bg-[#00E5CC] border-[#00E5CC] text-background font-black'
: 'bg-surface-2 text-text-muted border-border hover:border-[#00E5CC]/40'
? 'bg-gradient-to-b from-[#FFC83D] to-[#E5A800] border-[#FFC83D] text-background shadow-[0_4px_0_#B8860B]'
: 'bg-surface-2 text-text-muted border-border'
}`}
style={activeFilter !== f.value ? { boxShadow: 'inset 0 2px 4px rgba(0,0,0,0.3)' } : {}}
transition={{ type: 'spring', stiffness: 500, damping: 20 }}
>
{f.label}
......@@ -86,105 +84,149 @@ export function TournamentsPage() {
{loading ? (
<div className="flex flex-col gap-4">
{[1, 2, 3].map((i) => (
<div key={i} className="rounded-2xl bg-surface-1 border-3 border-border p-5 animate-pulse">
<div key={i} className="game-panel animate-pulse !p-5">
<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">
<div className="flex flex-col items-center justify-center py-16 gap-5">
<motion.div
className="w-20 h-20 rounded-2xl bg-surface-2 border-3 border-[#FFC83D]/30 flex items-center justify-center"
className="w-24 h-24 flex items-center justify-center"
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)' }}
initial={{ scale: 0, rotate: -10 }}
animate={{ scale: 1, rotate: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 18 }}
>
<Trophy size={32} className="text-[#FFC83D]" />
<div className="w-full h-full bg-gradient-to-br from-surface-2 to-surface-3 flex items-center justify-center"
style={{ clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)' }}
>
<Trophy size={36} className="text-[#FFC83D]/60" />
</div>
</motion.div>
<p className="text-text-muted text-sm font-bold">لا توجد بطولات</p>
<p className="text-text-muted text-sm font-black">لا توجد بطولات</p>
</div>
) : (
<div className="flex flex-col gap-4">
<AnimatePresence>
{tournaments.map((t, i) => (
<motion.div
key={t.id}
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 relative">
<div className="absolute top-4 left-4">
<StatusBadge status={t.status} />
</div>
<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={12} />
{TIME_CONTROL_LABELS[t.time_control] ?? t.time_control}
</span>
<span className="flex items-center gap-1">
<Users size={12} />
{t.registrations_count}/{t.max_players}
</span>
</div>
{tournaments.map((t, i) => {
const statusCfg = STATUS_CONFIG[t.status] ?? STATUS_CONFIG.completed
const playerPercent = Math.min((t.registrations_count / t.max_players) * 100, 100)
return (
<motion.div
key={t.id}
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 }}
>
<div className="game-panel relative overflow-hidden !p-5">
{/* Status ribbon - diagonal corner badge */}
<div
className="absolute top-0 left-0 w-28 h-7 flex items-center justify-center"
style={{
background: statusCfg.bg,
borderBottom: `2px solid ${statusCfg.color}`,
borderRight: `2px solid ${statusCfg.color}`,
borderBottomRightRadius: '12px',
}}
>
<span className="text-[10px] font-black" style={{ color: statusCfg.color }}>
{statusCfg.label}
</span>
</div>
{(t.prize_pool_coins > 0 || t.prize_pool_gems > 0) && (
<div className="flex items-center gap-3">
{t.prize_pool_coins > 0 && (
<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.5 text-[#B44DFF] font-black text-sm">
<Gem size={14} className="text-[#B44DFF]" />
{t.prize_pool_gems.toLocaleString('ar-EG')}
</span>
)}
{/* Tournament name */}
<h3 className="text-lg font-black leading-tight mt-5 mb-3">
{t.name_ar || t.name}
</h3>
{/* Info row */}
<div className="flex items-center gap-3 text-xs text-text-muted font-bold mb-3">
<span className="flex items-center gap-1.5 bg-surface-2 border-2 border-border rounded-lg px-2.5 py-1">
{FORMAT_LABELS[t.format] ?? t.format}
</span>
<span className="flex items-center gap-1">
<Clock size={12} />
{TIME_CONTROL_LABELS[t.time_control] ?? t.time_control}
</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">
مسجل
{/* Prize display - treasure row */}
{(t.prize_pool_coins > 0 || t.prize_pool_gems > 0) && (
<div className="flex items-center gap-3 mb-3 p-2.5 rounded-xl bg-surface-2/50 border-2 border-[#FFC83D]/20"
style={{ boxShadow: 'inset 0 2px 6px rgba(0,0,0,0.2)' }}
>
{t.prize_pool_coins > 0 && (
<span className="flex items-center gap-1.5 text-[#FFC83D] font-black text-sm">
<Coins size={16} className="text-[#FFC83D]" />
{t.prize_pool_coins.toLocaleString('ar-EG')}
</span>
)}
{t.prize_pool_gems > 0 && (
<span className="flex items-center gap-1.5 text-[#B44DFF] font-black text-sm">
<Gem size={16} className="text-[#B44DFF]" />
{t.prize_pool_gems.toLocaleString('ar-EG')}
</span>
)}
</div>
)}
{/* Player count - game progress bar */}
<div className="flex items-center gap-2.5 mb-3">
<Users size={13} className="text-text-muted shrink-0" />
<div className="flex-1 h-3 rounded-full bg-surface-3 border-2 border-border overflow-hidden"
style={{ boxShadow: 'inset 0 2px 4px rgba(0,0,0,0.3)' }}
>
<motion.div
className="h-full rounded-full"
style={{
background: 'linear-gradient(90deg, #00E5CC, #00B8A3)',
boxShadow: 'inset 0 -1px 2px rgba(0,0,0,0.2), 0 0 6px rgba(0,229,204,0.3)',
}}
initial={{ width: 0 }}
animate={{ width: `${playerPercent}%` }}
transition={{ duration: 0.8, ease: 'easeOut' }}
/>
</div>
<span className="text-[11px] font-black text-text-muted whitespace-nowrap">
{t.registrations_count}/{t.max_players}
</span>
</div>
{/* Register button */}
{t.status === 'registration' && user && (
<div className="flex justify-center mt-2">
{isRegistered(t.id) ? (
<div className="flex items-center gap-2.5">
<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.93 }}
onClick={() => unregister(t.id)}
className="btn-3d px-3.5 py-2 rounded-xl bg-[#FF5252] text-white text-xs font-black"
>
إلغاء
</motion.button>
</div>
) : (
<motion.button
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"
onClick={() => register(t.id)}
className="btn-3d w-[75%] py-3 rounded-xl bg-gradient-to-b from-[#FFC83D] to-[#E5A800] text-background text-sm font-black"
>
إلغاء
سجل الان
</motion.button>
</div>
) : (
<Button
variant="cyan"
size="sm"
onClick={() => register(t.id)}
className="w-[80%] mx-auto"
>
سجل
</Button>
)}
</div>
)}
</Card>
</motion.div>
))}
)}
</div>
)}
</div>
</motion.div>
)
})}
</AnimatePresence>
</div>
)}
......
{
"status": "failed",
"failedTests": []
}
\ No newline at end of file
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