diff --git a/client/app.js b/client/app.js index 8a98dae..1e3aa0f 100644 --- a/client/app.js +++ b/client/app.js @@ -81,6 +81,7 @@ class GolfGame { this.initCardTooltips(); this.bindEvents(); this.initMobileDetection(); + this.initDesktopScorecard(); this.checkUrlParams(); } @@ -111,9 +112,11 @@ class GolfGame { this.isMobile = e.matches; document.body.classList.toggle('mobile-portrait', e.matches); setAppHeight(); - // Close any open drawers on layout change + // Close any open drawers/overlays on layout change if (!e.matches) { this.closeDrawers(); + } else { + this.closeDesktopScorecard(); } }; mql.addEventListener('change', update); @@ -154,6 +157,31 @@ class GolfGame { if (bottomBar) bottomBar.classList.remove('hidden'); } + initDesktopScorecard() { + if (!this.desktopScorecardBtn) return; + + this.desktopScorecardBtn.addEventListener('click', () => { + const isOpen = this.desktopScorecardOverlay.classList.contains('open'); + if (isOpen) { + this.closeDesktopScorecard(); + } else { + this.desktopScorecardOverlay.classList.add('open'); + this.desktopScorecardBtn.classList.add('active'); + this.desktopScorecardBackdrop.classList.add('visible'); + } + }); + + this.desktopScorecardBackdrop.addEventListener('click', () => { + this.closeDesktopScorecard(); + }); + } + + closeDesktopScorecard() { + if (this.desktopScorecardOverlay) this.desktopScorecardOverlay.classList.remove('open'); + if (this.desktopScorecardBtn) this.desktopScorecardBtn.classList.remove('active'); + if (this.desktopScorecardBackdrop) this.desktopScorecardBackdrop.classList.remove('visible'); + } + initAudio() { // Initialize audio context on first user interaction const initCtx = () => { @@ -544,6 +572,13 @@ class GolfGame { this.gameUsername = document.getElementById('game-username'); this.gameLogoutBtn = document.getElementById('game-logout-btn'); this.authBar = document.getElementById('auth-bar'); + + // Desktop scorecard overlay elements + this.desktopScorecardBtn = document.getElementById('desktop-scorecard-btn'); + this.desktopScorecardOverlay = document.getElementById('desktop-scorecard-overlay'); + this.desktopScorecardBackdrop = document.getElementById('desktop-scorecard-backdrop'); + this.desktopStandingsList = document.getElementById('desktop-standings-list'); + this.desktopScoreTable = document.getElementById('desktop-score-table')?.querySelector('tbody'); } bindEvents() { @@ -1464,12 +1499,8 @@ class GolfGame { this.swapAnimationCardEl = handCardEl; this.swapAnimationHandCardEl = handCardEl; - // Hide originals and UI during animation - handCardEl.classList.add('swap-out'); + // Hide discard button during animation (held card hidden later by onStart) this.discardBtn.classList.add('hidden'); - if (this.heldCardFloating) { - this.heldCardFloating.style.visibility = 'hidden'; - } // Store drawn card data before clearing const drawnCardData = this.drawnCard; @@ -1492,6 +1523,12 @@ class GolfGame { { rotation: 0, wasHandFaceDown: false, + onStart: () => { + handCardEl.classList.add('swap-out'); + if (this.heldCardFloating) { + this.heldCardFloating.style.visibility = 'hidden'; + } + }, onComplete: () => { handCardEl.classList.remove('swap-out'); if (this.heldCardFloating) { @@ -1555,6 +1592,12 @@ class GolfGame { { rotation: 0, wasHandFaceDown: true, + onStart: () => { + if (handCardEl) handCardEl.classList.add('swap-out'); + if (this.heldCardFloating) { + this.heldCardFloating.style.visibility = 'hidden'; + } + }, onComplete: () => { if (handCardEl) handCardEl.classList.remove('swap-out'); if (this.heldCardFloating) { @@ -2887,9 +2930,6 @@ class GolfGame { return; } - // Hide the source card during animation - sourceCardEl.classList.add('swap-out'); - // Use unified swap animation if (window.cardAnimations) { const heldRect = window.cardAnimations.getHoldingRect(); @@ -2902,6 +2942,9 @@ class GolfGame { { rotation: sourceRotation, wasHandFaceDown: !wasFaceUp, + onStart: () => { + sourceCardEl.classList.add('swap-out'); + }, onComplete: () => { if (sourceCardEl) sourceCardEl.classList.remove('swap-out'); this.opponentSwapAnimation = null; @@ -4347,6 +4390,11 @@ class GolfGame { `; this.scoreTable.appendChild(tr); }); + + // Mirror to desktop overlay + if (this.desktopScoreTable) { + this.desktopScoreTable.innerHTML = this.scoreTable.innerHTML; + } } updateStandings() { @@ -4384,7 +4432,7 @@ class GolfGame { return `
${medal}${name}${p.rounds_won} wins
`; }).join(''); - this.standingsList.innerHTML = ` + const standingsContent = `
By Score
${pointsHtml} @@ -4394,6 +4442,10 @@ class GolfGame { ${holesHtml}
`; + this.standingsList.innerHTML = standingsContent; + if (this.desktopStandingsList) { + this.desktopStandingsList.innerHTML = standingsContent; + } } renderCard(card, clickable, selected) { @@ -4473,6 +4525,11 @@ class GolfGame { this.scoreTable.appendChild(tr); }); + // Mirror to desktop overlay + if (this.desktopScoreTable) { + this.desktopScoreTable.innerHTML = this.scoreTable.innerHTML; + } + // Show rankings announcement only for final results const existingAnnouncement = document.getElementById('rankings-announcement'); if (existingAnnouncement) existingAnnouncement.remove(); diff --git a/client/card-animations.js b/client/card-animations.js index 28c2576..2e4fc65 100644 --- a/client/card-animations.js +++ b/client/card-animations.js @@ -1105,7 +1105,7 @@ class CardAnimations { // heldRect: position of the held card (or null to use default holding position) // options: { rotation, wasHandFaceDown, onComplete } animateUnifiedSwap(handCardData, heldCardData, handRect, heldRect, options = {}) { - const { rotation = 0, wasHandFaceDown = false, onComplete } = options; + const { rotation = 0, wasHandFaceDown = false, onComplete, onStart } = options; const T = window.TIMING?.swap || { lift: 100, arc: 320, settle: 100 }; const discardRect = this.getDiscardRect(); @@ -1137,15 +1137,15 @@ class CardAnimations { delete el.dataset.animating; el.remove(); }); - this._runUnifiedSwap(handCardData, heldCardData, handRect, heldRect, discardRect, T, rotation, wasHandFaceDown, onComplete); + this._runUnifiedSwap(handCardData, heldCardData, handRect, heldRect, discardRect, T, rotation, wasHandFaceDown, onComplete, onStart); }, 350); return; } - this._runUnifiedSwap(handCardData, heldCardData, handRect, heldRect, discardRect, T, rotation, wasHandFaceDown, onComplete); + this._runUnifiedSwap(handCardData, heldCardData, handRect, heldRect, discardRect, T, rotation, wasHandFaceDown, onComplete, onStart); } - _runUnifiedSwap(handCardData, heldCardData, handRect, heldRect, discardRect, T, rotation, wasHandFaceDown, onComplete) { + _runUnifiedSwap(handCardData, heldCardData, handRect, heldRect, discardRect, T, rotation, wasHandFaceDown, onComplete, onStart) { // Create the two traveling cards const travelingHand = this.createCardFromData(handCardData, handRect, rotation); const travelingHeld = this.createCardFromData(heldCardData, heldRect, 0); @@ -1154,6 +1154,9 @@ class CardAnimations { document.body.appendChild(travelingHand); document.body.appendChild(travelingHeld); + // Now that overlays cover the originals, hide them + if (onStart) onStart(); + this.playSound('card'); // If hand card was face-down, flip it first diff --git a/server/handlers.py b/server/handlers.py index 0bb0bf8..6bd427a 100644 --- a/server/handlers.py +++ b/server/handlers.py @@ -313,22 +313,6 @@ async def handle_swap(data: dict, ctx: ConnectionContext, *, broadcast_game_stat reason=f"swapped {drawn_card.rank.value} into position {position}, replaced {old_rank}", ) - # Broadcast reveal of old face-down card before state update - if old_card_data: - reveal_msg = { - "type": "card_revealed", - "player_id": ctx.player_id, - "position": position, - "card": old_card_data, - } - for pid, p in ctx.current_room.players.items(): - if not p.is_cpu and p.websocket: - try: - await p.websocket.send_json(reveal_msg) - except Exception: - pass - await asyncio.sleep(1.0) - await broadcast_game_state(ctx.current_room) await asyncio.sleep(1.0) check_and_run_cpu_turn(ctx.current_room)