1. 05 Jun, 2026 40 commits
    • 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
    • Mahmoud Aglan's avatar
    • Mahmoud Aglan's avatar
      fix: chess piece images maintain aspect ratio — no more stretching · a1a13d94
      Mahmoud Aglan authored
      Uses contain-fit logic: scales image to fit within the square while
      preserving natural proportions, centered within the cell.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      a1a13d94
    • Mahmoud Aglan's avatar
      feat: game tile colors now read from branding system · 08e5c496
      Mahmoud Aglan authored
      Game tiles (chess, domino, ludo, backgammon) now get their gradient colors
      from the theme — editing chess_primary/chess_secondary etc in admin branding
      immediately changes the homepage tile colors. Also uploaded proper game icons.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      08e5c496
    • Mahmoud Aglan's avatar
      feat: bottom tab bar icons now editable via admin branding · 67498bb4
      Mahmoud Aglan authored
      Tab bar icons (play, rank, social, shop, profile) now use assetImg()
      which loads from platform_assets. Uploaded matching PNGs from app icons.
      Falls back to original SVGs if no custom icons uploaded.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      67498bb4
    • Mahmoud Aglan's avatar
      feat: chess board reads colors from branding + logos wired throughout app · 6f4568f4
      Mahmoud Aglan authored
      Chess board:
      - Square colors now read from theme system (getColor) instead of hardcoded
        constants — editing board colors in admin branding instantly affects all
        modes (game, analysis, puzzles)
      
      Logos:
      - HUD brand text replaced with assetImg('logo') — shows uploaded PNG
      - Splash screen uses logo image instead of text
      - Favicon and apple-touch-icon added to index.php
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      6f4568f4
    • Mahmoud Aglan's avatar
      feat: chess pieces smaller + faster move animation with squish effect · d21403f1
      Mahmoud Aglan authored
      - Pieces now have 10% padding (was 5%) making them visually smaller
      - Move animation 120ms (was 200ms) with cubic ease-out
      - Slight squish (85% scale) at midpoint of move
      - Bigger squish (72% scale) when capturing a piece
      - Dragging piece slightly smaller (1.1x instead of 1.2x)
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      d21403f1
    • Mahmoud Aglan's avatar
      fix: admin branding page now shows uploaded assets — load from platform_assets table · 3432eb36
      Mahmoud Aglan authored
      The admin was only reading from local theme.json and platform_theme,
      but uploaded emoji/asset URLs are stored in platform_assets. Now loads
      them on page render so the preview thumbnails actually appear.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      3432eb36
    • Mahmoud Aglan's avatar
      feat: chess pieces now load custom PNGs from branding system · c7c76636
      Mahmoud Aglan authored
      When chess piece images are uploaded via admin (chess_piece_wK, chess_piece_bQ,
      etc.), the board renders them instead of vector paths. Falls back to the
      original canvas paths if no images are uploaded. Re-renders automatically
      once images finish loading.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      c7c76636
    • Mahmoud Aglan's avatar
      fix: remove group_name from platform_assets insert — column doesn't exist · ca122ef4
      Mahmoud Aglan authored
      The platform_assets table has no group_name column (unlike platform_theme).
      This was causing all emoji uploads from admin to silently fail with HTTP 400.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      ca122ef4
    • Mahmoud Aglan's avatar
      feat: wire ALL remaining emoji slots — dice faces, puzzle themes, particles, room icons · ef6b1d98
      Mahmoud Aglan authored
      Every admin emoji slot now has a corresponding emoji() or getAsset() call:
      - Dice faces (1-6): custom dice images replace CSS dots when uploaded
      - Puzzle themes: fork, pin, castle, crown, sacrifice use emoji()
      - Particle star: juice engine uses themed particle
      - Bot thinking dots, book/best-move symbols in classifier
      - Domino/ludo room icons, chess pawn in leaderboard tabs
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      ef6b1d98
    • Mahmoud Aglan's avatar
      fix: wire bell emoji slot to HUD notification icon · 9be847bd
      Mahmoud Aglan authored
      The admin had a 'bell' emoji slot but the HUD used a hardcoded SVG.
      Now uses emoji('bell', '🔔', 18) so uploaded PNGs actually appear.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      9be847bd
    • Mahmoud Aglan's avatar
      fix: emoji uploads not reflecting — add group_name, kill API cache, add debug logging · ee317285
      Mahmoud Aglan authored
      Three issues fixed:
      1. platform_assets insert was missing group_name (likely NOT NULL column, same
         bug as platform_theme had in 18d0e512)
      2. API had Cache-Control: max-age=60 so browser served stale theme data
      3. theme.load() silently swallowed errors — added console logging and
         cache-bust param to diagnose failures
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      ee317285
    • Mahmoud Aglan's avatar
      feat: wire emoji() theming across entire player app — all registered emojis now replaceable · a032b00e
      Mahmoud Aglan authored
      Every emoji that has a slot in admin branding now goes through the emoji()
      function from core/theme.js. Uploading a PNG/SVG replacement in admin will
      now show up everywhere that emoji appears in the player app.
      
      Files updated: hud, multiplayer, player-panel, chess (game/analysis/review/
      result/history), ludo (game/result), domino (result), play table, daily
      rewards, challenges, ranks, shop, leaderboard, tournaments, friends,
      activity, org (home/browser), profile (view/settings), puzzles.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      a032b00e
    • Mahmoud Aglan's avatar
      fix: wire up emoji() theming so uploaded PNGs actually replace hardcoded emojis · 1733e6bd
      Mahmoud Aglan authored
      The emoji() function existed in theme.js but no component imported or called it.
      All emojis were hardcoded strings. Now result screens, daily rewards, challenges,
      shop, and history use emoji() which checks for admin-uploaded replacements.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      1733e6bd
    • Mahmoud Aglan's avatar
      feat: challenges + battlepass now read from DB tables (admin-configurable) · f96c69db
      Mahmoud Aglan authored
      challenges.php:
      - Reads challenge_templates from DB instead of hardcoded array
      - Admin can add/remove/edit challenge types via management panel
      - Fallback to 3 basic challenges if DB is empty
      
      battlepass.php:
      - Reads active season from 'seasons' table instead of hardcoded config
      - Admin can create new seasons, set dates, tiers, XP requirements
      - Fallback to Season 1 defaults if no active season
      
      Both APIs now fully driven by database:
      - Management panel writes to tables → Player app reads automatically
      - No more hardcoded game config in PHP files
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      f96c69db
    • Mahmoud Aglan's avatar
      fix: PNG uploads now render correctly — remove pixelation from raster images · 211a94f5
      Mahmoud Aglan authored
      image-rendering:-webkit-optimize-contrast caused nearest-neighbor scaling on
      PNGs making them blocky. Now only applied to SVGs. Also added cache-busting
      timestamp so re-uploaded assets aren't served stale from browser cache.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      211a94f5
    • Mahmoud Aglan's avatar
      feat: FULL SYNC Phase 1+2 — ban enforcement, atomic economy, admin-controlled rewards · ae3a9a78
      Mahmoud Aglan authored
      DATABASE CREATED (via SSH to Supabase):
      - reward_config table: 8 configurable reward values (chess_win/loss/draw, daily, streak, etc.)
      - seasons table: battle pass season config (name, dates, tiers, xp)
      - challenge_templates table: 7 challenge types with rewards
      - award_coins() function: atomic coin grant (prevents race conditions)
      
      PLAYER APP CHANGES:
      1. Ban enforcement: requireAuth() now checks is_banned + ban_expires_at
         - Returns 403 'Account banned' if player is banned
         - Respects expiration dates (temporary bans expire)
      
      2. Economy from config: game.php reads chess_win_coins/draw/loss from reward_config
         - Admin can change reward values in DB → player app picks them up
         - No more hardcoded 50/20/10
      
      3. Atomic coin award: calls award_coins() DB function
         - Single transaction: UPDATE profiles + INSERT economy_transactions
         - No race condition on concurrent coin grants
      
      MANAGEMENT CAN NOW:
      - Ban a player → immediately blocked from all player app actions
      - Change reward_config values → player app uses new amounts
      - Create seasons → player app reads active season from DB
      - Create challenge templates → player app picks random 3 per day
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      ae3a9a78
    • Mahmoud Aglan's avatar
      fix: image upload — add apikey header + public URL with anon key · af58cbf5
      Mahmoud Aglan authored
      Upload was failing because:
      1. Missing 'apikey' header on PUT request to Supabase Storage
      2. Public URL needs ?apikey= query param for this Supabase instance
      
      Fixed:
      - Added 'apikey: SERVICE_KEY' header to upload curl request
      - Public URL now includes anon key: .../branding/slot.ext?apikey=ANON_KEY
      - Browser can load the image directly from this URL
      - Survives deploys (stored in Supabase Storage, URL in platform_assets)
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      af58cbf5
    • Mahmoud Aglan's avatar
      fix: branding save uses API directly (not form POST) — reliable Supabase persistence · badef7ea
      Mahmoud Aglan authored
      Problem: Form POST to same page required PHP session + page re-render.
      Save appeared to work but values weren't persisting to Supabase reliably.
      
      Fix: Save button now POSTs to /api/branding.php directly as JSON.
      - Collects all input values from DOM
      - Sends { action: 'save_theme', values: { key: value, ... } }
      - API writes each value to platform_theme table (upsert)
      - No session dependency, no form submission, no page reload
      - Response confirms success/failure with message
      
      On page refresh: PHP reads from platform_theme → shows saved values Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      badef7ea
    • Mahmoud Aglan's avatar
      fix: branding page — manual save button, images stored in Supabase Storage · 67c6136f
      Mahmoud Aglan authored
      1. REMOVED auto-save entirely
         - No more saving on every keystroke
         - Changes tracked as 'dirty' state
      
      2. NEW: Sticky bottom save bar
         - Shows 'No unsaved changes' (grey) / '️ Unsaved changes' (yellow)
         - '💾 Save All' button only active when changes exist
         - Success: '✓ Saved!' feedback
         - beforeunload warning if leaving with unsaved changes
      
      3. Image uploads now go to Supabase Storage
         - Uploaded to: profile-images/branding/{slot}.{ext}
         - Public URL stored in platform_assets table
         - Survives deploys (not local filesystem)
         - Also saved locally for immediate preview in admin
         - Asset URL returned by /api/branding.php GET endpoint
      
      4. Player app reads asset URLs from Supabase
         - theme.js fetches /api/branding.php → includes assets from platform_assets
         - Uploaded SVGs/PNGs render at exact specified size
         - object-fit:contain + image-rendering for no pixelation
      
      Flow:
      - Admin uploads logo.svg → Supabase Storage → URL in platform_assets
      - Admin changes gold color → marks dirty → clicks Save → Supabase platform_theme
      - Player loads app → /api/branding.php → gets colors + asset URLs → applies
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      67c6136f
    • Mahmoud Aglan's avatar
      fix: chess pieces now ACTUALLY animate — call board.animateMove() before setPosition · 1401d21f
      Mahmoud Aglan authored
      The animateMove method existed on the board but was never called.
      Game scene was still doing board.setPosition() instantly.
      
      Fixed:
      - Player moves: animateMove(from, to) → piece slides 200ms → then setPosition
      - Bot moves: same — piece slides smoothly before board updates
      - Initial position: stays instant (correct — no animation needed on load)
      
      The animation method uses requestAnimationFrame to interpolate piece
      position linearly from source square to destination over 200ms.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      1401d21f