1. 06 Jun, 2026 11 commits
  2. 05 Jun, 2026 29 commits
    • Mahmoud Aglan's avatar
      feat(chess): full time control selection like chess.com · 17bece0d
      Mahmoud Aglan authored
      Categorized into Bullet, Blitz, Rapid, and Classical with Arabic labels:
      - Bullet: 1+0, 1+1, 2+1
      - Blitz: 3+0, 3+2, 5+0, 5+3, 5+5
      - Rapid: 10+0, 10+5, 15+10, 20+0, 30+0
      - Classical: 45+45, 60+0, 90+30
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      17bece0d
    • Mahmoud Aglan's avatar
      chore: add test ad banner asset · c69429f8
      Mahmoud Aglan authored
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      c69429f8
    • Mahmoud Aglan's avatar
      fix(chess): ad banner takes full top void, hidden when no ad · abbd3736
      Mahmoud Aglan authored
      Ad area now flex:1 (absorbs all dead space above the board).
      Hidden by default — only shown when an active campaign is loaded.
      Game UI pushed further down for thumb reach.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      abbd3736
    • Mahmoud Aglan's avatar
    • Mahmoud Aglan's avatar
      feat(chess): ad banner in top space, emote inline with player bar · ebf076a5
      Mahmoud Aglan authored
      - Add ad banner slot in the dead space above the board
      - Move emote toggle button inline with player profile data
      - Emote panel now opens below player bar (no more fixed overlay)
      - Reduce top spacer to flex:0.4 so game sits near-center (slightly bottom-biased)
      - Add api/ads.php endpoint to serve active campaigns from ad_campaigns table
      - Ad system tracks impressions and filters by slot/game targeting
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      ebf076a5
    • Mahmoud Aglan's avatar
      fix: add top spacer to push game content to bottom on tall screens · 622d1454
      Mahmoud Aglan authored
      Uses a flex:1 spacer div at the top of the layout instead of
      manipulating board container flex. On tall screens, dead space
      goes above the opponent bar — everything else stays packed at
      the bottom within thumb reach.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      622d1454
    • Mahmoud Aglan's avatar
      fix: revert broken bottom-anchor, keep board full-screen with emote off board · 3cc2d257
      Mahmoud Aglan authored
      The justify-content:flex-end approach left dead space at top.
      Instead: keep flex:1 on board container (fills screen), use
      align-items:flex-end so the canvas sits at the bottom of its
      container. Emote button stays at fixed position off the board.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      3cc2d257
    • Mahmoud Aglan's avatar
      fix: anchor game layouts to bottom — dead space now at top, not below controls · 6a49b380
      Mahmoud Aglan authored
      Portrait mobile games should have everything within thumb reach at the
      bottom. Removed flex:1 stretching and added justify-content:flex-end
      so the board, bars, and controls all stack from the bottom up.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      6a49b380
    • Mahmoud Aglan's avatar
      fix: mobile ergonomics — push boards down, move emotes off board, enlarge touch targets · 6143b157
      Mahmoud Aglan authored
      Board content now aligns to bottom (thumb zone) instead of center.
      Emote button repositioned to fixed bottom-right, off the chess squares.
      Control buttons enlarged to 48px+ with safe-area padding.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      6143b157
    • Mahmoud Aglan's avatar
      fix: UI polish — touch targets, board sizing, login logo, bot cards · 51e59856
      Mahmoud Aglan authored
      - Login: show brand logo via assetImg() instead of plain text, larger register link
      - Bot select: 44px back button, difficulty color bars, fallback initials when portrait fails
      - Chess board: raise max-width 400→500px, reduce padding — fills more screen
      - Chess controls: min-height 44px, better contrast (#e2e8f0), larger border-radius
      - Ludo board: raise cap 360→420px to fill available space
      - Ludo exit button: 44px circular touch target instead of rectangular
      - Tab bar items: min 44px touch targets with centered content
      - HUD bell button: 36→44px
      - Game menu: staggered entrance animation on buttons/chips
      - Fix 9px font in ludo panels → 10px minimum
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      51e59856
    • Mahmoud Aglan's avatar
      fix: multiplayer sync — Ludo turn passing + Chess check/SFX for opponent moves · 93eeb5c8
      Mahmoud Aglan authored
      Ludo multiplayer:
      - Sync state to server on EVERY turn change (including no-valid-moves pass)
      - Use turn_count to detect stale vs fresh server state (prevents re-processing)
      - Non-host players poll at 1.5s and receive dice rolls + board state
      - Bot turns run only on host, results synced to server for other players
      - Fix double-encoding of game_state/positions in API
      - Play notification sound when turn returns to player
      
      Chess multiplayer:
      - Detect move type (check/capture/castle) from FEN diff on received moves
      - Show check highlight (red king square) for opponent's checking moves
      - Play correct SFX (check/capture/castle) instead of generic 'move'
      - Show last-move highlight squares for received moves
      - Skip polling during recovery to prevent SFX burst on reconnect
      - Track captured pieces from opponent's moves
      - Sync move list from server history
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      93eeb5c8
    • Mahmoud Aglan's avatar
      fix: live chess — resign syncs, reconnect preserves color, clocks sync · f49fc5cb
      Mahmoud Aglan authored
      Three critical multiplayer sync issues fixed:
      
      1. Resign now notifies server (action:'resign') BEFORE ending locally.
         Opponent's polling detects status:'completed' + result and shows win.
      
      2. Reconnect recovery completely rewritten:
         - Determines player color from match data (white_player_id vs userId)
         - Flips board correctly for black
         - Sets lastKnownMoveCount from server to prevent duplicate move processing
         - Detects if game already ended while disconnected (opponent resigned)
         - Restores canSelect with correct color check
      
      3. sendLiveMove now includes clock times (white_time_remaining_ms,
         black_time_remaining_ms) so opponent sees accurate clocks.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      f49fc5cb
    • Mahmoud Aglan's avatar
      fix: Ludo panels match board corners (RTL fix) + exit button added · 6d23a1f2
      Mahmoud Aglan authored
      Panel positions:
      - Forced direction:ltr on panel rows so Green=top-left, Yellow=top-right,
        Red=bottom-left, Blue=bottom-right — matching the board zones visually.
        (The page is RTL Arabic which was reversing element order.)
      
      Exit button:
      - Added exit button (✕) next to dice area
      - Single player: confirms then ends game as loss
      - Multiplayer: notifies server to replace player with bot, then exits
      - Confirmation dialog prevents accidental exits
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      6d23a1f2
    • Mahmoud Aglan's avatar
      fix: chess reconnection loads server FEN + opponent profile fetched from match data · 4704435b
      Mahmoud Aglan authored
      Problem 1: Reconnection reset board to starting position
      - engine.create() always starts fresh
      - FIX: if params.recovered, fetch match from server → engine.load(current_fen)
      - Also restores clock times + determines whose turn from FEN
      
      Problem 2: Opponent name stuck on 'جاري التحميل'
      - params.opponentId was undefined (not passed on reconnect or matchmaking)
      - FIX: if no opponentId in params, fetch match → get white/black_player_id → find opponent
      - Uses fetchAndRenderOpponent() helper to update avatar/name/level
      
      Problem 3: Other player can't see moves after reconnect
      - This was because board reset to start position
      - Now board loads current_fen from server → correct position displayed
      - Polling continues from correct move_count
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      4704435b
    • Mahmoud Aglan's avatar
      fix: emotes stay next to sender, animate slower (3s), no center fly · 5893f13a
      Mahmoud Aglan authored
      Emotes now pop up next to the player panel that sent them and gently
      float up in place before fading. Duration increased from 1.8s to 3s.
      Multiplayer sync unchanged — still uses sendEmote/onEmoteReceived.
      Bot emotes in ludo also use the new positioned animation.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      5893f13a
    • Mahmoud Aglan's avatar
      feat: player-centric panels — photos, names, levels in ALL games · 23f9266a
      Mahmoud Aglan authored
      Ludo:
      - Each panel shows: avatar (photo/bot🤖), name, level
      - YOUR panel shows your profile photo + display_name + level
      - Bot panels show 🤖 emoji with colored border matching their zone
      - Panel positions already match zone colors (Green TL, Yellow TR, Red BL, Blue BR)
      
      Chess:
      - Opponent bar: photo (bot portrait for bots, profile pic for humans) + name + level
      - YOUR bar: your profile photo + display_name + Lv.X
      - Gold border around your avatar, blue border around opponent's
      - Live mode: fetches opponent profile → updates avatar, name, level dynamically
      - Bot mode: shows Stockfish portrait + bot name + 'بوت' label
      
      Both games now feel player-centric — you always see WHO you're playing against
      with their real identity, not generic 'Opponent' or 'Bot 1' text.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      23f9266a
    • Mahmoud Aglan's avatar
      fix: wire match-live into Chess and Ludo — enables reconnect on refresh · 317f8d20
      Mahmoud Aglan authored
      Chess game scene:
      - Imports and calls matchLive.start(matchId, 'chess', callbacks)
      - Creates localStorage entry → browser refresh auto-resumes
      - matchLive.session.destroy() on endGame → clears recovery
      
      Ludo game scene:
      - Same pattern: matchLive.start(matchId, 'ludo', callbacks)
      - Clears session on endGame
      
      Now the flow works:
      1. Player enters live game → matchLive.start() saves to localStorage
      2. Player refreshes browser → engine boot finds recovery → verifies server → auto-rejoins
      3. Game ends normally → matchLive.session.destroy() clears localStorage
      4. Player goes to homepage after game → no recovery popup (correct)
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      317f8d20
    • Mahmoud Aglan's avatar
      fix: append apikey to avatar public URL for self-hosted Supabase · 95be7796
      Mahmoud Aglan authored
      Self-hosted Supabase Kong requires apikey param even for public bucket
      URLs. Without it, browsers get 401 when loading the image.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      95be7796
    • Mahmoud Aglan's avatar
      fix: avatar upload — add missing apikey header for Supabase Storage · bd8edd8d
      Mahmoud Aglan authored
      Supabase Storage requires both Authorization and apikey headers. The
      missing apikey caused "No API key found in request" → 500 on upload.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      bd8edd8d
    • Mahmoud Aglan's avatar
      fix: Ludo board completely rewritten to match standard layout · fda0afc5
      Mahmoud Aglan authored
      Standard Ludo board: Green(TL), Yellow(TR), Red(BL), Blue(BR)
      Player order: 0=Red(BL), 1=Green(TL), 2=Yellow(TR), 3=Blue(BR)
      
      Path is now CLOCKWISE:
      - Red enters from bottom center going UP
      - Green enters from left center going RIGHT
      - Yellow enters from top center going DOWN
      - Blue enters from right center going LEFT
      
      All positions recalculated from scratch:
      - SHARED_PATH: 52 squares going clockwise matching reference image
      - HOME_COLUMNS: each player's 6-square run toward center
      - HOME_BASES: pieces start in correct corners
      - START_SQUARES: [0, 13, 26, 39] with correct global positions
      - Center triangles: point toward center from each player's side
      - Home zones: correct corner colors
      - Panel positions: match board layout spatially
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      fda0afc5
    • Mahmoud Aglan's avatar
      fix: avatar upload 500 — add error handlers, use mime_content_type · c2a2be92
      Mahmoud Aglan authored
      Replace finfo_open with simpler mime_content_type(). Add global
      exception/error handlers that return JSON instead of HTML error pages.
      Include curl errors and storage HTTP codes in error responses for
      debugging. Remove fileinfo from Dockerfile (already bundled in PHP 8.3).
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      c2a2be92
    • Mahmoud Aglan's avatar
      fix: avatar upload 500 — increase PHP upload limits + better errors · 13f4c712
      Mahmoud Aglan authored
      Add upload_max_filesize=10M to PHP config (default 2M was rejecting
      compressed images). Add fileinfo extension explicitly. Improve error
      messages in avatar.php to report the actual failure reason.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      13f4c712
    • Mahmoud Aglan's avatar
      fix: compress avatar images client-side before upload · 73495fd9
      Mahmoud Aglan authored
      Resize any image to 512x512 center-cropped JPEG at 82% quality before
      uploading. Handles huge photos from phone cameras that would fail or
      timeout on upload. Removes the 5MB size gate since compression brings
      all images to ~50-150KB.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      73495fd9
    • Mahmoud Aglan's avatar
      fix: Ludo board — correct clockwise player order and home positions · 3d0e89de
      Mahmoud Aglan authored
      The player order was wrong — Blue was assigned to Green's position and
      vice versa. On a standard Ludo board going clockwise from Red (top-left):
        Red(TL) → Green(BL) → Yellow(BR) → Blue(TR)
      
      Fixed:
      - COLORS order: red, green, yellow, blue (was red, blue, yellow, green)
      - HOME_BASES: matched to correct corners
      - HOME_COLUMNS: Green enters from left, Blue from right
      - HOME_ENTRY: correct global squares for each player
      - Center triangles: Red(top), Green(left), Yellow(bottom), Blue(right)
      - Home zone drawing: correct corner positions
      - Player panels: top row = Red(TL) + Blue(TR), bottom = Green(BL) + Yellow(BR)
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      3d0e89de
    • Mahmoud Aglan's avatar
      ffa60ac2
    • Mahmoud Aglan's avatar
      fix: auto-reconnect on homepage + auto-close match after 30s inactivity · c6478774
      Mahmoud Aglan authored
      Player reconnection:
      - On app boot, checks localStorage for active match
      - Verifies match is still 'in_progress' on server before rejoining
      - If match ended/aborted → clears recovery, goes to homepage
      - If server unreachable → tries to rejoin anyway (optimistic)
      
      Match auto-close:
      - handleGet() checks updated_at timestamp on every poll
      - If match hasn't been updated in 30+ seconds → both players inactive
      - Server marks match as 'completed' with result 'aborted'
      - Next player who polls sees status='completed' → game ends cleanly
      - Prevents zombie matches lingering forever
      
      Flow:
      1. Both players disconnect → no pings → updated_at goes stale
      2. After 30s, if either player comes back and polls → server closes match
      3. Match shows as 'aborted' → player sees 'game ended' UI
      4. If only one player comes back within 30s → they keep playing (match alive)
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      c6478774
    • Mahmoud Aglan's avatar
      feat: redesign top bar with profile avatar + add photo upload · d6be81de
      Mahmoud Aglan authored
      Replace cluttered logo/level/coins/gems/bell HUD with clean layout:
      avatar+level badge (left), coins+gems (center), bell (right). Avatar
      taps navigate to profile. Add profile photo upload with camera badge
      overlay, client+server validation, Supabase Storage upload endpoint.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      d6be81de
    • Mahmoud Aglan's avatar
    • Mahmoud Aglan's avatar
      feat: match-session.js — comprehensive multiplayer edge case handler · 412ee8f6
      Mahmoud Aglan authored
      New core module handles ALL connection edge cases:
      
      TAB REFRESH RECOVERY:
      - Active match stored in localStorage with timestamp
      - On app boot, checks for recoverable match (< 5 min old)
      - Automatically re-enters the game scene with recovered state
      - Fetches latest state from server immediately
      
      TAB VISIBILITY (switch/minimize):
      - Pauses polling when tab is hidden (saves bandwidth)
      - Resumes and immediately fetches latest state on return
      - Resets disconnect timers on tab return
      
      OPPONENT DISCONNECT DETECTION:
      - Tracks lastOpponentActivity timestamp
      - After 30s: fires onOpponentDisconnect (show warning UI)
      - After 60s: fires onOpponentAbandon (auto-claim win)
      - If opponent comes back: fires onOpponentReconnect
      
      NETWORK LOSS HANDLING:
      - If server unreachable for 10s: fires onConnectionLost
      - On recovery: fires onConnectionRestored
      - Polling continues with error tolerance
      
      SERVER PING (keep-alive):
      - Every 10s, pings server with player ID + timestamp
      - Stored in game_state.ping field
      - Opponent's polling reads this to know sender is alive
      
      API:
      - create(matchId, gameType, callbacks) — start session
      - destroy() — clean up timers + localStorage
      - getRecoverableMatch() — check for resume on boot
      - markOpponentActive() — call when opponent data received
      - isConnected() / isOpponentConnected() — status checks
      
      Engine updated:
      - Imports match-session on boot
      - Checks for recoverable match after auth
      - Auto-resumes game if found
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      412ee8f6