Commit 0c0bcb97 authored by Mahmoud Aglan's avatar Mahmoud Aglan

paswe 0

parent a1d2298a
# EL3AB Player App — V2 Total Rebuild Plan
## Architecture
- **Framework:** Vite + React 19 + TypeScript (keeping current)
- **Styling:** Tailwind CSS v4 with strict design tokens from spec
- **State:** Zustand stores
- **Backend:** Supabase (self-hosted at caprover.al-arcade.com)
- **Realtime:** Supabase Realtime channels
- **Animations:** Framer Motion (restrained per spec)
- **Bot API:** Stockfish API at stockfishapi.caprover.al-arcade.com
## Rebuild Rules
1. Every phase ends with a deployable build (`npm run build` must pass)
2. After human confirms build is live, I screenshot, analyze, fix or proceed
3. Design system spec is LAW — strict 8pt grid, correct palette, correct radii
4. All data from DB — no hardcoded constants for things that exist in tables
5. Arabic-first, RTL-native
6. Mobile-first, desktop expands
---
## PHASE 0 — Design System Foundation & Token Reset
**Goal:** Rip out all spec-violating styles. Establish the correct token system as the single source of truth. Every component after this phase uses ONLY these tokens.
### Tasks:
1. Rewrite `src/index.css` @theme with correct spec colors:
- Background: `#071120` (not #0B0E1A)
- Secondary Surface: `#0D1728`
- Elevated Card: `#132238`
- Gold: `#E7B83D` (not #FFC83D)
- Royal Blue: `#2979FF`
- Cyan: `#15D7FF`
- Border: `rgba(255,255,255,0.08)`
- Status colors: Win=Emerald, Loss=Crimson, Draw=Amber, Live=Gold, Online=Green
2. Typography tokens:
- Load Inter (English) + IBM Plex Sans Arabic (Arabic primary) + Cairo (fallback)
- Define font scale: 64/48/32/24/18/16/14/12
- No text under 14px except captions (12px)
3. Spacing tokens (strict 8pt):
- xs=4, sm=8, md=16, lg=24, xl=32, 2xl=48, 3xl=64, 4xl=96
- Mobile padding: 16px (not 20px)
- Remove ALL forbidden values
4. Border radius tokens:
- tiny=8, standard=12, large=16, hero=24
- Remove all 3/5/6/10/14/20/40 values
5. Shadow system (3 levels only):
- Level 1: small elevation
- Level 2: cards/floating
- Level 3: modals only
6. Remove DecorativeBackground floating particles (spec: "NOT floating particles everywhere")
7. Restrain gold usage — audit every gold element, keep only for rank/premium/trophies/hero CTAs
8. Container system:
- Mobile: 100%, padding 16px
- Tablet: max 768px, padding 24px
- Desktop: max 1440px, padding 32px
### Deliverable:
- Clean token system
- All existing components/pages updated to use new tokens
- No visual regressions in layout structure
- `npm run build` passes
---
## PHASE 1 — Core Layout & Navigation Rebuild
**Goal:** Rebuild AppShell, Header, BottomNav, and page transitions to match spec exactly. Desktop sidebar. Responsive grid foundation.
### Tasks:
1. Rebuild `BottomNav.tsx`:
- 5 items: Home, Play, Tournaments, Social, Profile
- 44x44 minimum touch targets
- Active state with gold indicator (restrained)
- Clean, no excessive decoration
2. Rebuild `Header.tsx`:
- Logo + Level + Currency (coins/gems) + Notifications bell
- Sticky, minimal, spec-compliant spacing
- No diagonal clip-path gimmicks
3. Rebuild `AppShell.tsx`:
- Desktop: left nav rail + content + optional right panel
- Tablet: content only with bottom nav
- Mobile: content + bottom nav
- Page outlet with AnimatePresence (fade only, 150-250ms)
4. Create layout primitives:
- `<Section>` — enforces vertical rhythm (64px desktop, 48px tablet, 32px mobile)
- `<Container>` — enforces max-width + padding per breakpoint
- `<Grid>` — 12/8/4 column responsive grid
5. Remove PageTransition elastic/spring animations — use fade 200ms
### Deliverable:
- Responsive shell works on mobile/tablet/desktop
- Navigation functional
- Skeleton ready for all pages
- `npm run build` passes
---
## PHASE 2 — Authentication & Profile System
**Goal:** Complete auth flow + full profile utilizing ALL 48 profile columns.
### Tasks:
1. Rebuild `LoginPage.tsx` and `RegisterPage.tsx`:
- Clean, premium feel
- Arabic-first labels
- Username + display_name + preferred_language on register
- Error handling
2. Rebuild `ProfilePage.tsx`:
- Avatar + Frame (using `avatar_frame_id`, `avatar_border_color`)
- Display name (AR + EN)
- Country + City
- Bio (bilingual)
- All 4 ELO ratings (bullet/blitz/rapid/classical)
- FIDE data if available (id, ratings, title)
- XP bar with level progress (query `xp_levels` table)
- Stats: total games, wins, draws, losses, win streak, best streak
- Tournament stats
- Coins + Gems display
- Online status
- Ban status display (if banned)
- Edit profile modal
3. Create `useProfile` hook:
- Query full profile
- Query `xp_levels` for level requirements
- Query `player_achievements` for badge display
- Query `player_cosmetics` for equipped items
- Query `rating_history` for rating graph data
4. Rating history graph component:
- Query `rating_history` table
- Show rating over time per time control
- Mini sparkline on profile
### Deliverable:
- Full auth working
- Profile shows ALL available data
- Rating graph displays history
- XP/level progression visible
- `npm run build` passes
---
## PHASE 3 — Home Screen & Activity Feed
**Goal:** Build the engagement hub per spec (daily return, social visibility, recommendations).
### Tasks:
1. Rebuild `HomePage.tsx` with spec structure:
- Welcome Header (display_name, level, greeting)
- Daily Missions / Daily Reward
- Continue Playing (last match from `matches` table)
- Live Tournaments (from `el3ab_tournaments` where status = active)
- Friends Online (from `friendships` + `profiles.is_online`)
- Recommended Matches (based on rating)
- Leaderboard preview
- Organizations (user's orgs from `org_members`)
- Store Promotions (featured cosmetics)
2. Build Activity Feed component:
- Query `activity_feed` table
- Show: who did what (matches, achievements, tournament results)
- Visibility filtering (public/friends/org)
3. Rebuild `DailyRewardModal.tsx`:
- Clean design using tokens
- Streak visualization
- XP + coin rewards from `xp_levels` table
- Economy transaction on claim
4. Create `useDashboard` hook:
- Aggregate data from multiple tables efficiently
- Friends online count
- Active tournaments count
- Unread notifications count
### Deliverable:
- Home screen shows real data from all relevant tables
- Activity feed functional
- Daily reward system working
- `npm run build` passes
---
## PHASE 4 — Play & Matchmaking System
**Goal:** Zero-friction play flow per spec. Dynamic game selection from DB.
### Tasks:
1. Rebuild `PlayPage.tsx`:
- Query `game_plugins` table (NOT hardcoded constants)
- Show enabled games dynamically
- Per-game: time controls from `default_time_controls`
- One primary CTA: "Play Now"
- Game > Time Control > Rated/Casual — that's it
- Check `feature_flags` for `matchmaking_enabled`, `ranked_games_enabled`, `bot_games_enabled`
2. Rebuild `MatchmakingPage.tsx`:
- Clean matchmaking animation (pulse, not bouncy/elastic)
- Show rating range expansion over time
- Opponent found: avatar + rating + time control + estimated strength
- 300ms fade + 500ms pulse per spec
- Sound cue optional
- Uses `matchmaking_queue` table properly
3. Rebuild `BotSelectPage.tsx`:
- Query bots from Stockfish API (`GET /api/chess/bots`)
- Show portraits from `portrait_url`
- Arabic names + styles from API
- ELO range display
- Difficulty progression visual
4. Create `useFeatureFlags` hook:
- Query `feature_flags` table
- Gate features dynamically
- Cache with short TTL
5. Create `useGamePlugins` hook:
- Query `game_plugins` table
- Replace hardcoded `GAMES` constant
- Respect `is_enabled`, `is_beta` flags
### Deliverable:
- Play page driven by database
- Feature flags respected
- Matchmaking smooth and spec-compliant
- Bot selection from live API
- `npm run build` passes
---
## PHASE 5 — In-Game Experience
**Goal:** The most critical system. Board dominates. Timer clear. Touch targets 44x44.
### Tasks:
1. Rebuild `GamePage.tsx` (Bot mode):
- Board: 70-78% vertical space on mobile
- Desktop: board center, left=player info, right=move list
- Timer prominent
- Material advantage display
- Move history (scrollable)
- Resign/Draw/Rematch buttons (44x44 min)
- Sound integration
- Save completed game to `matches` table with full PGN
- Record rating changes (bot games optional)
2. Rebuild `GamePage.tsx` (Multiplayer mode):
- Same layout rules
- Realtime sync via Supabase channel
- Opponent profile display
- Clock management (initial_time_ms, increment_ms from match)
- Call `check_opponent_timeout` RPC
- Game result → update `matches`, trigger `rating_history` insert
- Chat toggle (if `chat_enabled` flag)
3. In-game chat component:
- Insert to `chat_messages` (channel_type='match', channel_id=match.id)
- Realtime subscription for messages
- Message padding: 12px 16px per spec
- Timestamp opacity 60%
4. Spectator mode (basic):
- If `spectator_enabled` flag
- Read-only board view
- Subscribe to match channel
- No controls, just watch
### Deliverable:
- Games fully playable (bot + multiplayer)
- Results saved to DB
- Rating changes recorded
- In-game chat working
- Spectator view functional
- `npm run build` passes
---
## PHASE 6 — Tournament System (Full)
**Goal:** EL3AB's strongest differentiator. Official, prestigious, competitive.
### Tasks:
1. Rebuild `TournamentsPage.tsx` (listing):
- Query `el3ab_tournaments` with all 58 columns
- Filter by status (upcoming/active/completed)
- Filter by game_key
- Show: banner, name, format, time control, prize pool, registration count, dates
- Registration CTA
- Check `tournaments_enabled` flag
2. Create `TournamentDetailPage.tsx`:
- Hero Banner (banner_url)
- Tournament Status + Countdown
- Registration CTA (call `register_tournament_player` RPC)
- Prize Pool (coins/gems distribution from `prize_distribution` jsonb)
- Live Standings (query `standings`)
- Bracket visualization (query `tournament_brackets` + `bracket_matches`)
- Pairings per round (query `pairings`)
- Rules (format, tiebreaks, rating limits)
- Sponsors (from `tournament_sponsorships` + `sponsors`)
- Announcements (from `tournament_announcements`)
- Media gallery (from `tournament_media`)
- View count tracking (insert to `tournament_page_views`)
3. Bracket visualization component:
- Query `bracket_matches` (29 columns per match)
- Desktop: horizontal scroll bracket
- Mobile: phase-by-phase view per spec
- Show seeds, scores, winners
- Link to next_match_id progression
- Support single/double elimination via `loser_next_match_id`
4. Live Tournament Mode (desktop):
- Split layout: Standings | Board Stream | Chat
- Realtime updates via `get_tournament_room` RPC
- Auto-refresh standings
- Tournament announcements feed
5. Tournament registration flow:
- Check rating limits (min_rating, max_rating)
- Check entry fee (coins/gems)
- Deduct fee via economy transaction
- Call `register_tournament_player` RPC
- Cancel via `cancel_tournament_registration` RPC
6. Multi-phase tournament support:
- Query `tournament_phases`
- Show current phase indicator
- Phase navigation (swiss → bracket → finals)
- Advancement rules display
### Deliverable:
- Full tournament listing with filters
- Tournament detail page with all sections
- Bracket visualization (single + double elimination)
- Live mode working
- Registration + cancellation flow
- `npm run build` passes
---
## PHASE 7 — Organizations & Clubs
**Goal:** Esports clubs / sports federations / gaming communities. NOT random groups.
### Tasks:
1. Create `OrganizationsPage.tsx` (discovery):
- List active orgs from `el3ab_organizations`
- Filter by tier, country, game
- Show: logo, name, member count, tier, verified badge
- Search functionality
2. Create `OrgDetailPage.tsx`:
- Banner + Logo
- Org info (description bilingual, social links, website)
- Tier + Verified status
- Custom branding (primary_color, secondary_color from DB)
- Stats: members, tournaments, matches
- Members list (from `org_members` with roles)
- Active Tournaments (org's tournaments from `el3ab_tournaments`)
- Org Achievements (from `org_achievements`)
- Seasonal Rankings (from `org_seasonal_rankings`)
- Recruitment posts (from `org_recruitment_posts`)
- Media gallery (from `org_media`)
3. Create `OrgChatPage.tsx`:
- Channels list (from `org_chat_channels`)
- Channel types: general, announcements, game-specific
- Messages (from `org_chat_messages`)
- Realtime subscription
- Moderation (from `org_chat_moderation`)
4. Create `OrgEventsPage.tsx`:
- Events list (from `org_events`)
- Calendar view (online/offline events)
- RSVP (from `org_event_participants`)
- Link to tournament if `tournament_id` set
5. Org membership flow:
- Join via invite code (`org_invite_codes`)
- Join via invite link (`org_invite_links`, track in `org_invite_uses`)
- Application flow (`org_membership_applications`)
- Role display (owner/admin/member/etc from `org_members.role`)
6. Org Challenges (Org vs Org):
- Display challenges from `org_challenges`
- Challenge status tracking
- Roster selection (from `org_rosters` + `org_roster_players`)
- Score display
7. Org Treasury:
- Balance display (from `org_treasury`)
- Transaction history (from `org_treasury_transactions`)
- Admin-only visibility
8. Training Sessions:
- List sessions (from `org_training_sessions`)
- Schedule + join flow
### Deliverable:
- Org discovery + detail pages
- Org chat with channels
- Events + RSVP
- Membership flow (invite/apply)
- Challenges display
- Treasury (admin view)
- `npm run build` passes
---
## PHASE 8 — Social System & Chat
**Goal:** Lightweight, always alive, Discord-inspired. NOT Facebook.
### Tasks:
1. Rebuild `FriendsPage.tsx`:
- Friends list with online status
- Pending requests (sent/received)
- Quick actions: challenge, message, view profile
- Search/add by username
- Block functionality
2. Create DM chat system:
- Direct messages via `chat_messages` (channel_type='dm')
- Conversation list
- Realtime message subscription
- Message padding 12px 16px
- Group spacing 8px
- Timestamp opacity 60%
- Unread indicators
3. Activity Feed on social tab:
- Query `activity_feed`
- Filter by visibility (public/friends)
- Actions: completed match, won tournament, unlocked achievement, joined org
- Tap to navigate to target
4. Online presence system:
- Use `usePresence` hook (already exists)
- Show online friends prominently
- "Challenge friend" quick action
5. Update BottomNav "Social" tab:
- Sub-tabs: Friends, Messages, Activity
- Unread badge count
### Deliverable:
- Friends system fully functional
- DM chat working with realtime
- Activity feed populated
- Online presence visible
- `npm run build` passes
---
## PHASE 9 — Achievements & XP System
**Goal:** Prestigious, collectible, competitive achievement system.
### Tasks:
1. Seed `achievements` table with initial data:
- Categories: gameplay, social, tournament, collection, milestone
- Tiers: 1 (bronze), 2 (silver), 3 (gold), 4 (diamond)
- Arabic + English names/descriptions
- Conditions (jsonb: type, target, game_key)
- Rewards (XP, coins, cosmetics)
- Hidden achievements
2. Create `AchievementsPage.tsx`:
- Grid of all achievements grouped by category
- Progress bars for in-progress ones
- Locked/unlocked state
- Tier-based visual treatment
- Hidden achievements show "???" until unlocked
- Reward preview
3. Achievement unlock celebration:
- Toast notification on unlock
- Modal with achievement details
- XP + reward granted animation
- Save to `player_achievements`
4. Achievement display in Profile:
- Recent achievements section
- Showcase (pinned achievements)
- Completion percentage per category
5. Achievement progress tracking:
- After each game: check conditions
- After tournament results: check conditions
- Social actions: check conditions
- Update `player_achievements.progress`
### Deliverable:
- Achievements seeded in DB
- Achievement page with categories/tiers
- Progress tracking functional
- Unlock celebrations
- Profile integration
- `npm run build` passes
---
## PHASE 10 — Shop & Cosmetics
**Goal:** Premium, collectible, luxury cosmetics. NOT cheap mobile monetization.
### Tasks:
1. Seed `cosmetics` table:
- Avatar frames (common → legendary)
- Board themes
- Piece sets
- Profile banners
- Titles/badges
- All with Arabic names, preview_url, asset_url
- Price in coins and/or gems
- Limited edition items with `available_until`
2. Rebuild `ShopPage.tsx`:
- Category tabs from `cosmetic_type_registry`
- Rarity filter
- Card ratio 4:5 per spec
- Hover: slight lift + border glow (no insane animations)
- Price display (coins/gems)
- Limited edition countdown
- Owned indicator
- Purchase flow with confirmation
3. Cosmetic preview system:
- Avatar frame preview on profile
- Board theme preview in mini-board
- Piece set preview
4. Equip system:
- Toggle equipped via `player_cosmetics.is_equipped`
- Update `profiles.avatar_frame_id` etc
- Instant visual feedback
5. Economy transaction logging:
- Every purchase → `economy_transactions` insert
- Balance validation server-side
- Insufficient funds handling
### Deliverable:
- Cosmetics seeded in DB
- Shop fully functional with categories
- Purchase + equip flow
- Preview system
- Economy tracking
- `npm run build` passes
---
## PHASE 11 — Leaderboard & Rankings
**Goal:** Global and filtered leaderboards pulling from real DB data.
### Tasks:
1. Rebuild `LeaderboardPage.tsx`:
- Query `leaderboards` table
- Filter: game_key, time_control_type, period (daily/weekly/monthly/all-time)
- Country filter
- Show: rank, avatar+frame, display_name, rating, games_played, win_rate
- Highlight current user position
- Top 3 special treatment (gold/silver/bronze)
2. Org leaderboard:
- Query `org_seasonal_rankings`
- Org standings by season
- Points, matches won/lost, tournaments won
3. Friends leaderboard:
- Filter to friends only
- Quick challenge from leaderboard
4. Rating distribution visualization:
- Show where user falls in global distribution
- Percentile display
### Deliverable:
- Full leaderboard with filters
- Org rankings
- Friends filter
- `npm run build` passes
---
## PHASE 12 — Notifications & Settings
**Goal:** Complete notification system + app settings.
### Tasks:
1. Rebuild `NotificationsPage.tsx`:
- Query `notifications` table (bilingual title/body)
- Types: match_invite, friend_request, tournament_start, achievement_unlock, org_invite, system
- Mark as read (single + all)
- Tap to navigate (use `data` jsonb for routing)
- Realtime subscription for new notifications
- Unread count in header bell
2. Rebuild `SettingsPage.tsx`:
- Language preference (AR/EN)
- Notification preferences
- Sound toggle
- Theme preference (if `platform_theme` supports)
- Account management (change password, delete account)
- Privacy settings
- Block list management
- App version (call `check_app_version` RPC)
- Logout
3. Push notification registration (if applicable):
- Web push subscription
- Permission request flow
### Deliverable:
- Notifications fully working with realtime
- Settings page complete
- Unread badge accurate
- `npm run build` passes
---
## PHASE 13 — Anti-Cheat & Reporting
**Goal:** Trust system. Players can report. Engine correlation visible.
### Tasks:
1. Create report modal (in-game + from profile):
- Report reason selection
- Description field
- Submit to `cheat_reports` table
- Attach match_id automatically
2. Post-game analysis display:
- If `analysis_complete` on match
- Show average centipawn loss
- Move accuracy percentage
- Flag indicator if `is_flagged`
3. Fair play badge on profile:
- If no cheat reports / clean record
- Show analysis stats for recent games
### Deliverable:
- Report system functional
- Post-game analysis display
- `npm run build` passes
---
## PHASE 14 — Sponsorship & Charity Integration
**Goal:** Display sponsors in tournaments. Charity donation flow.
### Tasks:
1. Sponsor display components:
- Banner 728x90 in tournament pages
- 300x250 in sidebars
- Brand color integration
- Link to sponsor website
2. Charity integration in tournaments:
- If tournament has `charity_id`
- Show charity info (from `charities` table)
- Percentage going to charity
- Donation counter
- Verified charity badge
3. Seed sponsors + charities in DB for demo
### Deliverable:
- Sponsors visible in tournament detail
- Charity donation display
- `npm run build` passes
---
## PHASE 15 — Polish, Performance & Accessibility
**Goal:** Final pass. WCAG AA. RTL perfect. Performance targets met.
### Tasks:
1. Accessibility audit:
- Color contrast check (WCAG AA minimum)
- Focus indicators on all interactive elements
- Screen reader labels (aria)
- Keyboard navigation
2. RTL perfection:
- Mirror all layouts
- RTL animations
- RTL tables (leaderboard, standings)
- Test with Arabic content throughout
3. Performance optimization:
- Code splitting per route (React.lazy)
- Image optimization (WebP, lazy load)
- Supabase query optimization (select specific columns)
- Navigation target: <150ms
- Initial load target: <2s
4. Error boundaries + loading states:
- Skeleton screens (not spinners)
- Error fallback UI
- Offline indicator
- Network retry logic
5. Motion system final pass:
- All transitions 150-250ms
- Only fade/scale/slide
- Remove any remaining elastic/bounce
- No floating particles
6. Final token audit:
- Grep for any remaining forbidden values
- Ensure 100% token usage
- No inline colors/spacing/radii
### Deliverable:
- WCAG AA compliant
- RTL perfect
- Performance targets met
- Clean motion system
- Zero token violations
- `npm run build` passes
---
## PHASE EXECUTION PROTOCOL
For EACH phase:
```
1. I implement the phase
2. Run `npm run build` — must pass
3. Tell you "Phase X ready for deploy"
4. You confirm: "build is live"
5. I run screenshot script against live URL
6. Save screenshots to /screenshots/phase-X/
7. Analyze screenshots against spec
8. If issues found → fix → rebuild → you confirm again
9. If clean → proceed to next phase
```
## Screenshot Targets Per Phase
| Phase | Pages to Screenshot |
|-------|-------------------|
| 0 | Home, Play, Profile (token validation) |
| 1 | All pages (layout/nav validation) |
| 2 | Login, Register, Profile |
| 3 | Home (all sections) |
| 4 | Play, Matchmaking, Bot Select |
| 5 | Game (bot), Game (multiplayer) |
| 6 | Tournaments list, Tournament detail, Bracket |
| 7 | Orgs list, Org detail, Org chat |
| 8 | Friends, DM chat, Activity feed |
| 9 | Achievements page, Profile badges |
| 10 | Shop categories, Purchase flow |
| 11 | Leaderboard (all filters) |
| 12 | Notifications, Settings |
| 13 | Report modal, Post-game analysis |
| 14 | Tournament with sponsors/charity |
| 15 | Full app RTL, accessibility, performance metrics |
---
## Total Estimated Tables Connected After Rebuild
| Phase | New Tables Connected | Running Total |
|-------|---------------------|---------------|
| 0 | 0 | 11 |
| 1 | 0 | 11 |
| 2 | xp_levels, rating_history, player_achievements, player_cosmetics | 15 |
| 3 | activity_feed | 16 |
| 4 | game_plugins, feature_flags | 18 |
| 5 | chat_messages (match) | 19 |
| 6 | tournament_phases, tournament_brackets, bracket_matches, tournament_players, standings, pairings, tournament_announcements, tournament_media, tournament_page_views, tournament_sponsorships, tournament_prize_payouts | 30 |
| 7 | el3ab_organizations, org_members, org_chat_channels, org_chat_messages, org_events, org_event_participants, org_challenges, org_rosters, org_roster_players, org_treasury, org_treasury_transactions, org_seasonal_rankings, org_achievements, org_announcements, org_media, org_invite_codes, org_invite_links, org_invite_uses, org_membership_applications, org_recruitment_posts, org_loyalty_rewards, org_training_sessions | 52 |
| 8 | activity_feed (expanded) | 52 |
| 9 | achievements (seed + UI) | 53 |
| 10 | cosmetics (seed + UI), cosmetic_type_registry | 55 |
| 11 | leaderboards (expanded), org_seasonal_rankings | 55 |
| 12 | notifications (expanded) | 55 |
| 13 | cheat_reports | 56 |
| 14 | sponsors, charities, charity_donations, tournament_ad_slots | 60 |
| 15 | platform_theme, platform_branding, platform_assets, system_config | 64 |
**Final coverage: ~65/99 tables connected** (remaining ~34 are admin-only, legacy, or internal-use tables like audit_logs, workflow_rules, ad_campaigns, ad_impressions, approval_requests, etc.)
---
## START COMMAND
Say **"start phase 0"** and I begin.
......@@ -4,15 +4,15 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#0A0A14" />
<meta name="theme-color" content="#071120" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<title>EL3AB - العب</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@400;600;700;900&display=swap" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans+Arabic:wght@400;500;600;700&family=Inter:wght@400;500;600;700&family=Cairo:wght@600;700;900&display=swap" rel="stylesheet" />
</head>
<body class="bg-background text-text-primary font-cairo antialiased overflow-x-hidden">
<body class="bg-background text-text-primary antialiased overflow-x-hidden">
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
......
import { motion, AnimatePresence } from 'framer-motion'
import { Gift, Coins, Flame, Sparkles } from 'lucide-react'
import { Gift, Coins, Flame } from 'lucide-react'
import { Button } from './ui/Button'
interface DailyRewardModalProps {
......@@ -20,60 +20,45 @@ export function DailyRewardModal({ open, streak, reward, loading, onClaim, onClo
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
onClick={onClose}
>
<motion.div
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 }}
className="w-full max-w-[340px] rounded-[var(--radius-hero)] bg-surface-1 border border-border p-6 flex flex-col items-center gap-5"
style={{ boxShadow: 'var(--shadow-3)' }}
initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.8, opacity: 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 25 }}
exit={{ scale: 0.9, opacity: 0 }}
transition={{ duration: 0.2 }}
onClick={(e) => e.stopPropagation()}
>
<div className="absolute top-0 left-0 w-full h-32 bg-gradient-to-b from-gold/8 to-transparent" />
<motion.div
className="w-20 h-20 rounded-full bg-gradient-to-br from-gold/20 to-gold/5 border-3 border-gold/50 flex items-center justify-center relative"
animate={{ rotate: [0, 5, -5, 0] }}
transition={{ duration: 3, repeat: Infinity }}
>
<Gift size={36} className="text-gold" />
<motion.div
className="absolute -top-1 -right-1"
animate={{ scale: [1, 1.3, 1], opacity: [1, 0.6, 1] }}
transition={{ duration: 1.5, repeat: Infinity }}
>
<Sparkles size={16} className="text-gold" />
</motion.div>
</motion.div>
<div className="w-16 h-16 rounded-[var(--radius-large)] bg-gold/10 border border-gold/30 flex items-center justify-center">
<Gift size={32} className="text-gold" />
</div>
<div className="text-center z-10">
<h3 className="text-xl font-black text-text-primary">المكافأة اليومية</h3>
<div className="text-center">
<h3 className="text-xl font-bold text-text-primary">المكافأة اليومية</h3>
<p className="text-sm text-text-muted mt-1">ادخل كل يوم واحصل على مكافآت اكثر</p>
</div>
<div className="flex items-center gap-3 bg-surface-2 rounded-2xl px-5 py-3 border border-border">
<Flame size={20} className="text-coral" />
<span className="text-sm font-bold text-text-secondary">سلسلة:</span>
<span className="text-lg font-black text-coral">{streak}</span>
<div className="flex items-center gap-3 bg-surface-2 rounded-[var(--radius-standard)] px-4 py-3 border border-border">
<Flame size={18} className="text-coral" />
<span className="text-sm font-medium text-text-secondary">سلسلة:</span>
<span className="text-lg font-bold text-coral">{streak}</span>
<span className="text-sm text-text-muted">يوم</span>
</div>
<motion.div
className="flex items-center gap-3 bg-gold/10 rounded-2xl px-6 py-4 border border-gold/30"
animate={{ scale: [1, 1.02, 1] }}
transition={{ duration: 2, repeat: Infinity }}
>
<Coins size={28} className="text-gold" />
<span className="text-3xl font-black text-gold">+{reward}</span>
</motion.div>
<div className="flex items-center gap-3 bg-gold/10 rounded-[var(--radius-standard)] px-5 py-4 border border-gold/20">
<Coins size={24} className="text-gold" />
<span className="text-2xl font-bold text-gold">+{reward}</span>
</div>
<Button
variant="gold"
size="lg"
onClick={onClaim}
loading={loading}
className="w-[80%] mx-auto"
className="w-full"
>
استلم المكافأة
</Button>
......
......@@ -10,120 +10,45 @@ 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">
<div className="app-container !p-0">
{/* 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>
<nav className="fixed bottom-0 left-0 right-0 z-50 bg-surface-1 border-t border-border">
<div className="flex items-center justify-around px-4 py-2 max-w-[480px] mx-auto">
{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="relative flex flex-col items-center gap-1 py-1 min-w-[44px] min-h-[44px] justify-center"
whileTap={{ scale: 0.9 }}
transition={{ duration: 0.15 }}
>
<Icon
size={22}
className={isActive ? 'text-gold' : 'text-text-muted'}
strokeWidth={isActive ? 2.5 : 2}
/>
<span
className={`text-[11px] font-semibold ${isActive ? 'text-gold' : 'text-text-muted'}`}
>
{item.label}
</span>
{isActive && (
<motion.div
className="absolute -bottom-2 w-6 h-[3px] rounded-full bg-gold"
layoutId="nav-indicator"
transition={{ duration: 0.2 }}
/>
)}
</motion.button>
)
})}
</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',
<div className="fixed inset-0 pointer-events-none z-0">
<div
className="absolute inset-0"
style={{
background: 'radial-gradient(ellipse at 50% 0%, rgba(23, 37, 60, 0.5) 0%, transparent 60%)',
}}
>
<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>
)
}
......@@ -2,7 +2,6 @@ import { motion } from 'framer-motion'
import { Bell, Coins, Gem } from 'lucide-react'
import { useNotificationStore } from '../../stores/notificationStore'
import { useAuthStore } from '../../stores/authStore'
import { GoldCrown } from '../icons/GoldCrown'
import { useNavigate } from 'react-router-dom'
export function Header() {
......@@ -11,100 +10,55 @@ export function Header() {
const navigate = useNavigate()
return (
<header className="sticky top-0 z-50 px-4 pt-3 pb-2">
<div className="app-container !p-0">
{/* 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
<header className="sticky top-0 z-50 bg-surface-1/95 backdrop-blur-sm border-b border-border">
<div className="app-container flex items-center justify-between h-14">
{/* Logo */}
<div className="flex items-center gap-2">
<span className="text-lg font-bold text-gold tracking-wide">
EL3AB
</span>
{profile && (
<div className="flex items-center justify-center w-7 h-7 rounded-full bg-surface-2 border border-border">
<span className="text-[11px] font-bold text-gold">
{profile.level || 1}
</span>
</div>
)}
</div>
{/* Center: Level indicator */}
{profile && (
<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>
{/* Resources + Bell */}
<div className="flex items-center gap-3">
{profile && (
<>
<div className="flex items-center gap-1.5 px-3 py-1.5 rounded-[var(--radius-tiny)] bg-surface-2 border border-border">
<Coins size={14} className="text-gold" />
<span className="text-xs font-semibold text-text-primary">{profile.coins}</span>
</div>
)}
{/* 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-gold/10 border-2 border-gold/40 shadow-[inset_0_0_8px_rgba(255,200,60,0.15)]"
whileTap={{ scale: 0.9 }}
>
<Coins size={14} className="text-gold" />
<span className="text-xs font-black text-gold">{profile.coins}</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>
)}
</>
{profile.gems > 0 && (
<div className="flex items-center gap-1.5 px-3 py-1.5 rounded-[var(--radius-tiny)] bg-surface-2 border border-border">
<Gem size={12} className="text-purple" />
<span className="text-xs font-semibold text-text-primary">{profile.gems}</span>
</div>
)}
</>
)}
{/* 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>
<motion.button
className="relative flex items-center justify-center w-10 h-10 rounded-[var(--radius-tiny)] bg-surface-2 border border-border"
onClick={() => navigate('/notifications')}
whileTap={{ scale: 0.9 }}
transition={{ duration: 0.15 }}
>
<Bell size={18} className="text-text-secondary" />
{unreadCount > 0 && (
<div className="absolute -top-1 -right-1 w-5 h-5 rounded-full bg-coral flex items-center justify-center">
<span className="text-[10px] font-bold text-white">
{unreadCount > 9 ? '9+' : unreadCount}
</span>
</div>
)}
</motion.button>
</div>
</div>
</header>
......
......@@ -9,11 +9,11 @@ interface PageTransitionProps {
export function PageTransition({ children, className = '' }: PageTransitionProps) {
return (
<motion.div
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 }}
className={`app-container py-6 pb-24 ${className}`}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
>
{children}
</motion.div>
......
......@@ -5,7 +5,7 @@ import { Loader2 } from 'lucide-react'
interface ButtonProps {
children: ReactNode
onClick?: () => void
variant?: 'gold' | 'ghost' | 'coral' | 'cyan' | 'purple'
variant?: 'gold' | 'ghost' | 'blue' | 'cyan' | 'danger'
size?: 'sm' | 'md' | 'lg'
disabled?: boolean
loading?: boolean
......@@ -13,18 +13,18 @@ interface ButtonProps {
type?: 'button' | 'submit'
}
const variants = {
gold: 'bg-gradient-to-b from-gold-light to-gold text-background font-black shadow-lg shadow-gold/30 border-b-4 border-gold-muted',
ghost: 'bg-surface-2 border-2 border-border text-text-primary font-bold hover:border-gold/50 hover:bg-surface-3',
coral: 'bg-gradient-to-b from-[#FF7070] to-coral text-white font-black shadow-lg shadow-coral/25 border-b-4 border-[#B83A3A]',
cyan: 'bg-gradient-to-b from-[#33FFE5] to-cyan text-background font-black shadow-lg shadow-cyan/25 border-b-4 border-[#009E8E]',
purple: 'bg-gradient-to-b from-[#CC70FF] to-purple text-white font-black shadow-lg shadow-purple/25 border-b-4 border-[#6B3D8F]',
const variantStyles = {
gold: 'btn-gold',
blue: 'btn-blue',
cyan: 'btn-cyan',
ghost: 'btn-ghost',
danger: 'btn-danger',
}
const sizes = {
sm: 'px-5 py-2 text-sm rounded-xl gap-2 min-w-[90px]',
md: 'px-7 py-3 text-base rounded-2xl gap-2.5 min-w-[120px]',
lg: 'px-10 py-4 text-lg rounded-2xl gap-3 min-w-[160px]',
const sizeStyles = {
sm: 'px-4 py-2 text-sm min-h-[36px]',
md: 'px-6 py-3 text-base min-h-[44px]',
lg: 'px-8 py-4 text-lg min-h-[52px]',
}
export function Button({
......@@ -42,10 +42,9 @@ export function Button({
type={type}
onClick={onClick}
disabled={disabled || loading}
className={`inline-flex items-center justify-center transition-all uppercase tracking-wide ${variants[variant]} ${sizes[size]} ${disabled ? 'opacity-50 cursor-not-allowed saturate-50' : ''} ${className}`}
whileTap={!disabled ? { scale: 0.92, y: 2 } : undefined}
whileHover={!disabled ? { scale: 1.03 } : undefined}
transition={{ type: 'spring', stiffness: 600, damping: 20 }}
className={`btn ${variantStyles[variant]} ${sizeStyles[size]} ${disabled ? 'opacity-50 cursor-not-allowed' : ''} ${className}`}
whileTap={!disabled ? { scale: 0.96, y: 2 } : undefined}
transition={{ duration: 0.15 }}
>
{loading && <Loader2 size={18} className="animate-spin" />}
{children}
......
......@@ -4,27 +4,20 @@ import type { ReactNode } from 'react'
interface CardProps {
children: ReactNode
className?: string
glow?: boolean
onClick?: () => void
variant?: 'default' | 'gold' | 'elevated'
variant?: 'default' | 'elevated'
}
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',
}
export function Card({ children, className = '', onClick, variant = 'default' }: CardProps) {
const base = variant === 'elevated' ? 'card card-elevated' : 'card'
return (
<motion.div
className={`${base} ${variantStyles[variant]} ${onClick ? 'cursor-pointer' : ''} ${className}`}
whileHover={onClick ? { y: -4, scale: 1.01, boxShadow: '0 12px 32px rgba(0,0,0,0.5)' } : undefined}
whileTap={onClick ? { scale: 0.97 } : undefined}
className={`${base} ${onClick ? 'cursor-pointer' : ''} ${className}`}
whileHover={onClick ? { y: -2 } : undefined}
whileTap={onClick ? { scale: 0.98 } : undefined}
onClick={onClick}
transition={{ type: 'spring', stiffness: 400, damping: 22 }}
transition={{ duration: 0.15 }}
>
{children}
</motion.div>
......
......@@ -4,8 +4,7 @@ import type { ReactNode } from 'react'
interface GamePanelProps {
children: ReactNode
className?: string
variant?: 'default' | 'gold' | 'legendary' | 'recessed'
rivets?: boolean
variant?: 'default' | 'elevated'
onClick?: () => void
}
......@@ -13,52 +12,21 @@ 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' : ''
const styles = variant === 'elevated'
? 'bg-surface-2 border border-border-strong shadow-[var(--shadow-2)]'
: 'bg-surface-1 border border-border shadow-[var(--shadow-1)]'
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}
className={`rounded-[var(--radius-large)] p-4 md:p-6 relative ${styles} ${onClick ? 'cursor-pointer' : ''} ${className}`}
whileHover={onClick ? { y: -2 } : undefined}
whileTap={onClick ? { scale: 0.98 } : undefined}
onClick={onClick}
transition={{ type: 'spring', stiffness: 400, damping: 22 }}
transition={{ duration: 0.15 }}
>
{/* 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>
{children}
</motion.div>
)
}
interface GameProgressBarProps {
value: number
max: number
color?: 'gold' | 'cyan' | 'green' | 'purple'
color?: 'gold' | 'cyan' | 'green' | 'blue'
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',
const fillColors: Record<string, string> = {
gold: 'bg-gold',
cyan: 'bg-cyan',
green: 'bg-green',
blue: 'bg-royal-blue',
}
export function GameProgressBar({
......@@ -22,25 +22,15 @@ export function GameProgressBar({
return (
<div className="w-full">
<div className="progress-game relative">
{/* Fill bar */}
<div className="h-3 rounded-[var(--radius-tiny)] bg-surface-3 overflow-hidden">
<div
className={`${fillColorClass[color]} relative`}
className={`h-full rounded-[var(--radius-tiny)] ${fillColors[color]} transition-all duration-300`}
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 font-medium">{value}</span>
<span className="text-xs text-text-muted">{max}</span>
</div>
)}
......
......@@ -3,66 +3,14 @@ import type { ComponentType } from 'react'
interface RibbonHeaderProps {
text: string
icon?: ComponentType<{ size?: number; className?: string }>
color?: 'gold' | 'cyan' | 'purple' | 'coral'
size?: 'sm' | 'md'
className?: string
}
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]
export function RibbonHeader({ text, icon: Icon, className = '' }: RibbonHeaderProps) {
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 className={`flex items-center gap-2 mb-4 ${className}`}>
{Icon && <Icon size={20} className="text-gold" />}
<h2 className="text-lg font-bold text-text-primary">{text}</h2>
</div>
)
}
......@@ -2,9 +2,8 @@ import type { ReactNode } from 'react'
interface ShieldBadgeProps {
children: ReactNode
size?: 'sm' | 'md' | 'lg' | 'xl'
color?: 'gold' | 'cyan' | 'purple' | 'coral' | 'default'
glow?: boolean
size?: 'sm' | 'md' | 'lg'
color?: 'gold' | 'cyan' | 'blue' | 'default'
className?: string
}
......@@ -12,72 +11,26 @@ 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)',
},
default: 'bg-surface-2 border-border',
gold: 'bg-surface-2 border-gold/40',
cyan: 'bg-surface-2 border-cyan/40',
blue: 'bg-surface-2 border-royal-blue/40',
}
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',
}}
className={`inline-flex items-center justify-center rounded-[var(--radius-standard)] border-2 ${sizeMap[size]} ${colorMap[color]} ${className}`}
>
{/* 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>
{children}
</div>
)
}
@import "tailwindcss";
@theme {
/* === BACKGROUNDS === */
--color-background: #0B0E1A;
--color-surface-1: #151929;
--color-surface-2: #1E2340;
--color-surface-3: #2A3055;
--color-border: #3D4570;
--color-border-gold: rgba(255, 200, 60, 0.35);
/* === BRAND (vibrant Brawl Stars palette) === */
--color-gold: #FFC83D;
--color-gold-light: #FFE066;
--color-gold-muted: #C9972E;
/* === BACKGROUNDS (from spec) === */
--color-background: #071120;
--color-surface-1: #0D1728;
--color-surface-2: #132238;
--color-surface-3: #1A2D4A;
--color-border: rgba(255, 255, 255, 0.08);
--color-border-strong: rgba(255, 255, 255, 0.15);
/* === BRAND === */
--color-gold: #E7B83D;
--color-gold-light: #F0CC5F;
--color-gold-muted: #A3822B;
--color-coral: #FF5252;
--color-royal-blue: #4D8BFF;
--color-cyan: #00E5CC;
--color-royal-blue: #2979FF;
--color-cyan: #15D7FF;
--color-purple: #B44DFF;
--color-electric-blue: #3B82F6;
--color-green: #4ADE80;
--color-orange: #FF8C42;
/* === TEXT === */
--color-text-primary: #FFFFFF;
--color-text-secondary: #B8BDD6;
--color-text-muted: #6E748C;
/* === SEMANTIC === */
--color-win: #00E5CC;
--color-loss: #FF5252;
--color-draw: #B8BDD6;
--font-family-cairo: 'Cairo', sans-serif;
--color-text-primary: #F4F7FB;
--color-text-secondary: #B8C4D6;
--color-text-muted: #6B7A8D;
/* === SEMANTIC (status) === */
--color-win: #10B981;
--color-loss: #DC2626;
--color-draw: #F59E0B;
--color-live: #E7B83D;
--color-online: #22C55E;
/* === TYPOGRAPHY === */
--font-family-primary: 'IBM Plex Sans Arabic', 'Inter', sans-serif;
--font-family-display: 'Cairo', 'Inter', sans-serif;
/* === SPACING (8pt grid) === */
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;
--spacing-2xl: 48px;
--spacing-3xl: 64px;
--spacing-4xl: 96px;
/* === RADIUS === */
--radius-tiny: 8px;
--radius-standard: 12px;
--radius-large: 16px;
--radius-hero: 24px;
/* === SHADOWS === */
--shadow-1: 0 2px 8px rgba(0, 0, 0, 0.2);
--shadow-2: 0 4px 16px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.15);
--shadow-3: 0 8px 32px rgba(0, 0, 0, 0.4), 0 4px 8px rgba(0, 0, 0, 0.2);
}
* {
......@@ -41,7 +65,7 @@
}
html {
font-family: var(--font-family-cairo);
font-family: var(--font-family-primary);
}
body {
......@@ -50,6 +74,8 @@ body {
color: var(--color-text-primary);
overflow-x: hidden;
-webkit-tap-highlight-color: transparent;
font-size: 16px;
line-height: 1.5;
}
#root {
......@@ -68,256 +94,166 @@ body {
}
::-webkit-scrollbar-thumb {
background: var(--color-border);
border-radius: 3px;
background: var(--color-border-strong);
border-radius: var(--radius-tiny);
}
::-webkit-scrollbar-thumb:hover {
background: var(--color-gold-muted);
/* === CONTAINER SYSTEM === */
.app-container {
width: 100%;
max-width: 100%;
margin: 0 auto;
padding-left: var(--spacing-md);
padding-right: var(--spacing-md);
}
/* ============================================================
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%);
@media (min-width: 768px) {
.app-container {
max-width: 768px;
padding-left: var(--spacing-lg);
padding-right: var(--spacing-lg);
}
}
/* ============================================================
DECORATIVE CLIP-PATH SHAPES
============================================================ */
.clip-shield {
clip-path: polygon(50% 0%, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%);
@media (min-width: 1024px) {
.app-container {
max-width: 1440px;
padding-left: var(--spacing-xl);
padding-right: var(--spacing-xl);
}
}
.clip-diamond {
clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
/* === CARD SYSTEM === */
.card {
border-radius: var(--radius-standard);
background: var(--color-surface-2);
border: 1px solid var(--color-border);
box-shadow: var(--shadow-1);
padding: var(--spacing-md);
}
.clip-nameplate {
clip-path: polygon(5% 0%, 100% 0%, 95% 100%, 0% 100%);
@media (min-width: 768px) {
.card {
padding: var(--spacing-lg);
}
}
.ribbon-banner {
clip-path: polygon(8% 0%, 92% 0%, 100% 100%, 0% 100%);
.card-elevated {
background: var(--color-surface-3);
box-shadow: var(--shadow-2);
}
/* ============================================================
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);
/* === BUTTON SYSTEM === */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--spacing-sm);
border-radius: var(--radius-standard);
font-weight: 600;
font-size: 16px;
min-height: 44px;
min-width: 44px;
padding: var(--spacing-sm) var(--spacing-md);
transition: transform 0.15s ease, box-shadow 0.15s ease, opacity 0.15s ease;
cursor: pointer;
border: none;
outline: none;
}
.game-panel-gold {
border-color: var(--color-gold);
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);
.btn:active {
transform: scale(0.97);
}
.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);
}
/* ============================================================
3D BUTTON SYSTEM
============================================================ */
.btn-3d {
position: relative;
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;
.btn-gold {
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;
color: #071120;
box-shadow: var(--shadow-2), 0 4px 0 var(--color-gold-muted);
}
.btn-3d-coral {
--btn-shadow-color: #8B1A1A;
background: linear-gradient(180deg, #FF7575 0%, var(--color-coral) 100%);
color: #fff;
.btn-gold:active {
box-shadow: var(--shadow-1), 0 2px 0 var(--color-gold-muted);
transform: translateY(2px) scale(0.97);
}
.btn-3d-purple {
--btn-shadow-color: #4A1A8B;
background: linear-gradient(180deg, #C97AFF 0%, var(--color-purple) 100%);
color: #fff;
.btn-blue {
background: linear-gradient(180deg, #4D9AFF 0%, var(--color-royal-blue) 100%);
color: #FFFFFF;
box-shadow: var(--shadow-2), 0 4px 0 #1A5ACC;
}
/* ============================================================
GAME PROGRESS BAR
============================================================ */
.progress-game {
height: 16px;
border-radius: 8px;
background: var(--color-surface-3);
border: 2px solid var(--color-border);
overflow: hidden;
.btn-cyan {
background: linear-gradient(180deg, #40E8FF 0%, var(--color-cyan) 100%);
color: #071120;
box-shadow: var(--shadow-2), 0 4px 0 #0A9DB3;
}
.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);
.btn-ghost {
background: transparent;
color: var(--color-text-secondary);
border: 1px solid var(--color-border-strong);
}
.progress-game-fill-cyan {
background: linear-gradient(90deg, #00B8A5, var(--color-cyan));
box-shadow: 0 0 12px rgba(0, 229, 204, 0.5);
.btn-ghost:hover {
background: var(--color-surface-2);
}
.progress-game-fill-green {
background: linear-gradient(90deg, #2DBF60, var(--color-green));
box-shadow: 0 0 12px rgba(74, 222, 128, 0.5);
.btn-danger {
background: linear-gradient(180deg, #FF6B6B 0%, var(--color-coral) 100%);
color: #FFFFFF;
box-shadow: var(--shadow-2), 0 4px 0 #B33A3A;
}
.progress-game-fill-purple {
background: linear-gradient(90deg, #9333EA, var(--color-purple));
box-shadow: 0 0 12px rgba(180, 77, 255, 0.5);
/* === SECTION RHYTHM === */
.section-gap {
margin-bottom: var(--spacing-xl);
}
/* ============================================================
ANIMATIONS
============================================================ */
@keyframes shimmer {
0% {
transform: translateX(-100%) skewX(-15deg);
}
100% {
transform: translateX(200%) skewX(-15deg);
@media (min-width: 768px) {
.section-gap {
margin-bottom: var(--spacing-2xl);
}
}
@keyframes badge-bounce {
0%, 100% {
transform: scale(1);
}
25% {
transform: scale(1.15);
}
50% {
transform: scale(0.95);
}
75% {
transform: scale(1.05);
@media (min-width: 1024px) {
.section-gap {
margin-bottom: var(--spacing-3xl);
}
}
@keyframes rays-rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
/* === ANIMATIONS (spec-compliant) === */
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes float-gentle {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-8px);
}
@keyframes fade-up {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes pulse-soft {
0%, 100% {
opacity: 0.6;
}
50% {
opacity: 1;
}
0%, 100% { opacity: 0.6; }
50% { opacity: 1; }
}
.animate-shimmer {
animation: shimmer 3s ease-in-out infinite;
}
.animate-badge-bounce {
animation: badge-bounce 0.5s ease-in-out;
@keyframes shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
.animate-rays-rotate {
animation: rays-rotate 90s linear infinite;
.animate-fade-in {
animation: fade-in 0.2s ease-out;
}
.animate-float-gentle {
animation: float-gentle 3s ease-in-out infinite;
.animate-fade-up {
animation: fade-up 0.2s ease-out;
}
.animate-pulse-soft {
animation: pulse-soft 2s 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 {
......@@ -326,7 +262,7 @@ body {
right: 0;
width: 260px;
height: 100dvh;
border-left: 2px solid var(--color-border);
border-left: 1px solid var(--color-border);
background: var(--color-surface-1);
z-index: 40;
}
......@@ -335,3 +271,13 @@ body {
margin-right: 260px;
}
}
/* === UTILITY === */
.text-gold { color: var(--color-gold); }
.text-cyan { color: var(--color-cyan); }
.text-win { color: var(--color-win); }
.text-loss { color: var(--color-loss); }
.text-draw { color: var(--color-draw); }
.bg-surface-1 { background-color: var(--color-surface-1); }
.bg-surface-2 { background-color: var(--color-surface-2); }
.bg-surface-3 { background-color: var(--color-surface-3); }
......@@ -86,7 +86,7 @@ export function BotSelectPage() {
>
<motion.div
onClick={() => setSelectedBot(bot.id)}
className={`game-panel !p-4 flex items-center gap-4 relative overflow-hidden cursor-pointer transition-all ${
className={`card !p-4 flex items-center gap-4 relative overflow-hidden cursor-pointer transition-all ${
isSelected ? '!border-[#FFC83D]' : ''
}`}
style={{
......@@ -192,7 +192,7 @@ export function BotSelectPage() {
<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"
className="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} />
......
......@@ -128,7 +128,7 @@ export function FriendsPage() {
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="card !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%)' }}
>
......@@ -152,7 +152,7 @@ export function FriendsPage() {
<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"
className="px-4 py-2 rounded-xl bg-[#FFC83D] text-background text-xs font-black"
>
اضافة
</motion.button>
......@@ -180,7 +180,7 @@ export function FriendsPage() {
</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 key={req.id} className="card !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%)' }}
>
......@@ -202,14 +202,14 @@ export function FriendsPage() {
<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"
className="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"
className="px-3.5 py-2 rounded-xl bg-[#FF5252] text-white text-xs font-black"
>
رفض
</motion.button>
......@@ -229,7 +229,7 @@ export function FriendsPage() {
</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">
<div key={friend.id} className="card !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" />
......@@ -283,7 +283,7 @@ export function FriendsPage() {
</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">
<div key={friend.id} className="card !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" />
......
......@@ -423,7 +423,7 @@ function MultiplayerGameView({ matchId }: { matchId: string }) {
{/* Action buttons */}
<div className="flex items-center gap-4 mt-5">
<Button
variant="coral"
variant="danger"
size="md"
onClick={resign}
disabled={gameOver}
......@@ -913,7 +913,7 @@ function BotGameView({ botId }: { botId: string | undefined }) {
{/* Action buttons */}
<div className="flex items-center gap-4 mt-5">
<Button
variant="coral"
variant="danger"
size="md"
onClick={() => { setGameOver(true); setResult('استسلام'); playSound('lose') }}
>
......
import { useState } from 'react'
import { motion } from 'framer-motion'
import { Play, TrendingUp, Swords, Flame, Bot, Users, Lightbulb, Crown } from 'lucide-react'
import { Play, TrendingUp, Swords, Flame, Bot, Users, Lightbulb } from 'lucide-react'
import { useNavigate } from 'react-router-dom'
import { useAuthStore } from '../stores/authStore'
import { PageTransition } from '../components/layout/PageTransition'
......@@ -20,14 +20,12 @@ const dailyTips = [
const stagger = {
hidden: {},
show: {
transition: { staggerChildren: 0.08 },
},
show: { transition: { staggerChildren: 0.06 } },
}
const fadeUp = {
hidden: { opacity: 0, y: 24 },
show: { opacity: 1, y: 0, transition: { type: 'spring' as const, stiffness: 400, damping: 24 } },
hidden: { opacity: 0, y: 12 },
show: { opacity: 1, y: 0, transition: { duration: 0.2 } },
}
export function HomePage() {
......@@ -59,266 +57,118 @@ export function HomePage() {
variants={stagger}
initial="hidden"
animate="show"
className="flex flex-col gap-7"
className="flex flex-col gap-6"
>
{/* === 1. PLAYER NAMEPLATE — Angled Banner === */}
{/* Welcome */}
{profile && (
<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)' }}
/>
<motion.div variants={fadeUp} className="flex items-center gap-3">
<div className="w-12 h-12 rounded-[var(--radius-standard)] bg-surface-2 border border-border flex items-center justify-center">
<span className="text-lg font-bold text-gold">
{profile.display_name?.charAt(0) || 'L'}
</span>
</div>
<div>
<h2 className="text-xl font-bold text-text-primary">
{profile.display_name}
</h2>
<span className="text-sm text-text-muted">
المستوى {profile.level}
</span>
</div>
</motion.div>
)}
{/* === 2. PLAY BUTTON — The Hero Crest === */}
<motion.div variants={fadeUp} className="flex justify-center">
{/* Play Button */}
<motion.div variants={fadeUp}>
<motion.button
onClick={() => {
playSound('tap')
navigate('/play')
}}
className="relative w-[85%] cursor-pointer"
whileTap={{ y: 4, scale: 0.97 }}
transition={{ type: 'spring', stiffness: 600, damping: 20 }}
className="btn-gold w-full py-6 rounded-[var(--radius-hero)] text-2xl font-bold"
whileTap={{ scale: 0.96, y: 2 }}
transition={{ duration: 0.15 }}
>
{/* 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="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
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>
<Play size={32} fill="currentColor" />
<span>العب الان</span>
</motion.button>
</motion.div>
{/* === 3. STATS AS SHIELD BADGES === */}
{/* Stats */}
{profile && (
<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
<motion.div variants={fadeUp} className="grid grid-cols-3 gap-3">
<StatCard
icon={<TrendingUp size={18} className="text-gold" />}
value={profile.elo_blitz}
label="تقييم"
gradient="linear-gradient(135deg, rgba(255,200,60,0.15), rgba(255,200,60,0.05))"
borderColor="#FFC83D"
/>
<StatBadge
<StatCard
icon={<Swords size={18} className="text-cyan" />}
value={profile.total_games_played}
label="مباراة"
gradient="linear-gradient(135deg, rgba(0,229,204,0.15), rgba(0,229,204,0.05))"
borderColor="#00E5CC"
isCenter
/>
<StatBadge
<StatCard
icon={<Flame size={18} className="text-coral" />}
value={profile.win_streak}
label="سلسلة فوز"
gradient="linear-gradient(135deg, rgba(255,82,82,0.15), rgba(255,82,82,0.05))"
borderColor="#FF5252"
/>
</motion.div>
)}
{/* === 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('tap')
navigate('/bot-select')
}}
>
<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>
</motion.button>
<motion.button
className="relative cursor-pointer"
style={{ transform: 'rotate(1deg)' }}
whileTap={{ y: 4, boxShadow: 'none' }}
onClick={() => {
playSound('tap')
navigate('/friends')
}}
>
<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>
</motion.button>
{/* Quick Actions */}
<motion.div variants={fadeUp} className="grid grid-cols-2 gap-4">
<QuickAction
icon={<Bot size={24} className="text-purple" />}
title="العب ضد روبوت"
subtitle="تدريب وتحسين"
onClick={() => { playSound('tap'); navigate('/bot-select') }}
/>
<QuickAction
icon={<Users size={24} className="text-gold" />}
title="تحدى صديق"
subtitle="ارسل دعوة"
onClick={() => { playSound('tap'); navigate('/friends') }}
/>
</motion.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>
</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>
{/* Daily Tip */}
<motion.div variants={fadeUp} className="card">
<div className="flex items-center gap-2 mb-3">
<Lightbulb size={16} className="text-gold" />
<span className="text-sm font-semibold text-gold">نصيحة اليوم</span>
</div>
<p className="text-sm text-text-secondary leading-relaxed">
{todayTip}
</p>
</motion.div>
</motion.div>
</PageTransition>
)
}
/* --- 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
}) {
function StatCard({ icon, value, label }: { icon: React.ReactNode; value: number; label: string }) {
return (
<div
className="relative flex flex-col items-center justify-center z-10"
style={{ transform: isCenter ? 'scale(1.08)' : 'scale(1)' }}
<div className="card flex flex-col items-center gap-1 py-3">
{icon}
<span className="text-lg font-bold text-text-primary">{value}</span>
<span className="text-[11px] text-text-muted">{label}</span>
</div>
)
}
function QuickAction({ icon, title, subtitle, onClick }: { icon: React.ReactNode; title: string; subtitle: string; onClick: () => void }) {
return (
<motion.button
className="card flex flex-col items-center gap-2 py-5 cursor-pointer"
onClick={onClick}
whileTap={{ scale: 0.96 }}
transition={{ duration: 0.15 }}
>
<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>
{icon}
<div className="text-center">
<p className="text-sm font-semibold text-text-primary">{title}</p>
<p className="text-[11px] text-text-muted mt-0.5">{subtitle}</p>
</div>
<span className="text-[9px] text-text-muted font-bold mt-1">{label}</span>
</div>
</motion.button>
)
}
......@@ -178,9 +178,9 @@ export function MatchmakingPage() {
جاري البحث عن خصم
</motion.h2>
{/* Timer in a game-panel mini-bar */}
{/* Timer in a card mini-bar */}
<motion.div
className="mt-4 game-panel !py-2 !px-6 inline-flex items-center justify-center"
className="mt-4 card !py-2 !px-6 inline-flex items-center justify-center"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.4 }}
......@@ -217,7 +217,7 @@ export function MatchmakingPage() {
<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"
className="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)' }}
>
الغاء
......
import { useState } from 'react'
import { motion } from 'framer-motion'
import { Zap, Timer, Clock, Hourglass, Lock, Cpu, Crown, Swords } from 'lucide-react'
import { Zap, Timer, Clock, Hourglass, Lock, Cpu, Swords } from 'lucide-react'
import { useNavigate } from 'react-router-dom'
import { PageTransition } from '../components/layout/PageTransition'
import { GAMES, TIME_CONTROLS } from '../lib/constants'
......@@ -15,14 +15,12 @@ const CATEGORIES = [
const stagger = {
hidden: {},
show: {
transition: { staggerChildren: 0.07 },
},
show: { transition: { staggerChildren: 0.06 } },
}
const fadeUp = {
hidden: { opacity: 0, y: 24 },
show: { opacity: 1, y: 0, transition: { type: 'spring' as const, stiffness: 400, damping: 24 } },
hidden: { opacity: 0, y: 12 },
show: { opacity: 1, y: 0, transition: { duration: 0.2 } },
}
export function PlayPage() {
......@@ -37,187 +35,79 @@ export function PlayPage() {
)?.[1].category
return (
<PageTransition className="!py-6">
<PageTransition>
<motion.div
variants={stagger}
initial="hidden"
animate="show"
className="flex flex-col gap-5"
className="flex flex-col gap-6"
>
{/* === 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>
{/* Page Title */}
<motion.div variants={fadeUp}>
<h1 className="text-2xl font-bold text-text-primary">اختر اللعبة</h1>
</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>
{/* Featured Game: Chess */}
<motion.div variants={fadeUp} className="card border-gold/20">
<div className="flex items-center gap-4">
<div className="w-14 h-14 rounded-[var(--radius-standard)] bg-gold/10 border border-gold/20 flex items-center justify-center">
<span className="text-2xl">♟️</span>
</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 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 className="flex-1">
<span className="text-lg font-bold text-text-primary">{chessGame.nameAr}</span>
<p className="text-sm text-text-secondary mt-0.5">
العب شطرنج اونلاين ضد لاعبين حقيقيين
</p>
<div className="flex items-center gap-2 mt-2">
<div className="w-2 h-2 rounded-full bg-online animate-pulse-soft" />
<span className="text-xs text-online font-medium">اونلاين</span>
</div>
</div>
{/* Gold bottom accent */}
<div className="h-[3px] w-full bg-gradient-to-r from-transparent via-gold to-transparent" />
</div>
</motion.div>
{/* === 3. OTHER GAMES — Compact Locked Tiles === */}
<motion.div variants={fadeUp} className="grid grid-cols-2 gap-2.5">
{/* Other Games (locked) */}
<motion.div variants={fadeUp} className="grid grid-cols-2 gap-3">
{otherGames.map((game) => (
<div
key={game.key}
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)',
}}
className="card flex items-center gap-2 py-3 px-3 opacity-50"
>
{/* 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>
<Lock size={14} className="text-text-muted" />
<div>
<span className="text-sm font-medium text-text-muted block">{game.nameAr}</span>
<span className="text-[11px] text-text-muted">قريبا</span>
</div>
</div>
))}
</motion.div>
{/* === 4. TIME CONTROL SECTION === */}
{/* Time Control */}
<motion.div variants={fadeUp} className="space-y-4">
{/* 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>
<h2 className="text-lg font-bold text-text-primary">نظام الوقت</h2>
{/* Category Tabs — Trapezoidal */}
<div className="flex gap-2 justify-center overflow-x-auto scrollbar-hide pb-1">
{/* Category Tabs */}
<div className="flex gap-2">
{CATEGORIES.map((cat) => {
const isActive = activeCategory === cat.key
const Icon = cat.icon
return (
<motion.button
<button
key={cat.key}
className="relative cursor-pointer"
whileTap={{ scale: 0.92 }}
transition={{ type: 'spring', stiffness: 500, damping: 20 }}
className={`flex items-center gap-1.5 px-4 py-2 rounded-[var(--radius-tiny)] text-sm font-semibold transition-all ${
isActive
? 'bg-gold text-background'
: 'bg-surface-2 text-text-muted border border-border'
}`}
onClick={() => {
playSound('tap')
const first = Object.entries(TIME_CONTROLS).find(([, v]) => v.category === cat.key)
if (first) setSelectedTC(first[0])
}}
>
<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>
<Icon size={14} />
{cat.label}
</button>
)
})}
</div>
......@@ -231,94 +121,51 @@ export function PlayPage() {
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 }}
className={`py-4 px-3 text-center font-bold rounded-[var(--radius-standard)] border transition-all ${
isSelected
? 'border-gold bg-gold/10 text-gold'
: 'border-border bg-surface-1 text-text-secondary'
}`}
whileTap={{ scale: 0.95 }}
transition={{ duration: 0.15 }}
onClick={() => {
playSound('tap')
setSelectedTC(key)
}}
>
<div
className={`py-5 px-3 text-center font-black text-base 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>
{tc.labelAr}
</motion.button>
)
})}
</div>
</motion.div>
{/* === 5. ACTION BUTTONS === */}
<motion.div variants={fadeUp} className="flex flex-col items-center gap-5 mt-2">
{/* Primary: Find opponent — massive 3D gold */}
{/* Action Buttons */}
<motion.div variants={fadeUp} className="flex flex-col gap-3 mt-2">
<motion.button
className="relative w-[88%] cursor-pointer"
whileTap={{ y: 4 }}
transition={{ type: 'spring', stiffness: 600, damping: 20 }}
className="btn-gold w-full py-5 rounded-[var(--radius-large)] text-lg font-bold"
whileTap={{ scale: 0.96, y: 2 }}
transition={{ duration: 0.15 }}
onClick={() => {
playSound('tap')
navigate(`/matchmaking?tc=${selectedTC}&game=chess`)
}}
>
<div
className="relative flex items-center justify-center gap-3 py-6 rounded-2xl font-black text-xl text-background overflow-hidden"
style={{
background: 'linear-gradient(180deg, #FFE066 0%, #FFC83D 70%, #C9972E 100%)',
boxShadow: '0 7px 0 0 #9B6B1A, 0 10px 28px 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={24} className="text-background" />
<span>البحث عن خصم</span>
</div>
<Swords size={22} />
<span>البحث عن خصم</span>
</motion.button>
{/* Secondary: Play vs bot — purple 3D */}
<motion.button
className="relative w-[82%] cursor-pointer"
whileTap={{ y: 3 }}
transition={{ type: 'spring', stiffness: 600, damping: 20 }}
className="btn-ghost w-full py-4 rounded-[var(--radius-large)] text-base font-semibold"
whileTap={{ scale: 0.96 }}
transition={{ duration: 0.15 }}
onClick={() => {
playSound('tap')
navigate('/bot-select')
}}
>
<div
className="flex items-center justify-center gap-3 py-5 rounded-2xl font-black text-base text-text-primary border-3 border-purple/40"
style={{
background: 'linear-gradient(180deg, rgba(180,77,255,0.15) 0%, rgba(180,77,255,0.05) 100%)',
boxShadow: '0 6px 0 0 rgba(120,40,180,0.4)',
}}
>
<Cpu size={20} className="text-purple" />
<span>العب ضد الروبوت</span>
</div>
<Cpu size={20} className="text-purple" />
<span>العب ضد الروبوت</span>
</motion.button>
</motion.div>
</motion.div>
......
......@@ -25,7 +25,7 @@ export function SettingsPage() {
<div className="flex flex-col gap-3">
{/* Sound toggle */}
<div className="game-panel !p-4 flex items-center justify-between">
<div className="card !p-4 flex items-center justify-between">
<div className="flex items-center gap-3">
{soundEnabled ? (
<div className="w-10 h-10 rounded-xl bg-[#00E5CC]/15 border-2 border-[#00E5CC]/30 flex items-center justify-center">
......@@ -63,7 +63,7 @@ export function SettingsPage() {
</div>
{/* Report bug */}
<div className="game-panel !p-4 flex items-center gap-3">
<div className="card !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>
......@@ -74,7 +74,7 @@ export function SettingsPage() {
</div>
{/* Version info - recessed panel */}
<div className="game-panel !p-4 flex items-center gap-3"
<div className="card !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">
......@@ -92,7 +92,7 @@ export function SettingsPage() {
<motion.button
whileTap={{ scale: 0.93 }}
onClick={handleLogout}
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"
className="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>
......
......@@ -242,7 +242,7 @@ export function ShopPage() {
onClick={() => !purchasing && setSelectedItem(null)}
>
<motion.div
className="w-full max-w-[340px] game-panel !p-0 flex flex-col items-center relative overflow-hidden"
className="w-full max-w-[340px] card !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 }}
......@@ -333,7 +333,7 @@ export function ShopPage() {
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"
className="w-full py-3 rounded-xl bg-[#00E5CC] text-background text-sm font-black disabled:opacity-50"
>
{purchasing ? 'جاري...' : 'تفعيل'}
</motion.button>
......@@ -365,7 +365,7 @@ export function ShopPage() {
? (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"
className="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>
......
......@@ -84,7 +84,7 @@ export function TournamentsPage() {
{loading ? (
<div className="flex flex-col gap-4">
{[1, 2, 3].map((i) => (
<div key={i} className="game-panel animate-pulse !p-5">
<div key={i} className="card 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>
......@@ -122,7 +122,7 @@ export function TournamentsPage() {
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">
<div className="card 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"
......@@ -207,7 +207,7 @@ export function TournamentsPage() {
<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"
className="px-3.5 py-2 rounded-xl bg-[#FF5252] text-white text-xs font-black"
>
إلغاء
</motion.button>
......@@ -216,7 +216,7 @@ export function TournamentsPage() {
<motion.button
whileTap={{ scale: 0.93 }}
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"
className="w-[75%] py-3 rounded-xl bg-gradient-to-b from-[#FFC83D] to-[#E5A800] text-background text-sm font-black"
>
سجل الان
</motion.button>
......
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