Commit 588975c5 authored by Mahmoud Aglan's avatar Mahmoud Aglan

fix: floating nav bar, inset header, px-6 margins everywhere

Bottom nav is now floating with rounded corners and shadow.
Header is now a floating pill with rounded corners.
All pages use px-6 (24px) margins for proper inset from edges.
Cards, buttons, and sections all have visible spacing from screen edges.
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 5512a11d
This diff is collapsed.
import { chromium } from 'playwright'
import { mkdirSync } from 'fs'
const BASE_URL = 'https://el3ab-player.caprover.al-arcade.com'
const VIEWPORT = { width: 390, height: 844 }
const EMAIL = 'testplayer1@el3ab.com'
const PASSWORD = 'test123456'
async function main() {
mkdirSync('screenshots', { recursive: true })
const browser = await chromium.launch()
const context = await browser.newContext({
viewport: VIEWPORT,
deviceScaleFactor: 2,
locale: 'ar',
})
const page = await context.newPage()
// Login
await page.goto(`${BASE_URL}/login`, { waitUntil: 'networkidle', timeout: 15000 })
await page.waitForTimeout(1000)
await page.fill('input[type="email"]', EMAIL)
await page.fill('input[type="password"]', PASSWORD)
await page.click('button[type="submit"]')
await page.waitForTimeout(3000)
// Navigate to bot game directly
await page.goto(`${BASE_URL}/game/bot/amina`, { waitUntil: 'networkidle', timeout: 15000 })
await page.waitForTimeout(2000)
await page.screenshot({ path: 'screenshots/bot-match-01-start.png' })
console.log('captured: initial board')
// Target squares specifically within the chess board grid (data-testid="chess-board")
const squares = page.locator('[data-testid="chess-board"] > div')
const squareCount = await squares.count()
console.log(`found ${squareCount} squares on the board`)
// The board is an 8x8 grid. For white's perspective (not flipped):
// Grid index = row*8 + col, where row 0 = rank 8 (top), row 7 = rank 1 (bottom)
// e2 = file e (col 4), rank 2 (row 6) -> index: 6*8 + 4 = 52
// e4 = file e (col 4), rank 4 (row 4) -> index: 4*8 + 4 = 36
// Click e2 (select white pawn)
console.log('clicking e2 (index 52)...')
await squares.nth(52).click()
await page.waitForTimeout(800)
await page.screenshot({ path: 'screenshots/bot-match-02-selected-e2.png' })
console.log('captured: selected e2')
// Click e4 (move pawn)
console.log('clicking e4 (index 36)...')
await squares.nth(36).click()
await page.waitForTimeout(800)
await page.screenshot({ path: 'screenshots/bot-match-03-moved-e4.png' })
console.log('captured: moved e4')
// Wait for bot to respond (up to 12 seconds)
console.log('waiting for bot to respond...')
await page.waitForTimeout(10000)
await page.screenshot({ path: 'screenshots/bot-match-04-bot-responded.png' })
console.log('captured: after bot response')
// Play another move: d2-d4
// d2 = file d (col 3), rank 2 (row 6) -> index: 6*8 + 3 = 51
// d4 = file d (col 3), rank 4 (row 4) -> index: 4*8 + 3 = 35
console.log('clicking d2 (index 51)...')
await squares.nth(51).click()
await page.waitForTimeout(800)
console.log('clicking d4 (index 35)...')
await squares.nth(35).click()
await page.waitForTimeout(800)
await page.screenshot({ path: 'screenshots/bot-match-05-moved-d4.png' })
console.log('captured: moved d4')
// Wait for bot second response
console.log('waiting for bot second response...')
await page.waitForTimeout(10000)
await page.screenshot({ path: 'screenshots/bot-match-06-bot-responded-2.png' })
console.log('captured: after bot second response')
// Play Nf3
// g1 = file g (col 6), rank 1 (row 7) -> index: 7*8 + 6 = 62
// f3 = file f (col 5), rank 3 (row 5) -> index: 5*8 + 5 = 45
console.log('clicking g1 (index 62)...')
await squares.nth(62).click()
await page.waitForTimeout(800)
console.log('clicking f3 (index 45)...')
await squares.nth(45).click()
await page.waitForTimeout(800)
// Wait for bot
console.log('waiting for bot third response...')
await page.waitForTimeout(10000)
await page.screenshot({ path: 'screenshots/bot-match-07-after-3-moves.png' })
console.log('captured: after 3 moves each')
await browser.close()
console.log('done - bot match test complete')
}
main()
......@@ -9,14 +9,11 @@ const PASSWORD = 'test123456'
const pages = [
{ name: '03-home', path: '/' },
{ name: '04-play', path: '/play' },
{ name: '05-matchmaking', path: '/matchmaking' },
{ name: '06-profile', path: '/profile' },
{ name: '07-tournaments', path: '/tournaments' },
{ name: '08-friends', path: '/friends' },
{ name: '09-leaderboard', path: '/leaderboard' },
{ name: '10-notifications', path: '/notifications' },
{ name: '11-shop', path: '/shop' },
{ name: '12-settings', path: '/settings' },
{ name: '05-bot-select', path: '/bot-select' },
{ name: '06-game-bot', path: '/game/bot/amina' },
{ name: '07-profile', path: '/profile' },
{ name: '08-leaderboard', path: '/leaderboard' },
{ name: '09-shop', path: '/shop' },
]
async function main() {
......
......@@ -5,9 +5,9 @@ import { ToastContainer } from '../ui/ToastContainer'
export function AppShell() {
return (
<div className="flex flex-col min-h-dvh">
<div className="flex flex-col min-h-dvh bg-[#0a0a12]">
<Header />
<main className="flex-1 pb-20 overflow-y-auto">
<main className="flex-1 pb-24 overflow-y-auto">
<Outlet />
</main>
<BottomNav />
......
......@@ -15,8 +15,8 @@ export function BottomNav() {
const navigate = useNavigate()
return (
<nav className="fixed bottom-0 left-0 right-0 z-50 bg-surface-1/90 backdrop-blur-xl border-t border-border">
<div className="flex items-center justify-around px-2 py-2 max-w-lg mx-auto">
<nav className="fixed bottom-0 left-0 right-0 z-50 px-4 pb-2">
<div className="flex items-center justify-around px-2 py-2.5 max-w-lg mx-auto bg-surface-1/95 backdrop-blur-xl border border-border/60 rounded-2xl shadow-xl shadow-black/30">
{NAV_ITEMS.map((item) => {
const isActive = location.pathname === item.path
const Icon = item.icon
......@@ -51,7 +51,7 @@ export function BottomNav() {
{isActive && (
<motion.div
className="absolute -bottom-1 w-5 h-1 rounded-full bg-gold"
className="absolute -bottom-0.5 w-5 h-1 rounded-full bg-gold"
layoutId="nav-indicator"
transition={{ type: 'spring', stiffness: 400, damping: 30 }}
style={{ boxShadow: '0 0 8px rgba(212, 168, 67, 0.6)' }}
......
......@@ -12,37 +12,39 @@ export function Header() {
const navigate = useNavigate()
return (
<header className="sticky top-0 z-50 flex items-center justify-between px-4 py-3 bg-surface-1/80 backdrop-blur-xl border-b border-border">
<div className="flex items-center gap-2">
<GoldCrown size={28} animate={false} />
<span className="text-lg font-bold text-gold">EL3AB</span>
</div>
<div className="flex items-center gap-4">
{profile && (
<motion.div
className="flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-surface-2 border border-border-gold"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ type: 'spring', stiffness: 400, damping: 25 }}
>
<Coins size={14} className="text-gold" />
<span className="text-sm font-semibold text-gold">{profile.coins}</span>
</motion.div>
)}
<header className="sticky top-0 z-50 px-4 pt-3 pb-2">
<div className="flex items-center justify-between px-4 py-2.5 bg-surface-1/90 backdrop-blur-xl border border-border/60 rounded-2xl shadow-lg shadow-black/20">
<div className="flex items-center gap-2">
<GoldCrown size={26} animate={false} />
<span className="text-base font-bold text-gold">EL3AB</span>
</div>
<div className="relative" onClick={() => navigate('/notifications')}>
<AnimatedIcon icon={Bell} size={22} color="var(--color-text-secondary)" onClick={() => {}} />
{unreadCount > 0 && (
<div className="flex items-center gap-3">
{profile && (
<motion.div
className="absolute -top-1 -right-1 w-4 h-4 rounded-full bg-coral flex items-center justify-center"
className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-surface-2/80 border border-gold/30"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ type: 'spring', stiffness: 500, damping: 20 }}
transition={{ type: 'spring', stiffness: 400, damping: 25 }}
>
<span className="text-[10px] font-bold text-white">{unreadCount > 9 ? '9+' : unreadCount}</span>
<Coins size={13} className="text-gold" />
<span className="text-xs font-bold text-gold">{profile.coins}</span>
</motion.div>
)}
<div className="relative" onClick={() => navigate('/notifications')}>
<AnimatedIcon icon={Bell} size={20} color="var(--color-text-secondary)" onClick={() => {}} />
{unreadCount > 0 && (
<motion.div
className="absolute -top-1 -right-1 w-4 h-4 rounded-full bg-coral flex items-center justify-center"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ type: 'spring', stiffness: 500, damping: 20 }}
>
<span className="text-[10px] font-bold text-white">{unreadCount > 9 ? '9+' : unreadCount}</span>
</motion.div>
)}
</div>
</div>
</div>
</header>
......
......@@ -39,7 +39,7 @@ export function BotSelectPage() {
if (loading) {
return (
<PageTransition className="px-5 py-8 flex flex-col items-center justify-center min-h-[60vh]">
<PageTransition className="px-6 py-8 flex flex-col items-center justify-center min-h-[60vh]">
<div className="w-10 h-10 border-2 border-gold/30 border-t-gold rounded-full animate-spin" />
<p className="mt-4 text-sm text-text-muted">جاري تحميل الروبوتات...</p>
</PageTransition>
......@@ -47,7 +47,7 @@ export function BotSelectPage() {
}
return (
<PageTransition className="px-5 py-6 flex flex-col gap-6 pb-36">
<PageTransition className="px-6 py-7 flex flex-col gap-6 pb-36">
{/* Header */}
<div className="flex items-center gap-4">
<motion.button
......
......@@ -365,7 +365,7 @@ export function GamePage() {
return (
<div className="flex flex-col min-h-dvh bg-background">
{/* Header */}
<div className="flex items-center justify-between px-5 py-3 bg-surface-1 border-b border-border">
<div className="flex items-center justify-between px-6 py-3.5 bg-surface-1 border-b border-border">
<motion.button
onClick={() => navigate(-1)}
className="flex items-center gap-0.5 text-text-muted p-2"
......
This diff is collapsed.
......@@ -28,7 +28,7 @@ export function PlayPage() {
const otherGames = GAMES.filter((g) => g.key !== 'chess')
return (
<PageTransition className="px-5 py-6 flex flex-col gap-7">
<PageTransition className="px-6 py-7 flex flex-col gap-7">
<h1 className="text-xl font-bold">اختر اللعبة</h1>
{/* Chess Hero Card */}
......
......@@ -26,7 +26,7 @@ export function ProfilePage() {
: 0
return (
<PageTransition className="px-5 py-6 flex flex-col gap-6">
<PageTransition className="px-6 py-7 flex flex-col gap-6">
{/* Avatar + Name */}
<div className="flex items-start justify-between">
<div className="flex items-center gap-4">
......
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