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

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

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

- New color palette: brighter gold, vibrant coral/cyan/purple
- Thick bordered cards, push-effect buttons, animated glow rings
- app-container system: centered content, generous gutters
- All 14 pages rewritten with the new design language
- No edge-to-edge elements anywhere
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent f574992e
...@@ -23,7 +23,7 @@ export function DailyRewardModal({ open, streak, reward, loading, onClaim, onClo ...@@ -23,7 +23,7 @@ export function DailyRewardModal({ open, streak, reward, loading, onClaim, onClo
onClick={onClose} onClick={onClose}
> >
<motion.div <motion.div
className="w-full max-w-[320px] rounded-3xl bg-surface-1 border border-border p-7 flex flex-col items-center gap-5 relative overflow-hidden" className="w-full max-w-[340px] rounded-3xl bg-surface-1 border-3 border-border p-7 flex flex-col items-center gap-5 relative overflow-hidden"
initial={{ scale: 0.8, opacity: 0 }} initial={{ scale: 0.8, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }} animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.8, opacity: 0 }} exit={{ scale: 0.8, opacity: 0 }}
...@@ -33,7 +33,7 @@ export function DailyRewardModal({ open, streak, reward, loading, onClaim, onClo ...@@ -33,7 +33,7 @@ export function DailyRewardModal({ open, streak, reward, loading, onClaim, onClo
<div className="absolute top-0 left-0 w-full h-32 bg-gradient-to-b from-gold/8 to-transparent" /> <div className="absolute top-0 left-0 w-full h-32 bg-gradient-to-b from-gold/8 to-transparent" />
<motion.div <motion.div
className="w-20 h-20 rounded-full bg-gradient-to-br from-gold/20 to-gold/5 border-2 border-gold/40 flex items-center justify-center relative" className="w-20 h-20 rounded-full bg-gradient-to-br from-gold/20 to-gold/5 border-3 border-gold/50 flex items-center justify-center relative"
animate={{ rotate: [0, 5, -5, 0] }} animate={{ rotate: [0, 5, -5, 0] }}
transition={{ duration: 3, repeat: Infinity }} transition={{ duration: 3, repeat: Infinity }}
> >
...@@ -73,7 +73,7 @@ export function DailyRewardModal({ open, streak, reward, loading, onClaim, onClo ...@@ -73,7 +73,7 @@ export function DailyRewardModal({ open, streak, reward, loading, onClaim, onClo
size="lg" size="lg"
onClick={onClaim} onClick={onClaim}
loading={loading} loading={loading}
className="w-full" className="w-[80%] mx-auto"
> >
استلم المكافأة استلم المكافأة
</Button> </Button>
......
...@@ -10,9 +10,9 @@ export function AppShell() { ...@@ -10,9 +10,9 @@ export function AppShell() {
useNotifications() useNotifications()
return ( return (
<div className="flex flex-col min-h-dvh bg-[#0a0a12]"> <div className="flex flex-col min-h-dvh bg-background">
<Header /> <Header />
<main className="flex-1 pb-24 overflow-y-auto"> <main className="flex-1 pb-28 overflow-y-auto">
<Outlet /> <Outlet />
</main> </main>
<BottomNav /> <BottomNav />
......
...@@ -15,37 +15,48 @@ export function BottomNav() { ...@@ -15,37 +15,48 @@ export function BottomNav() {
const navigate = useNavigate() const navigate = useNavigate()
return ( return (
<nav className="fixed bottom-0 left-0 right-0 z-50 px-5 pb-3 pt-1"> <nav className="fixed bottom-0 left-0 right-0 z-50 px-4 pb-3 pt-1 md:px-6">
<div className="flex items-center justify-around px-3 py-3 bg-surface-2 border border-border rounded-[18px] shadow-2xl shadow-black/40"> <div className="app-container !p-0">
{NAV_ITEMS.map((item) => { <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">
const isActive = location.pathname === item.path {NAV_ITEMS.map((item) => {
const Icon = item.icon const isActive = location.pathname === item.path
const Icon = item.icon
return ( return (
<motion.button <motion.button
key={item.path} key={item.path}
onClick={() => navigate(item.path)} onClick={() => navigate(item.path)}
className="flex flex-col items-center gap-1 px-2 py-1 rounded-xl relative" className={`flex flex-col items-center gap-0.5 px-3 py-2 rounded-xl relative ${
whileTap={{ scale: 0.85 }} isActive ? 'bg-gold/15' : ''
> }`}
<Icon whileTap={{ scale: 0.8 }}
size={20} whileHover={{ scale: 1.05 }}
className={isActive ? 'text-gold' : 'text-text-muted'} >
strokeWidth={isActive ? 2.5 : 1.8}
/>
<span className={`text-[9px] font-semibold ${isActive ? 'text-gold' : 'text-text-muted'}`}>
{item.label}
</span>
{isActive && (
<motion.div <motion.div
className="absolute -bottom-1 w-4 h-[3px] rounded-full bg-gold" animate={isActive ? { y: -2 } : { y: 0 }}
layoutId="nav-dot" transition={{ type: 'spring', stiffness: 500, damping: 20 }}
style={{ boxShadow: '0 0 6px rgba(212, 168, 67, 0.7)' }} >
/> <Icon
)} size={22}
</motion.button> 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>
)
})}
</div>
</div> </div>
</nav> </nav>
) )
......
import { motion } from 'framer-motion' import { motion } from 'framer-motion'
import { Bell, Coins } from 'lucide-react' import { Bell, Coins, Gem } from 'lucide-react'
import { useNotificationStore } from '../../stores/notificationStore' import { useNotificationStore } from '../../stores/notificationStore'
import { useAuthStore } from '../../stores/authStore' import { useAuthStore } from '../../stores/authStore'
import { AnimatedIcon } from '../icons/AnimatedIcon'
import { GoldCrown } from '../icons/GoldCrown' import { GoldCrown } from '../icons/GoldCrown'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
...@@ -12,32 +11,55 @@ export function Header() { ...@@ -12,32 +11,55 @@ export function Header() {
const navigate = useNavigate() const navigate = useNavigate()
return ( return (
<header className="sticky top-0 z-50 px-5 pt-3 pb-1"> <header className="sticky top-0 z-50 px-4 pt-3 pb-1 md:px-6">
<div className="flex items-center justify-between px-5 py-3 bg-surface-2 border border-border rounded-[18px]"> <div className="app-container !p-0">
<div className="flex items-center gap-2.5"> <div className="flex items-center justify-between px-4 py-2.5 bg-surface-2 border-3 border-border rounded-2xl shadow-lg shadow-black/30">
<GoldCrown size={24} animate={false} /> <div className="flex items-center gap-2">
<span className="text-sm font-bold text-gold tracking-wide">EL3AB</span> <GoldCrown size={28} animate={false} />
</div> <span className="text-base font-black text-gold tracking-wider">EL3AB</span>
</div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-2">
{profile && ( {profile && (
<div className="flex items-center gap-1.5 px-3 py-1 rounded-full bg-surface-3/80 border border-gold/20"> <>
<Coins size={12} className="text-gold" /> <motion.div
<span className="text-[11px] font-bold text-gold">{profile.coins}</span> className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-gold/15 border-2 border-gold/40"
</div> whileTap={{ scale: 0.9 }}
)} >
<Coins size={14} className="text-gold" />
<span className="text-xs font-black text-gold">{profile.coins}</span>
</motion.div>
<div className="relative" onClick={() => navigate('/notifications')}> {profile.gems > 0 && (
<AnimatedIcon icon={Bell} size={18} color="var(--color-text-secondary)" onClick={() => {}} /> <motion.div
{unreadCount > 0 && ( className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-purple/15 border-2 border-purple/40"
<motion.div whileTap={{ scale: 0.9 }}
className="absolute -top-1 -right-1 w-3.5 h-3.5 rounded-full bg-coral flex items-center justify-center" >
initial={{ scale: 0 }} <Gem size={12} className="text-purple" />
animate={{ scale: 1 }} <span className="text-xs font-black text-purple">{profile.gems}</span>
> </motion.div>
<span className="text-[8px] font-bold text-white">{unreadCount > 9 ? '9+' : unreadCount}</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>
)}
</motion.button>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -9,11 +9,11 @@ interface PageTransitionProps { ...@@ -9,11 +9,11 @@ interface PageTransitionProps {
export function PageTransition({ children, className = '' }: PageTransitionProps) { export function PageTransition({ children, className = '' }: PageTransitionProps) {
return ( return (
<motion.div <motion.div
className={`max-w-[360px] mx-auto w-full ${className}`} className={`app-container py-6 flex flex-col gap-6 ${className}`}
initial={{ opacity: 0, x: -20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, x: 20 }} exit={{ opacity: 0, y: -20 }}
transition={{ type: 'spring', stiffness: 300, damping: 30 }} transition={{ type: 'spring', stiffness: 300, damping: 28 }}
> >
{children} {children}
</motion.div> </motion.div>
......
...@@ -5,7 +5,7 @@ import { Loader2 } from 'lucide-react' ...@@ -5,7 +5,7 @@ import { Loader2 } from 'lucide-react'
interface ButtonProps { interface ButtonProps {
children: ReactNode children: ReactNode
onClick?: () => void onClick?: () => void
variant?: 'gold' | 'ghost' | 'coral' | 'cyan' variant?: 'gold' | 'ghost' | 'coral' | 'cyan' | 'purple'
size?: 'sm' | 'md' | 'lg' size?: 'sm' | 'md' | 'lg'
disabled?: boolean disabled?: boolean
loading?: boolean loading?: boolean
...@@ -14,16 +14,17 @@ interface ButtonProps { ...@@ -14,16 +14,17 @@ interface ButtonProps {
} }
const variants = { const variants = {
gold: 'bg-gradient-to-l from-gold to-gold-light text-background font-bold shadow-lg shadow-gold/20', gold: 'bg-gradient-to-b from-gold-light to-gold text-background font-black shadow-lg shadow-gold/30 border-b-4 border-gold-muted',
ghost: 'bg-surface-2 border border-border text-text-primary font-semibold hover:border-gold/40 hover:bg-surface-3', ghost: 'bg-surface-2 border-2 border-border text-text-primary font-bold hover:border-gold/50 hover:bg-surface-3',
coral: 'bg-gradient-to-l from-coral to-coral/80 text-white font-bold shadow-md shadow-coral/15', coral: 'bg-gradient-to-b from-[#FF7070] to-coral text-white font-black shadow-lg shadow-coral/25 border-b-4 border-[#B83A3A]',
cyan: 'bg-gradient-to-l from-cyan to-cyan/80 text-background font-bold shadow-md shadow-cyan/15', cyan: 'bg-gradient-to-b from-[#33FFE5] to-cyan text-background font-black shadow-lg shadow-cyan/25 border-b-4 border-[#009E8E]',
purple: 'bg-gradient-to-b from-[#CC70FF] to-purple text-white font-black shadow-lg shadow-purple/25 border-b-4 border-[#6B3D8F]',
} }
const sizes = { const sizes = {
sm: 'px-5 py-2.5 text-sm rounded-xl gap-2 min-w-[80px]', sm: 'px-5 py-2 text-sm rounded-xl gap-2 min-w-[90px]',
md: 'px-7 py-3.5 text-base rounded-xl gap-2.5 min-w-[100px]', md: 'px-7 py-3 text-base rounded-2xl gap-2.5 min-w-[120px]',
lg: 'px-8 py-4.5 text-lg rounded-2xl gap-3 min-w-[120px]', lg: 'px-10 py-4 text-lg rounded-2xl gap-3 min-w-[160px]',
} }
export function Button({ export function Button({
...@@ -41,9 +42,10 @@ export function Button({ ...@@ -41,9 +42,10 @@ export function Button({
type={type} type={type}
onClick={onClick} onClick={onClick}
disabled={disabled || loading} disabled={disabled || loading}
className={`inline-flex items-center justify-center transition-all duration-200 ${variants[variant]} ${sizes[size]} ${disabled ? 'opacity-50 cursor-not-allowed' : ''} ${className}`} className={`inline-flex items-center justify-center transition-all uppercase tracking-wide ${variants[variant]} ${sizes[size]} ${disabled ? 'opacity-50 cursor-not-allowed saturate-50' : ''} ${className}`}
whileTap={!disabled ? { scale: 0.96 } : undefined} whileTap={!disabled ? { scale: 0.92, y: 2 } : undefined}
transition={{ type: 'spring', stiffness: 500, damping: 20 }} whileHover={!disabled ? { scale: 1.03 } : undefined}
transition={{ type: 'spring', stiffness: 600, damping: 20 }}
> >
{loading && <Loader2 size={18} className="animate-spin" />} {loading && <Loader2 size={18} className="animate-spin" />}
{children} {children}
......
...@@ -6,16 +6,25 @@ interface CardProps { ...@@ -6,16 +6,25 @@ interface CardProps {
className?: string className?: string
glow?: boolean glow?: boolean
onClick?: () => void onClick?: () => void
variant?: 'default' | 'gold' | 'elevated'
} }
export function Card({ children, className = '', glow = false, onClick }: CardProps) { export function Card({ children, className = '', glow = false, onClick, variant = 'default' }: CardProps) {
const base = 'rounded-2xl p-5 border-3 transition-all'
const variantStyles = {
default: `bg-surface-1 border-border ${glow ? 'border-gold shadow-[0_0_20px_rgba(255,200,60,0.2)]' : ''}`,
gold: 'bg-gradient-to-br from-surface-2 to-surface-1 border-gold/60 shadow-[0_0_16px_rgba(255,200,60,0.15)]',
elevated: 'bg-surface-2 border-border shadow-xl shadow-black/40',
}
return ( return (
<motion.div <motion.div
className={`rounded-2xl bg-surface-1 border border-border p-5 ${glow ? 'border-gold/50 shadow-[0_0_20px_rgba(212,168,67,0.15)]' : ''} ${onClick ? 'cursor-pointer active:scale-[0.98]' : ''} ${className}`} className={`${base} ${variantStyles[variant]} ${onClick ? 'cursor-pointer' : ''} ${className}`}
whileHover={onClick ? { y: -2, boxShadow: '0 8px 30px rgba(212,168,67,0.12)' } : undefined} whileHover={onClick ? { y: -4, scale: 1.01, boxShadow: '0 12px 32px rgba(0,0,0,0.5)' } : undefined}
whileTap={onClick ? { scale: 0.98 } : undefined} whileTap={onClick ? { scale: 0.97 } : undefined}
onClick={onClick} onClick={onClick}
transition={{ type: 'spring', stiffness: 400, damping: 25 }} transition={{ type: 'spring', stiffness: 400, damping: 22 }}
> >
{children} {children}
</motion.div> </motion.div>
......
...@@ -23,8 +23,8 @@ export function Input({ ...@@ -23,8 +23,8 @@ export function Input({
const [focused, setFocused] = useState(false) const [focused, setFocused] = useState(false)
return ( return (
<div className="flex flex-col gap-1.5"> <div className="flex flex-col gap-2">
<label className="text-sm font-semibold text-text-secondary">{label}</label> <label className="text-sm font-bold text-text-secondary">{label}</label>
<div className="relative"> <div className="relative">
<input <input
type={type} type={type}
...@@ -34,19 +34,21 @@ export function Input({ ...@@ -34,19 +34,21 @@ export function Input({
disabled={disabled} disabled={disabled}
onFocus={() => setFocused(true)} onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)} onBlur={() => setFocused(false)}
className="w-full px-4 py-3 rounded-xl bg-surface-3 text-text-primary placeholder:text-text-muted border-2 border-transparent outline-none transition-colors focus:border-gold/60 disabled:opacity-50" className={`w-full px-4 py-3.5 rounded-xl bg-surface-3 text-text-primary placeholder:text-text-muted border-3 outline-none transition-all font-semibold ${
focused ? 'border-gold shadow-[0_0_12px_rgba(255,200,60,0.2)]' : 'border-border'
} ${error ? 'border-coral' : ''} disabled:opacity-50`}
dir="auto" dir="auto"
/> />
<motion.div <motion.div
className="absolute bottom-0 left-0 right-0 h-0.5 bg-gold rounded-full origin-center" className="absolute bottom-0 left-2 right-2 h-[3px] bg-gold rounded-full"
initial={{ scaleX: 0 }} initial={{ scaleX: 0 }}
animate={{ scaleX: focused ? 1 : 0 }} animate={{ scaleX: focused ? 1 : 0 }}
transition={{ type: 'spring', stiffness: 400, damping: 30 }} transition={{ type: 'spring', stiffness: 500, damping: 30 }}
/> />
</div> </div>
{error && ( {error && (
<motion.span <motion.span
className="text-xs text-coral" className="text-xs text-coral font-bold"
initial={{ opacity: 0, y: -4 }} initial={{ opacity: 0, y: -4 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
> >
......
...@@ -66,11 +66,11 @@ function ToastItem({ ...@@ -66,11 +66,11 @@ function ToastItem({
animate={{ opacity: 1, y: 0, scale: 1 }} animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: -30, scale: 0.9 }} exit={{ opacity: 0, y: -30, scale: 0.9 }}
transition={{ type: 'spring', stiffness: 400, damping: 25 }} transition={{ type: 'spring', stiffness: 400, damping: 25 }}
className={`pointer-events-auto w-full rounded-xl border p-3.5 flex items-center gap-3 backdrop-blur-xl shadow-lg ${colorClass}`} className={`pointer-events-auto w-full rounded-xl border-2 p-3.5 flex items-center gap-3 backdrop-blur-xl shadow-xl shadow-black/30 ${colorClass}`}
onClick={() => onDismiss(id)} onClick={() => onDismiss(id)}
> >
<Icon size={18} className="flex-shrink-0" /> <Icon size={18} className="flex-shrink-0" />
<span className="text-sm font-medium text-text-primary">{title}</span> <span className="text-sm font-bold text-text-primary">{title}</span>
</motion.div> </motion.div>
) )
} }
@import "tailwindcss"; @import "tailwindcss";
@theme { @theme {
--color-background: #06060E; /* === BACKGROUNDS === */
--color-surface-1: #141420; --color-background: #0B0E1A;
--color-surface-2: #1C1C30; --color-surface-1: #151929;
--color-surface-3: #26263C; --color-surface-2: #1E2340;
--color-border: #32324E; --color-surface-3: #2A3055;
--color-border-gold: rgba(212, 168, 67, 0.25); --color-border: #3D4570;
--color-border-gold: rgba(255, 200, 60, 0.35);
--color-gold: #D4A843;
--color-gold-light: #E8C55A; /* === BRAND (vibrant Brawl Stars palette) === */
--color-gold-muted: #B8964A; --color-gold: #FFC83D;
--color-coral: #E06B4A; --color-gold-light: #FFE066;
--color-royal-blue: #5B7FD6; --color-gold-muted: #C9972E;
--color-cyan: #4ECDC4; --color-coral: #FF5252;
--color-purple: #7B5EA7; --color-royal-blue: #4D8BFF;
--color-cyan: #00E5CC;
--color-purple: #B44DFF;
--color-electric-blue: #3B82F6; --color-electric-blue: #3B82F6;
--color-green: #4ADE80;
--color-orange: #FF8C42;
--color-text-primary: #F5F5F5; /* === TEXT === */
--color-text-secondary: #A0A0B8; --color-text-primary: #FFFFFF;
--color-text-muted: #6B6B80; --color-text-secondary: #B8BDD6;
--color-text-muted: #6E748C;
--color-win: #4ECDC4; /* === SEMANTIC === */
--color-loss: #E06B4A; --color-win: #00E5CC;
--color-draw: #A0A0B8; --color-loss: #FF5252;
--color-draw: #B8BDD6;
--font-family-cairo: 'Cairo', sans-serif; --font-family-cairo: 'Cairo', sans-serif;
} }
...@@ -52,8 +58,9 @@ body { ...@@ -52,8 +58,9 @@ body {
flex-direction: column; flex-direction: column;
} }
/* === SCROLLBAR === */
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 4px; width: 6px;
} }
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
...@@ -62,9 +69,101 @@ body { ...@@ -62,9 +69,101 @@ body {
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
background: var(--color-border); background: var(--color-border);
border-radius: 2px; border-radius: 3px;
} }
::-webkit-scrollbar-thumb:hover { ::-webkit-scrollbar-thumb:hover {
background: var(--color-gold-muted); background: var(--color-gold-muted);
} }
/* === GAME CARD THICK BORDERS === */
.game-card {
border: 3px solid var(--color-border);
border-radius: 20px;
background: var(--color-surface-1);
transition: transform 0.15s, box-shadow 0.15s;
}
.game-card:hover {
transform: translateY(-3px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
}
.game-card-gold {
border-color: var(--color-gold);
box-shadow: 0 0 20px rgba(255, 200, 60, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.05);
}
/* === PLAYFUL BUTTON PUSH EFFECT === */
.btn-push {
position: relative;
border-bottom: 4px solid rgba(0, 0, 0, 0.3);
transition: all 0.1s;
}
.btn-push:active {
border-bottom-width: 1px;
transform: translateY(3px);
}
/* === GLOW ANIMATIONS === */
@keyframes pulse-glow {
0%, 100% { box-shadow: 0 0 8px rgba(255, 200, 60, 0.3); }
50% { box-shadow: 0 0 24px rgba(255, 200, 60, 0.6); }
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-6px); }
}
.animate-pulse-glow {
animation: pulse-glow 2s ease-in-out infinite;
}
.animate-float {
animation: float 3s ease-in-out infinite;
}
/* === RESPONSIVE CONTAINER === */
.app-container {
width: 100%;
max-width: 480px;
margin: 0 auto;
padding-left: 20px;
padding-right: 20px;
}
@media (min-width: 768px) {
.app-container {
max-width: 520px;
padding-left: 24px;
padding-right: 24px;
}
}
@media (min-width: 1024px) {
.app-container {
max-width: 560px;
padding-left: 32px;
padding-right: 32px;
}
}
/* === DESKTOP LAYOUT === */
@media (min-width: 1024px) {
.desktop-sidebar {
position: fixed;
top: 0;
right: 0;
width: 260px;
height: 100dvh;
border-left: 2px solid var(--color-border);
background: var(--color-surface-1);
z-index: 40;
}
.desktop-main {
margin-right: 260px;
}
}
...@@ -23,9 +23,9 @@ export const GAMES = [ ...@@ -23,9 +23,9 @@ export const GAMES = [
] as const ] as const
export const RARITY_COLORS = { export const RARITY_COLORS = {
common: '#6B6B80', common: '#6E748C',
uncommon: '#5B7FD6', uncommon: '#4D8BFF',
rare: '#7B5EA7', rare: '#B44DFF',
epic: '#E06B4A', epic: '#FF5252',
legendary: '#D4A843', legendary: '#FFC83D',
} as const } as const
...@@ -8,13 +8,13 @@ import { fetchBots, getBotPortraitUrl, type Bot } from '../lib/stockfish' ...@@ -8,13 +8,13 @@ import { fetchBots, getBotPortraitUrl, type Bot } from '../lib/stockfish'
import { ChevronRight, Swords } from 'lucide-react' import { ChevronRight, Swords } from 'lucide-react'
const DIFFICULTY_COLORS: Record<string, string> = { const DIFFICULTY_COLORS: Record<string, string> = {
beginner: '#4ECDC4', beginner: '#00E5CC',
defensive: '#5B7FD6', defensive: '#4D8BFF',
aggressive: '#E06B4A', aggressive: '#FF5252',
positional: '#7B5EA7', positional: '#B44DFF',
creative: '#D4A843', creative: '#FFC83D',
solid: '#6B6B80', solid: '#6E748C',
near_perfect: '#FFD700', near_perfect: '#FFE066',
} }
const TOTAL_STRENGTH_DOTS = 7 const TOTAL_STRENGTH_DOTS = 7
...@@ -39,55 +39,50 @@ export function BotSelectPage() { ...@@ -39,55 +39,50 @@ export function BotSelectPage() {
if (loading) { if (loading) {
return ( return (
<PageTransition className="px-6 py-8 flex flex-col items-center justify-center min-h-[60vh]"> <PageTransition className="flex flex-col items-center justify-center min-h-[60vh]">
<div className="w-10 h-10 border-2 border-gold/30 border-t-gold rounded-full animate-spin" /> <div className="w-12 h-12 border-3 border-gold/30 border-t-gold rounded-full animate-spin" />
<p className="mt-4 text-sm text-text-muted">جاري تحميل الروبوتات...</p> <p className="mt-4 text-sm text-text-muted font-bold">جاري تحميل الروبوتات...</p>
</PageTransition> </PageTransition>
) )
} }
return ( return (
<PageTransition className="px-7 py-7 flex flex-col gap-6 pb-36"> <PageTransition className="pb-36">
{/* Header */}
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<motion.button <motion.button
onClick={() => navigate(-1)} onClick={() => navigate(-1)}
className="w-10 h-10 rounded-full bg-surface-2 border border-border flex items-center justify-center" className="w-11 h-11 rounded-xl bg-surface-2 border-2 border-border flex items-center justify-center"
whileTap={{ scale: 0.9 }} whileTap={{ scale: 0.85 }}
> >
<ChevronRight size={20} className="text-text-secondary" /> <ChevronRight size={20} className="text-text-secondary" />
</motion.button> </motion.button>
<div> <div>
<h1 className="text-xl font-bold">العب ضد الروبوت</h1> <h1 className="text-xl font-black">العب ضد الروبوت</h1>
<p className="text-xs text-text-muted mt-1">اختر خصمك حسب مستواك</p> <p className="text-xs text-text-muted font-semibold mt-0.5">اختر خصمك حسب مستواك</p>
</div> </div>
</div> </div>
{/* Bot List */}
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
{bots.map((bot, i) => { {bots.map((bot, i) => {
const isSelected = selectedBot === bot.id const isSelected = selectedBot === bot.id
const diffColor = DIFFICULTY_COLORS[bot.style] || '#6B6B80' const diffColor = DIFFICULTY_COLORS[bot.style] || '#6E748C'
const strengthLevel = Math.min(i + 1, TOTAL_STRENGTH_DOTS) const strengthLevel = Math.min(i + 1, TOTAL_STRENGTH_DOTS)
return ( return (
<motion.div <motion.div
key={bot.id} key={bot.id}
initial={{ opacity: 0, y: 15 }} initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, x: 0 }}
transition={{ delay: i * 0.05, type: 'spring', stiffness: 400, damping: 25 }} transition={{ delay: i * 0.06, type: 'spring', stiffness: 400, damping: 22 }}
> >
<Card <Card
glow={isSelected} glow={isSelected}
onClick={() => setSelectedBot(bot.id)} onClick={() => setSelectedBot(bot.id)}
className={`flex items-center gap-4 transition-all ${ className={`flex items-center gap-4 ${isSelected ? '!border-gold' : ''}`}
isSelected ? 'border-gold/60 scale-[1.01]' : ''
}`}
> >
{/* Portrait */}
<div <div
className="relative w-14 h-14 rounded-full overflow-hidden flex-shrink-0 border-2" className="relative w-14 h-14 rounded-xl overflow-hidden flex-shrink-0 border-2"
style={{ backgroundColor: `${diffColor}15`, borderColor: `${diffColor}40` }} style={{ backgroundColor: `${diffColor}15`, borderColor: `${diffColor}60` }}
> >
<img <img
src={getBotPortraitUrl(bot.id)} src={getBotPortraitUrl(bot.id)}
...@@ -96,45 +91,43 @@ export function BotSelectPage() { ...@@ -96,45 +91,43 @@ export function BotSelectPage() {
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none' }} onError={(e) => { (e.target as HTMLImageElement).style.display = 'none' }}
/> />
<div className="absolute inset-0 flex items-center justify-center" style={{ color: diffColor }}> <div className="absolute inset-0 flex items-center justify-center" style={{ color: diffColor }}>
<span className="text-xl font-bold">{bot.name_ar?.charAt(0) || bot.name.charAt(0)}</span> <span className="text-xl font-black">{bot.name_ar?.charAt(0) || bot.name.charAt(0)}</span>
</div> </div>
</div> </div>
{/* Info */}
<div className="flex-1 min-w-0 space-y-1.5"> <div className="flex-1 min-w-0 space-y-1.5">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<h3 className="text-sm font-bold truncate">{bot.name_ar}</h3> <h3 className="text-sm font-black truncate">{bot.name_ar}</h3>
<span <span
className="px-2 py-0.5 rounded-md text-[9px] font-bold" className="px-2 py-0.5 rounded-lg text-[9px] font-black"
style={{ backgroundColor: `${diffColor}20`, color: diffColor }} style={{ backgroundColor: `${diffColor}25`, color: diffColor }}
> >
{bot.style_ar} {bot.style_ar}
</span> </span>
</div> </div>
<p className="text-[11px] text-text-muted truncate leading-relaxed">{bot.bio_ar}</p> <p className="text-[11px] text-text-muted truncate font-semibold">{bot.bio_ar}</p>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<span className="text-[10px] text-text-muted">{bot.elo_min}-{bot.elo_max} تقييم</span> <span className="text-[10px] text-text-muted font-bold">{bot.elo_min}-{bot.elo_max}</span>
<div className="flex items-center gap-[3px]"> <div className="flex items-center gap-[3px]">
{Array.from({ length: TOTAL_STRENGTH_DOTS }).map((_, dotIndex) => ( {Array.from({ length: TOTAL_STRENGTH_DOTS }).map((_, dotIndex) => (
<div <div
key={dotIndex} key={dotIndex}
className="w-[5px] h-[5px] rounded-full" className="w-[6px] h-[6px] rounded-full"
style={{ backgroundColor: dotIndex < strengthLevel ? diffColor : `${diffColor}25` }} style={{ backgroundColor: dotIndex < strengthLevel ? diffColor : `${diffColor}20` }}
/> />
))} ))}
</div> </div>
</div> </div>
</div> </div>
{/* Check */}
{isSelected && ( {isSelected && (
<motion.div <motion.div
className="w-7 h-7 rounded-full bg-gold flex items-center justify-center flex-shrink-0" className="w-8 h-8 rounded-full bg-gold flex items-center justify-center flex-shrink-0"
initial={{ scale: 0 }} initial={{ scale: 0, rotate: -90 }}
animate={{ scale: 1 }} animate={{ scale: 1, rotate: 0 }}
transition={{ type: 'spring', stiffness: 500, damping: 20 }} transition={{ type: 'spring', stiffness: 500, damping: 15 }}
> >
<Swords size={12} className="text-background" /> <Swords size={14} className="text-background" />
</motion.div> </motion.div>
)} )}
</Card> </Card>
...@@ -143,17 +136,18 @@ export function BotSelectPage() { ...@@ -143,17 +136,18 @@ export function BotSelectPage() {
})} })}
</div> </div>
{/* CTA */}
{selectedBot && ( {selectedBot && (
<motion.div <motion.div
className="fixed bottom-0 left-0 right-0 px-5 pt-4 pb-10 bg-gradient-to-t from-background via-background/95 to-transparent" className="fixed bottom-0 left-0 right-0 px-5 pt-4 pb-10 bg-gradient-to-t from-background via-background/95 to-transparent"
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ type: 'spring', stiffness: 400, damping: 25 }} transition={{ type: 'spring', stiffness: 400, damping: 22 }}
> >
<Button onClick={startGame} className="w-full" size="lg"> <div className="app-container !p-0">
ابدا المباراة <Button onClick={startGame} className="w-[80%] mx-auto block" size="lg">
</Button> ابدا المباراة
</Button>
</div>
</motion.div> </motion.div>
)} )}
</PageTransition> </PageTransition>
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -32,35 +32,41 @@ export function LoginPage() { ...@@ -32,35 +32,41 @@ export function LoginPage() {
} }
return ( return (
<div className="min-h-dvh flex flex-col items-center justify-center px-7 relative overflow-hidden"> <div className="min-h-dvh flex items-center justify-center relative overflow-hidden bg-background">
<motion.div <motion.div
className="absolute inset-0 opacity-30" className="absolute inset-0"
style={{ style={{
background: 'radial-gradient(ellipse at 50% 30%, rgba(212,168,67,0.15) 0%, transparent 60%)', background: 'radial-gradient(ellipse at 50% 30%, rgba(255,200,61,0.12) 0%, transparent 60%)',
}} }}
animate={{ animate={{
background: [ background: [
'radial-gradient(ellipse at 50% 30%, rgba(212,168,67,0.15) 0%, transparent 60%)', 'radial-gradient(ellipse at 50% 30%, rgba(255,200,61,0.12) 0%, transparent 60%)',
'radial-gradient(ellipse at 50% 30%, rgba(212,168,67,0.08) 0%, transparent 60%)', 'radial-gradient(ellipse at 50% 30%, rgba(255,200,61,0.05) 0%, transparent 60%)',
'radial-gradient(ellipse at 50% 30%, rgba(212,168,67,0.15) 0%, transparent 60%)', 'radial-gradient(ellipse at 50% 30%, rgba(255,200,61,0.12) 0%, transparent 60%)',
], ],
}} }}
transition={{ duration: 4, repeat: Infinity, ease: 'easeInOut' }} transition={{ duration: 4, repeat: Infinity, ease: 'easeInOut' }}
/> />
<motion.div <motion.div
className="relative w-full max-w-[310px]" className="relative w-full max-w-[380px] px-5"
initial={{ opacity: 0, y: 30 }} initial={{ opacity: 0, y: 40 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 25 }} transition={{ type: 'spring', stiffness: 400, damping: 22 }}
> >
<div className="flex flex-col items-center mb-8"> <div className="flex flex-col items-center mb-8">
<GoldCrown size={64} /> <GoldCrown size={72} />
<h1 className="mt-4 text-3xl font-black text-gold">EL3AB</h1> <h1 className="mt-4 text-4xl font-black text-gold">EL3AB</h1>
<p className="mt-1 text-text-secondary">تسجيل الدخول</p> <p className="mt-2 text-lg font-bold text-text-secondary">تسجيل الدخول</p>
</div> </div>
<form onSubmit={handleLogin} className="flex flex-col gap-4 p-7 rounded-2xl bg-surface-3 border-2 border-gold/30 shadow-[0_8px_40px_rgba(0,0,0,0.6),0_0_20px_rgba(212,168,67,0.08)]"> <motion.form
onSubmit={handleLogin}
className="flex flex-col gap-4 p-6 rounded-2xl bg-surface-2 border-3 border-border shadow-xl"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ type: 'spring', stiffness: 450, damping: 25, delay: 0.1 }}
>
<Input <Input
label="البريد الالكتروني" label="البريد الالكتروني"
type="email" type="email"
...@@ -78,25 +84,26 @@ export function LoginPage() { ...@@ -78,25 +84,26 @@ export function LoginPage() {
{error && ( {error && (
<motion.p <motion.p
className="text-sm text-coral text-center" className="text-sm font-bold text-coral text-center"
initial={{ opacity: 0 }} initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1 }} animate={{ opacity: 1, scale: 1 }}
transition={{ type: 'spring', stiffness: 500, damping: 20 }}
> >
{error} {error}
</motion.p> </motion.p>
)} )}
<Button type="submit" loading={loading} className="w-[85%] mx-auto mt-2"> <Button type="submit" variant="gold" size="lg" loading={loading} className="w-[80%] mx-auto mt-2">
دخول دخول
</Button> </Button>
</form> </motion.form>
<motion.button <motion.button
onClick={() => navigate('/register')} onClick={() => navigate('/register')}
className="mt-4 w-full text-center text-sm text-text-secondary hover:text-gold transition-colors" className="mt-5 w-full text-center text-sm font-bold text-text-secondary hover:text-gold transition-colors"
whileTap={{ scale: 0.98 }} whileTap={{ scale: 0.96 }}
> >
ليس لديك حساب؟ <span className="text-gold font-semibold">سجل الان</span> ليس لديك حساب؟ <span className="text-gold font-black">سجل الان</span>
</motion.button> </motion.button>
</motion.div> </motion.div>
</div> </div>
......
...@@ -36,25 +36,25 @@ export function MatchmakingPage() { ...@@ -36,25 +36,25 @@ export function MatchmakingPage() {
} }
return ( return (
<div className="flex-1 flex flex-col items-center justify-center px-6 py-12 relative overflow-hidden"> <div className="flex-1 flex flex-col items-center justify-center px-6 py-12 relative overflow-hidden min-h-dvh bg-background">
<AnimatePresence mode="wait"> <AnimatePresence mode="wait">
{matchFound ? ( {matchFound ? (
<motion.div <motion.div
key="found" key="found"
className="flex flex-col items-center" className="flex flex-col items-center"
initial={{ scale: 0.5, opacity: 0 }} initial={{ scale: 0.3, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }} animate={{ scale: 1, opacity: 1 }}
transition={{ type: 'spring', stiffness: 300, damping: 20 }} transition={{ type: 'spring', stiffness: 300, damping: 15 }}
> >
<motion.div <motion.div
className="w-24 h-24 rounded-full bg-gold/20 border-2 border-gold flex items-center justify-center" className="w-28 h-28 rounded-full bg-gold/20 border-3 border-gold flex items-center justify-center animate-pulse-glow"
animate={{ scale: [1, 1.1, 1] }} animate={{ scale: [1, 1.15, 1] }}
transition={{ duration: 0.5, repeat: 2 }} transition={{ duration: 0.6, repeat: 2 }}
> >
<span className="text-4xl">⚔️</span> <span className="text-5xl font-black text-gold">VS</span>
</motion.div> </motion.div>
<motion.h2 <motion.h2
className="mt-6 text-xl font-bold text-gold" className="mt-6 text-2xl font-black text-gold"
initial={{ opacity: 0, y: 10 }} initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }} transition={{ delay: 0.2 }}
...@@ -68,11 +68,11 @@ export function MatchmakingPage() { ...@@ -68,11 +68,11 @@ export function MatchmakingPage() {
className="flex flex-col items-center" className="flex flex-col items-center"
exit={{ opacity: 0, scale: 0.8 }} exit={{ opacity: 0, scale: 0.8 }}
> >
<div className="relative w-40 h-40 flex items-center justify-center"> <div className="relative w-44 h-44 flex items-center justify-center">
{[0, 1, 2].map((i) => ( {[0, 1, 2].map((i) => (
<motion.div <motion.div
key={i} key={i}
className="absolute inset-0 rounded-full border-2 border-gold/30" className="absolute inset-0 rounded-full border-3 border-gold/30"
initial={{ scale: 0.5, opacity: 0.8 }} initial={{ scale: 0.5, opacity: 0.8 }}
animate={{ scale: 2.5, opacity: 0 }} animate={{ scale: 2.5, opacity: 0 }}
transition={{ transition={{
...@@ -85,16 +85,16 @@ export function MatchmakingPage() { ...@@ -85,16 +85,16 @@ export function MatchmakingPage() {
))} ))}
<motion.div <motion.div
className="w-20 h-20 rounded-full bg-gradient-to-br from-gold/20 to-gold/5 border-2 border-gold/40 flex items-center justify-center" className="w-24 h-24 rounded-full bg-gradient-to-br from-gold/25 to-surface-2 border-3 border-gold/50 flex items-center justify-center shadow-lg"
animate={{ scale: [1, 1.05, 1] }} animate={{ scale: [1, 1.08, 1], rotate: [0, 3, -3, 0] }}
transition={{ duration: 2, repeat: Infinity, ease: 'easeInOut' }} transition={{ duration: 2, repeat: Infinity, ease: 'easeInOut' }}
> >
<span className="text-3xl font-black text-gold">?</span> <span className="text-4xl font-black text-gold">?</span>
</motion.div> </motion.div>
</div> </div>
<motion.h2 <motion.h2
className="mt-8 text-xl font-bold" className="mt-8 text-xl font-black"
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
transition={{ delay: 0.3 }} transition={{ delay: 0.3 }}
...@@ -103,7 +103,7 @@ export function MatchmakingPage() { ...@@ -103,7 +103,7 @@ export function MatchmakingPage() {
</motion.h2> </motion.h2>
<motion.div <motion.div
className="mt-3 text-lg font-mono text-gold tabular-nums" className="mt-3 text-2xl font-black font-mono text-gold tabular-nums"
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
transition={{ delay: 0.4 }} transition={{ delay: 0.4 }}
...@@ -112,7 +112,7 @@ export function MatchmakingPage() { ...@@ -112,7 +112,7 @@ export function MatchmakingPage() {
</motion.div> </motion.div>
<motion.div <motion.div
className="mt-2 flex gap-1" className="mt-3 flex gap-1.5"
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
transition={{ delay: 0.5 }} transition={{ delay: 0.5 }}
...@@ -120,9 +120,9 @@ export function MatchmakingPage() { ...@@ -120,9 +120,9 @@ export function MatchmakingPage() {
{[0, 1, 2].map((i) => ( {[0, 1, 2].map((i) => (
<motion.span <motion.span
key={i} key={i}
className="w-2 h-2 rounded-full bg-gold" className="w-3 h-3 rounded-full bg-gold"
animate={{ opacity: [0.3, 1, 0.3] }} animate={{ opacity: [0.2, 1, 0.2], scale: [0.8, 1.2, 0.8] }}
transition={{ duration: 1, repeat: Infinity, delay: i * 0.3 }} transition={{ duration: 1.2, repeat: Infinity, delay: i * 0.3 }}
/> />
))} ))}
</motion.div> </motion.div>
...@@ -133,7 +133,7 @@ export function MatchmakingPage() { ...@@ -133,7 +133,7 @@ export function MatchmakingPage() {
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
transition={{ delay: 1 }} transition={{ delay: 1 }}
> >
<Button variant="ghost" onClick={handleCancel}> <Button variant="ghost" onClick={handleCancel} size="md">
الغاء الغاء
</Button> </Button>
</motion.div> </motion.div>
......
...@@ -7,7 +7,7 @@ export function NotFoundPage() { ...@@ -7,7 +7,7 @@ export function NotFoundPage() {
const navigate = useNavigate() const navigate = useNavigate()
return ( return (
<div className="flex-1 flex flex-col items-center justify-center px-6 py-12 gap-6"> <div className="flex-1 flex flex-col items-center justify-center px-6 py-12 gap-6 min-h-dvh bg-background">
<motion.div <motion.div
className="text-6xl" className="text-6xl"
animate={{ animate={{
......
import { motion } from 'framer-motion' import { motion } from 'framer-motion'
import { Bell, UserPlus, Trophy, Swords, CheckCheck, Loader2 } from 'lucide-react' import { Bell, UserPlus, Trophy, Swords, Loader2 } from 'lucide-react'
import { PageTransition } from '../components/layout/PageTransition' import { PageTransition } from '../components/layout/PageTransition'
import { Button } from '../components/ui/Button'
import { useNotifications } from '../hooks/useNotifications' import { useNotifications } from '../hooks/useNotifications'
function relativeTime(date: string): string { function relativeTime(date: string): string {
...@@ -18,104 +19,114 @@ function getIcon(type: string) { ...@@ -18,104 +19,114 @@ function getIcon(type: string) {
switch (type) { switch (type) {
case 'friend_request': case 'friend_request':
case 'friend_accepted': case 'friend_accepted':
return <UserPlus size={16} className="text-cyan" /> return <UserPlus size={18} className="text-[#00E5CC]" />
case 'match_result': case 'achievement':
case 'win': return <Trophy size={18} className="text-[#FFC83D]" />
return <Trophy size={16} className="text-gold" /> case 'match':
case 'match_invite': case 'match_invite':
case 'challenge': case 'challenge':
return <Swords size={16} className="text-coral" /> return <Swords size={18} className="text-[#FF5252]" />
default: default:
return <Bell size={16} className="text-text-muted" /> return <Bell size={18} className="text-[#B44DFF]" />
} }
} }
export function NotificationsPage() { const stagger = {
const { notifications, markAsRead, markAllRead, loading } = useNotifications() hidden: { opacity: 0 },
show: { opacity: 1, transition: { staggerChildren: 0.06 } },
}
const stagger = { const item = {
hidden: { opacity: 0 }, hidden: { opacity: 0, y: 12, scale: 0.95 },
show: { opacity: 1, transition: { staggerChildren: 0.04 } }, show: { opacity: 1, y: 0, scale: 1, transition: { type: 'spring', stiffness: 400, damping: 22 } },
} }
const item = { export function NotificationsPage() {
hidden: { opacity: 0, y: 10 }, const { notifications, markAsRead, markAllRead, loading } = useNotifications()
show: { opacity: 1, y: 0 },
}
return ( return (
<PageTransition className="px-7 py-7 flex flex-col gap-6"> <PageTransition>
<div className="flex items-center justify-between"> <div className="flex flex-col gap-5 py-6">
<h1 className="text-xl font-bold">الاشعارات</h1> <div className="flex items-center justify-between">
{notifications.some((n) => !n.is_read) && ( <h1 className="text-xl font-black">الاشعارات</h1>
<motion.button {notifications.some((n) => !n.is_read) && (
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-surface-2 border border-border text-xs text-text-muted" <Button size="sm" variant="ghost" onClick={markAllRead}>
whileTap={{ scale: 0.9 }} قراءة الكل
onClick={markAllRead} </Button>
> )}
<CheckCheck size={14} />
قراءة الكل
</motion.button>
)}
</div>
{loading ? (
<div className="flex-1 flex items-center justify-center">
<Loader2 size={24} className="animate-spin text-text-muted" />
</div> </div>
) : notifications.length === 0 ? (
<div className="flex-1 flex flex-col items-center justify-center py-12"> {loading ? (
<div className="flex-1 flex items-center justify-center py-16">
<Loader2 size={28} className="animate-spin text-[#FFC83D]" />
</div>
) : notifications.length === 0 ? (
<motion.div <motion.div
className="w-16 h-16 rounded-full bg-surface-2 border border-border flex items-center justify-center mb-4" className="flex flex-col items-center justify-center py-20 gap-4"
initial={{ scale: 0 }} initial={{ opacity: 0, scale: 0.9 }}
animate={{ scale: 1 }} animate={{ opacity: 1, scale: 1 }}
transition={{ type: 'spring', stiffness: 300, damping: 20 }} transition={{ type: 'spring', stiffness: 300, damping: 20 }}
> >
<Bell size={24} className="text-text-muted" />
</motion.div>
<p className="text-text-muted text-sm">لا توجد اشعارات</p>
</div>
) : (
<motion.div className="flex flex-col gap-2" variants={stagger} initial="hidden" animate="show">
{notifications.map((notification) => (
<motion.div <motion.div
key={notification.id} className="w-20 h-20 rounded-full bg-surface-2 border-3 border-border flex items-center justify-center"
variants={item} initial={{ scale: 0, rotate: -15 }}
className={`rounded-xl p-3.5 border cursor-pointer transition-colors ${ animate={{ scale: 1, rotate: 0 }}
notification.is_read transition={{ type: 'spring', stiffness: 300, damping: 18 }}
? 'bg-surface-1 border-border'
: 'bg-surface-2 border-gold/30 border-r-4 border-r-gold'
}`}
whileTap={{ scale: 0.97 }}
onClick={() => {
if (!notification.is_read) markAsRead(notification.id)
}}
> >
<div className="flex items-start gap-3"> <Bell size={32} className="text-text-muted" />
<div className="w-8 h-8 rounded-full bg-surface-3 border border-border flex items-center justify-center shrink-0 mt-0.5"> </motion.div>
{getIcon(notification.type)} <p className="text-text-muted font-black text-base">لا توجد اشعارات</p>
</div> </motion.div>
<div className="flex-1 min-w-0"> ) : (
<p className={`text-sm font-semibold ${notification.is_read ? 'text-text-muted' : 'text-text-primary'}`}> <motion.div className="flex flex-col gap-3" variants={stagger} initial="hidden" animate="show">
{notification.title_ar || notification.title} {notifications.map((notification) => (
</p> <motion.div
{(notification.body_ar || notification.body) && ( key={notification.id}
<p className="text-xs text-text-muted mt-0.5 line-clamp-2"> variants={item}
{notification.body_ar || notification.body} className={`rounded-xl p-4 border-3 cursor-pointer transition-colors ${
notification.is_read
? 'bg-surface-1 border-border'
: 'bg-surface-2 border-[#FFC83D]/40'
}`}
whileTap={{ scale: 0.96 }}
onClick={() => {
if (!notification.is_read) markAsRead(notification.id)
}}
>
<div className="flex items-start gap-3">
<div className={`w-10 h-10 rounded-xl border-3 flex items-center justify-center shrink-0 mt-0.5 ${
notification.is_read
? 'bg-surface-3 border-border'
: 'bg-surface-3 border-[#FFC83D]/30'
}`}>
{getIcon(notification.type)}
</div>
<div className="flex-1 min-w-0">
<p className={`text-sm font-black ${notification.is_read ? 'text-text-muted' : 'text-text-primary'}`}>
{notification.title_ar || notification.title}
</p> </p>
{(notification.body_ar || notification.body) && (
<p className="text-xs text-text-muted font-bold mt-0.5 line-clamp-2">
{notification.body_ar || notification.body}
</p>
)}
<p className="text-[10px] text-text-muted font-bold mt-1.5">
{relativeTime(notification.created_at)}
</p>
</div>
{!notification.is_read && (
<motion.div
className="w-3 h-3 rounded-full bg-[#FFC83D] shrink-0 mt-2 shadow-md shadow-[#FFC83D]/40"
animate={{ scale: [1, 1.2, 1] }}
transition={{ repeat: Infinity, duration: 2 }}
/>
)} )}
<p className="text-[10px] text-text-muted mt-1">
{relativeTime(notification.created_at)}
</p>
</div> </div>
{!notification.is_read && ( </motion.div>
<div className="w-2 h-2 rounded-full bg-gold shrink-0 mt-2" /> ))}
)} </motion.div>
</div> )}
</motion.div> </div>
))}
</motion.div>
)}
</PageTransition> </PageTransition>
) )
} }
This diff is collapsed.
This diff is collapsed.
...@@ -50,27 +50,41 @@ export function RegisterPage() { ...@@ -50,27 +50,41 @@ export function RegisterPage() {
} }
return ( return (
<div className="min-h-dvh flex flex-col items-center justify-center px-7 relative overflow-hidden"> <div className="min-h-dvh flex items-center justify-center relative overflow-hidden bg-background">
<motion.div <motion.div
className="absolute inset-0 opacity-30" className="absolute inset-0"
style={{ style={{
background: 'radial-gradient(ellipse at 50% 30%, rgba(212,168,67,0.15) 0%, transparent 60%)', background: 'radial-gradient(ellipse at 50% 30%, rgba(255,200,61,0.12) 0%, transparent 60%)',
}} }}
animate={{
background: [
'radial-gradient(ellipse at 50% 30%, rgba(255,200,61,0.12) 0%, transparent 60%)',
'radial-gradient(ellipse at 50% 30%, rgba(255,200,61,0.05) 0%, transparent 60%)',
'radial-gradient(ellipse at 50% 30%, rgba(255,200,61,0.12) 0%, transparent 60%)',
],
}}
transition={{ duration: 4, repeat: Infinity, ease: 'easeInOut' }}
/> />
<motion.div <motion.div
className="relative w-full max-w-[310px]" className="relative w-full max-w-[380px] px-5"
initial={{ opacity: 0, y: 30 }} initial={{ opacity: 0, y: 40 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 25 }} transition={{ type: 'spring', stiffness: 400, damping: 22 }}
> >
<div className="flex flex-col items-center mb-6"> <div className="flex flex-col items-center mb-6">
<GoldCrown size={56} /> <GoldCrown size={72} />
<h1 className="mt-3 text-2xl font-black text-gold">حساب جديد</h1> <h1 className="mt-4 text-4xl font-black text-gold">EL3AB</h1>
<p className="mt-1 text-sm text-text-secondary">انضم الى مجتمع EL3AB</p> <p className="mt-2 text-lg font-bold text-text-secondary">حساب جديد</p>
</div> </div>
<form onSubmit={handleRegister} className="flex flex-col gap-3.5 p-7 rounded-2xl bg-surface-3 border-2 border-gold/30 shadow-[0_8px_40px_rgba(0,0,0,0.6),0_0_20px_rgba(212,168,67,0.08)]"> <motion.form
onSubmit={handleRegister}
className="flex flex-col gap-4 p-6 rounded-2xl bg-surface-2 border-3 border-border shadow-xl"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ type: 'spring', stiffness: 450, damping: 25, delay: 0.1 }}
>
<Input <Input
label="اسم المستخدم" label="اسم المستخدم"
value={username} value={username}
...@@ -100,25 +114,26 @@ export function RegisterPage() { ...@@ -100,25 +114,26 @@ export function RegisterPage() {
{error && ( {error && (
<motion.p <motion.p
className="text-sm text-coral text-center" className="text-sm font-bold text-coral text-center"
initial={{ opacity: 0 }} initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1 }} animate={{ opacity: 1, scale: 1 }}
transition={{ type: 'spring', stiffness: 500, damping: 20 }}
> >
{error} {error}
</motion.p> </motion.p>
)} )}
<Button type="submit" loading={loading} className="w-[85%] mx-auto mt-2"> <Button type="submit" variant="gold" size="lg" loading={loading} className="w-[80%] mx-auto mt-2">
انشاء حساب انشاء حساب
</Button> </Button>
</form> </motion.form>
<motion.button <motion.button
onClick={() => navigate('/login')} onClick={() => navigate('/login')}
className="mt-4 w-full text-center text-sm text-text-secondary hover:text-gold transition-colors" className="mt-5 w-full text-center text-sm font-bold text-text-secondary hover:text-gold transition-colors"
whileTap={{ scale: 0.98 }} whileTap={{ scale: 0.96 }}
> >
لديك حساب بالفعل؟ <span className="text-gold font-semibold">سجل دخول</span> لديك حساب بالفعل؟ <span className="text-gold font-black">سجل دخول</span>
</motion.button> </motion.button>
</motion.div> </motion.div>
</div> </div>
......
...@@ -16,64 +16,63 @@ export function SettingsPage() { ...@@ -16,64 +16,63 @@ export function SettingsPage() {
} }
return ( return (
<PageTransition className="px-7 py-7 flex flex-col gap-5"> <PageTransition>
<h1 className="text-xl font-bold">الاعدادات</h1> <h1 className="text-2xl font-black">الاعدادات</h1>
<Card className="flex items-center justify-between"> <div className="flex flex-col gap-4">
<div className="flex items-center gap-3"> <Card className="flex items-center justify-between">
{soundEnabled ? <Volume2 size={18} className="text-cyan" /> : <VolumeX size={18} className="text-text-muted" />} <div className="flex items-center gap-3">
<span className="text-sm font-semibold">الاصوات</span> {soundEnabled ? <Volume2 size={20} className="text-cyan" /> : <VolumeX size={20} className="text-text-muted" />}
</div> <span className="text-sm font-bold">الاصوات</span>
<motion.button </div>
className={`w-12 h-6 rounded-full p-0.5 ${soundEnabled ? 'bg-cyan' : 'bg-surface-3'}`} <motion.button
onClick={() => setSoundEnabled(!soundEnabled)} className={`w-14 h-7 rounded-full p-1 border-2 ${soundEnabled ? 'bg-cyan/20 border-cyan' : 'bg-surface-3 border-border'}`}
whileTap={{ scale: 0.9 }} onClick={() => setSoundEnabled(!soundEnabled)}
> whileTap={{ scale: 0.9 }}
<motion.div >
className="w-5 h-5 rounded-full bg-white" <motion.div
animate={{ x: soundEnabled ? 0 : 24 }} className={`w-5 h-5 rounded-full ${soundEnabled ? 'bg-cyan' : 'bg-text-muted'}`}
transition={{ type: 'spring', stiffness: 500, damping: 30 }} animate={{ x: soundEnabled ? 0 : 28 }}
/> transition={{ type: 'spring', stiffness: 500, damping: 30 }}
</motion.button> />
</Card> </motion.button>
</Card>
<Card className="flex items-center gap-3"> <Card className="flex items-center gap-3">
<Info size={18} className="text-text-muted" /> <Info size={20} className="text-gold" />
<div> <div>
<p className="text-sm font-semibold">EL3AB Player</p> <p className="text-sm font-bold">EL3AB Player</p>
<p className="text-xs text-text-muted">الاصدار 1.0.0</p> <p className="text-xs text-text-muted font-semibold">الاصدار 1.0.0</p>
</div> </div>
</Card> </Card>
<Card className="flex items-center gap-3"> <Card className="flex items-center gap-3">
<Bug size={18} className="text-text-muted" /> <Bug size={20} className="text-purple" />
<div> <div>
<p className="text-sm font-semibold">الابلاغ عن مشكلة</p> <p className="text-sm font-bold">الابلاغ عن مشكلة</p>
<p className="text-xs text-text-muted">ساعدنا نحسن التطبيق</p> <p className="text-xs text-text-muted font-semibold">ساعدنا في تحسين التطبيق</p>
</div> </div>
</Card> </Card>
</div>
<div className="pt-4"> <div className="flex flex-col gap-3 mt-4">
<motion.button <motion.button
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.95 }}
onClick={handleLogout} onClick={handleLogout}
className="w-full flex items-center justify-center gap-2.5 px-5 py-3.5 rounded-xl bg-coral/10 border border-coral/20 text-coral text-sm font-bold" className="flex items-center justify-center gap-2 w-[80%] mx-auto px-6 py-3 rounded-2xl bg-coral/10 border-2 border-coral/30 text-coral font-bold text-sm"
> >
<LogOut size={16} /> <LogOut size={16} />
<span>تسجيل الخروج</span> <span>تسجيل الخروج</span>
</motion.button> </motion.button>
</div>
<div className="pt-2"> <motion.button
<h3 className="text-xs font-bold text-text-muted mb-3">منطقة الخطر</h3>
<button
disabled disabled
className="w-full flex items-center justify-center gap-2.5 px-5 py-3.5 rounded-xl bg-surface-2 border border-border text-text-muted text-sm font-semibold opacity-50 cursor-not-allowed" className="flex items-center justify-center gap-2 w-[80%] mx-auto px-6 py-3 rounded-2xl bg-surface-2 border-2 border-border text-text-muted font-bold text-sm opacity-50 cursor-not-allowed"
> >
<Trash2 size={16} /> <Trash2 size={16} />
<span>حذف الحساب</span> <span>حذف الحساب</span>
<span className="text-[10px] mr-1 px-2 py-0.5 rounded-full bg-surface-3 text-text-muted">قريبا</span> <span className="text-[10px] bg-surface-3 px-2 py-0.5 rounded-full">قريبا</span>
</button> </motion.button>
</div> </div>
</PageTransition> </PageTransition>
) )
......
This diff is collapsed.
...@@ -57,7 +57,7 @@ export function SplashPage() { ...@@ -57,7 +57,7 @@ export function SplashPage() {
</motion.div> </motion.div>
<motion.h1 <motion.h1
className="mt-6 text-4xl font-black text-gold tracking-wider" className="mt-6 text-5xl font-black text-gold tracking-wider"
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.6, duration: 0.5 }} transition={{ delay: 0.6, duration: 0.5 }}
......
This diff is collapsed.
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