diff --git a/src/apps/golf/components/GolfGame.module.css b/src/apps/golf/components/GolfGame.module.css index 48c94d4..c69b52a 100644 --- a/src/apps/golf/components/GolfGame.module.css +++ b/src/apps/golf/components/GolfGame.module.css @@ -4,14 +4,14 @@ align-items: center; justify-content: center; min-height: 400px; - padding: 2rem; + padding: 1.5rem; } .title { font-family: 'Lexend', sans-serif; - font-size: 3rem; + font-size: 2.5rem; font-weight: 300; - margin-bottom: 2rem; + margin-bottom: 1.5rem; color: white; } @@ -24,10 +24,6 @@ max-width: 400px; } -.inputGroup { - margin-bottom: 1.5rem; -} - .input { width: 100%; padding: 0.75rem; @@ -63,6 +59,7 @@ border-radius: 0.5rem; cursor: pointer; transition: all 0.3s ease; + min-height: 48px; } .primaryButton { @@ -198,6 +195,17 @@ padding: 3rem; } +.waitingPulse { + animation: pulse 2s ease-in-out infinite; + color: rgba(255, 255, 255, 0.7); +} + +.startGameButton { + padding: 1rem 2rem; + font-size: 1.2rem; + min-height: 56px; +} + .gameArea { position: relative; display: flex; @@ -214,25 +222,29 @@ .cardGrid { display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 0.5rem; + grid-template-columns: repeat(2, auto); + gap: 0.75rem; justify-content: center; align-items: center; } +.cardGridDimmed { + opacity: 0.6; + pointer-events: none; + transition: opacity 0.3s ease; +} + .card { - width: 80px; - height: 120px; - background: white; - border-radius: 0.5rem; + width: 90px; + height: 130px; + border-radius: 0.75rem; display: flex; align-items: center; justify-content: center; position: relative; cursor: pointer; - transition: all 0.3s ease; + transition: transform 0.2s ease, box-shadow 0.2s ease; font-size: 1.5rem; - color: #333; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); user-select: none; -webkit-user-select: none; @@ -250,14 +262,7 @@ .card.interactive:active { transform: scale(0.95); - transition: transform 0.1s ease; -} - -.card.selected { - outline: 3px solid #ffd700; - outline-offset: -3px; - transform: scale(1.05); - box-shadow: 0 0 20px rgba(255, 215, 0, 0.6); + transition: transform 0.1s cubic-bezier(0.34, 1.56, 0.64, 1); } .card.clickable, @@ -266,6 +271,12 @@ transition: all 0.2s ease; -webkit-tap-highlight-color: transparent; touch-action: manipulation; + animation: subtlePulse 2s ease-in-out infinite; +} + +@keyframes subtlePulse { + 0%, 100% { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); } + 50% { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3), 0 0 12px rgba(144, 238, 144, 0.3); } } .card.clickable:hover, @@ -282,29 +293,83 @@ } .card.revealed { - background: white; + background: #faf8f0; + color: #1a1a1a; } -.cardBack { - font-size: 2rem; - color: #0d5016; +.cardFace { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; } -.cardRank { - font-weight: bold; - font-size: 2rem; +.cardCorner { + position: absolute; + display: flex; + flex-direction: column; + align-items: center; + line-height: 1; +} + +.topLeft { + top: 6px; + left: 6px; +} + +.bottomRight { + bottom: 6px; + right: 6px; + transform: rotate(180deg); +} + +.cornerRank { + font-size: 1rem; + font-weight: 700; + font-family: 'Lexend', sans-serif; +} + +.cornerSuit { + font-size: 0.7rem; + line-height: 1; +} + +.cardCenter { + font-size: 2.2rem; } -.cardRank.red, -.cardSuit.red { - color: #e53935; +.red { + color: #dc3545; } -.cardSuit { +/* Revealed cards in player grid get a warm glow */ +.playerArea .card.revealed { + box-shadow: 0 2px 12px rgba(255, 200, 60, 0.25); +} + +.cardBack { position: absolute; - bottom: 0.5rem; - right: 0.5rem; - font-size: 1.5rem; + inset: 0; + border-radius: 0.75rem; + background: + repeating-linear-gradient( + 45deg, + transparent, + transparent 8px, + rgba(144, 238, 144, 0.12) 8px, + rgba(144, 238, 144, 0.12) 9px + ), + repeating-linear-gradient( + -45deg, + transparent, + transparent 8px, + rgba(144, 238, 144, 0.12) 8px, + rgba(144, 238, 144, 0.12) 9px + ), + linear-gradient(135deg, #1a5c2a, #0d3f18); + box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.3); + border: 2px solid rgba(144, 238, 144, 0.2); } .cardCount { @@ -339,8 +404,8 @@ } .emptyPile { - width: 80px; - height: 120px; + width: 90px; + height: 130px; border: 2px dashed rgba(255, 255, 255, 0.3); border-radius: 0.5rem; display: flex; @@ -349,10 +414,52 @@ color: rgba(255, 255, 255, 0.5); } -.drawnCardHighlight .card { - outline: 3px solid #ffd700; - outline-offset: -3px; - box-shadow: 0 0 20px rgba(255, 215, 0, 0.6); +.heldCard { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; + animation: bobbing 2s ease-in-out infinite; +} + +.heldCard .card { + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4), 0 0 20px rgba(255, 215, 0, 0.3); +} + +.heldCardLabel { + font-size: 0.8rem; + color: rgba(255, 255, 255, 0.7); + font-family: 'Lexend', sans-serif; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +@keyframes bobbing { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-6px); } +} + +.pileDepleted { + opacity: 0.4; +} + +.turnIndicator { + text-align: center; + padding: 0.5rem 1rem; + font-family: 'Lexend', sans-serif; + font-size: 1rem; + color: rgba(255, 255, 255, 0.85); + background: rgba(255, 255, 255, 0.06); + border-radius: 0.5rem; + min-height: 2.5rem; + display: flex; + align-items: center; + justify-content: center; +} + +.waitingText { + color: rgba(255, 255, 255, 0.5); } .playerArea { @@ -415,20 +522,6 @@ transition: transform 0.1s ease; } -.actionButtonDisabled { - opacity: 0.4; - cursor: not-allowed; -} - -.actionButtonDisabled:hover { - background: rgba(255, 255, 255, 0.2); - border-color: rgba(255, 255, 255, 0.3); -} - -.actionButtonDisabled:active { - transform: none; -} - .knockButton { background: #ff6b6b; color: white; @@ -444,12 +537,6 @@ transition: transform 0.1s ease; } -.peekHint { - margin-top: 1rem; - color: #90ee90; - font-size: 0.9rem; -} - .notification { position: fixed; bottom: 2rem; @@ -624,8 +711,8 @@ } .card { - width: 70px; - height: 100px; + width: 78px; + height: 112px; font-size: 1.1rem; -webkit-tap-highlight-color: transparent; touch-action: manipulation; @@ -640,38 +727,48 @@ transition: transform 0.1s ease; } - .card.selected { - outline: 3px solid #ffd700; - outline-offset: -3px; - transform: scale(1.08); - box-shadow: 0 0 24px rgba(255, 215, 0, 0.6); + .cornerRank { font-size: 0.85rem; } + .cornerSuit { font-size: 0.6rem; } + .cardCenter { font-size: 1.8rem; } + .topLeft { top: 4px; left: 4px; } + .bottomRight { bottom: 4px; right: 4px; } + + .cardGrid { + gap: 0.75rem; + grid-template-columns: repeat(2, auto); + max-width: 180px; + margin: 0 auto; + padding: 0; } - .cardRank { - font-size: 1.5rem; + .playerArea .cardGrid { + max-width: 180px; } - .cardSuit { - font-size: 1.2rem; - bottom: 0.4rem; - right: 0.4rem; + .emptyPile { + width: 78px; + height: 112px; } - .cardBack { - font-size: 1.8rem; + .turnIndicator { + font-size: 0.9rem; + padding: 0.4rem 0.75rem; } - .cardGrid { - gap: 0.8rem; - grid-template-columns: repeat(2, 1fr); - max-width: 170px; - margin: 0 auto; - padding: 0; + .heldCard .card { + box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4), 0 0 14px rgba(255, 215, 0, 0.3); } - .emptyPile { - width: 70px; - height: 100px; + .heldCardLabel { + font-size: 0.75rem; + } + + .celebrationEmoji { + font-size: 4rem; + } + + .celebrationTitle { + font-size: 2rem; } .playerArea { @@ -737,24 +834,11 @@ font-size: 1.2rem; } - .winner { - font-size: 1rem; - } - .scoreRow { font-size: 0.85rem; padding: 0.4rem; } - .gameEndActions { - flex-direction: column; - gap: 0.5rem; - } - - .gameEndActions button { - width: 100%; - } - .rulesContent { padding: 1.5rem; max-width: 95%; @@ -785,11 +869,6 @@ margin: 1rem 0; } - .cumulativeTitle { - font-size: 1rem; - margin-bottom: 0.75rem; - } - .cumulativeRow { grid-template-columns: 1.5fr 0.8fr 0.8fr 0.8fr 0.9fr; gap: 0.3rem; @@ -846,21 +925,15 @@ } .card { - width: 55px; - height: 80px; - } - - .cardRank { - font-size: 1.2rem; + width: 64px; + height: 92px; } - .cardSuit { - font-size: 1rem; - } - - .cardBack { - font-size: 1.4rem; - } + .cornerRank { font-size: 0.75rem; } + .cornerSuit { font-size: 0.5rem; } + .cardCenter { font-size: 1.4rem; } + .topLeft { top: 3px; left: 3px; } + .bottomRight { bottom: 3px; right: 3px; } .piles { gap: 0.5rem; @@ -871,13 +944,13 @@ } .emptyPile { - width: 55px; - height: 80px; + width: 64px; + height: 92px; } .cardGrid { - max-width: 130px; - gap: 0.6rem; + max-width: 145px; + gap: 0.75rem; } .primaryButton, @@ -894,7 +967,26 @@ } .playerArea .cardGrid { - max-width: 130px; + max-width: 145px; + } + + .turnIndicator { + font-size: 0.85rem; + padding: 0.35rem 0.5rem; + min-height: 2rem; + } + + .celebrationEmoji { + font-size: 3.5rem; + } + + .celebrationTitle { + font-size: 1.75rem; + } + + .playAgainButton { + font-size: 1.1rem; + padding: 0.85rem; } .actions { @@ -914,10 +1006,6 @@ margin: 0.75rem 0; } - .cumulativeTitle { - font-size: 0.95rem; - } - .cumulativeRow { grid-template-columns: 1.2fr 0.7fr 0.7fr 0.8fr 0.9fr; gap: 0.25rem; @@ -973,70 +1061,30 @@ } } -.celebration { +.celebrationStage { text-align: center; - margin-bottom: 1rem; + cursor: pointer; } -.trophy { - font-size: 3rem; - display: block; - animation: bounce 2s ease-in-out infinite; - margin-bottom: 0.5rem; +.celebrationEmoji { + font-size: 5rem; + animation: bounce 1.5s ease-in-out infinite; + margin-bottom: 1rem; } -.gameOverTitle { +.celebrationTitle { + font-size: 2.5rem; color: white; font-family: 'Lexend', sans-serif; - font-size: 1.8rem; + font-weight: 300; margin: 0; - text-transform: uppercase; - letter-spacing: 0.05em; -} - -.winnerSection { - margin: 1.5rem 0; - padding: 1rem; - background: linear-gradient(135deg, rgba(255, 215, 0, 0.1), rgba(255, 165, 0, 0.1)); - border-radius: 0.5rem; - border: 1px solid rgba(255, 215, 0, 0.3); } -.confetti { - text-align: center; - font-size: 1.5rem; - margin-bottom: 0.5rem; - animation: pulse 1s ease-in-out infinite; -} - -.winner { - text-align: center; - display: flex; - flex-direction: column; - gap: 0.25rem; -} - -.winnerLabel { - color: #90ee90; +.celebrationTap { + color: rgba(255, 255, 255, 0.5); font-size: 0.9rem; - text-transform: uppercase; - letter-spacing: 0.1em; - font-family: 'Lexend', sans-serif; -} - -.winnerName { - color: #ffd700; - font-size: 1.8rem; - font-weight: bold; - font-family: 'Lexend', sans-serif; - text-shadow: 0 2px 4px rgba(255, 215, 0, 0.3); -} - -.winnerMessage { - color: white; - font-size: 0.95rem; - font-family: 'Lexend', sans-serif; - opacity: 0.9; + margin-top: 1.5rem; + animation: pulse 2s ease-in-out infinite; } @keyframes bounce { @@ -1106,27 +1154,70 @@ font-size: 0.9rem; } -.gameEndInfo { - text-align: center; - color: rgba(255, 255, 255, 0.6); - font-size: 0.75rem; - margin: 0; +.playAgainButton { + width: 100%; + padding: 1rem; + font-size: 1.2rem; font-family: 'Lexend', sans-serif; - padding-top: 0.5rem; - border-top: 1px solid rgba(255, 255, 255, 0.1); + font-weight: 500; + background: #90ee90; + color: #0d5016; + border: none; + border-radius: 0.75rem; + cursor: pointer; + margin-bottom: 1rem; + transition: all 0.2s ease; + -webkit-tap-highlight-color: transparent; + touch-action: manipulation; } -.gameEndActions { - display: flex; - gap: 0.75rem; - margin-top: 1rem; - justify-content: center; +.playAgainButton:active { + transform: scale(0.97); } -.gameEndActions button { - flex: 1; - padding: 0.5rem 1rem; +.roomTotalsDetails { + margin: 0.75rem 0; +} + +.roomTotalsSummary { + color: rgba(255, 255, 255, 0.6); font-size: 0.9rem; + font-family: 'Lexend', sans-serif; + cursor: pointer; + padding: 0.5rem; + text-align: center; + list-style: none; +} + +.roomTotalsSummary::-webkit-details-marker { + display: none; +} + +.roomTotalsSummary:hover { + color: white; +} + +.textLink { + background: none; + border: none; + color: rgba(255, 255, 255, 0.4); + font-family: 'Lexend', sans-serif; + font-size: 0.85rem; + cursor: pointer; + padding: 0.5rem; + text-decoration: underline; + text-underline-offset: 2px; +} + +.textLink:hover { + color: rgba(255, 255, 255, 0.7); +} + +.gameEndLinks { + display: flex; + justify-content: center; + gap: 1.5rem; + margin-top: 0.75rem; } /* Rules Modal Styles */ @@ -1469,14 +1560,6 @@ margin: 1.5rem 0; } -.cumulativeTitle { - color: #90ee90; - font-family: 'Lexend', sans-serif; - font-size: 1.3rem; - margin: 0 0 1rem 0; - text-align: center; -} - .cumulativeRow { display: grid; grid-template-columns: 2fr 1fr 1fr 1fr 1fr; @@ -1509,41 +1592,11 @@ text-align: center; } -.gameEndActions { - display: flex; - gap: 1rem; - justify-content: center; - flex-wrap: wrap; -} - -.tertiaryButton { - padding: 0.75rem 1.5rem; - font-size: 1rem; - font-family: 'Lexend', sans-serif; - background: transparent; - color: rgba(255, 255, 255, 0.6); - border: 1px solid rgba(255, 255, 255, 0.2); - border-radius: 0.5rem; - cursor: pointer; - transition: all 0.3s ease; -} - -.tertiaryButton:hover { - color: white; - border-color: rgba(255, 255, 255, 0.4); -} - /* Permalink Display Sections */ .permalinkSection { margin-bottom: 1.5rem; } -.gamePermalinkSection { - margin-top: 1rem; - padding-top: 1rem; - border-top: 1px solid rgba(255, 255, 255, 0.1); -} - /* New Game Notifications */ .newGameNotificationsSection { background: rgba(144, 238, 144, 0.05); @@ -1651,11 +1704,6 @@ margin-bottom: 1rem; } - .gamePermalinkSection { - margin-top: 0.75rem; - padding-top: 0.75rem; - } - .newGameNotificationsSection { grid-column: span 1; padding: 1rem; diff --git a/src/apps/golf/components/GolfGame.tsx b/src/apps/golf/components/GolfGame.tsx index 926347b..c32e946 100644 --- a/src/apps/golf/components/GolfGame.tsx +++ b/src/apps/golf/components/GolfGame.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useState, useEffect } from 'react' import styles from './GolfGame.module.css' import { useGolfGame } from '@/hooks/useGolfGame' import PermalinkDisplay from './PermalinkDisplay' @@ -26,6 +26,7 @@ interface GolfGameProps { const GolfGame = ({ onGameIdChange, onPlayerIdChange, onPlayerNameChange, onConnectionChange, permalinkParams }: GolfGameProps) => { const [showRules, setShowRules] = useState(false) + const [showScores, setShowScores] = useState(false) // Pass permalink parameters to useGolfGame hook for automatic joining @@ -40,7 +41,6 @@ const GolfGame = ({ onGameIdChange, onPlayerIdChange, onPlayerNameChange, onConn roomState, playerId, roomCode, - selectedCardIndex, isInLobby, isInRoom, notification, @@ -59,7 +59,6 @@ const GolfGame = ({ onGameIdChange, onPlayerIdChange, onPlayerNameChange, onConn startNewGame, drawCard, takeFromDiscard, - swapCard, discardDrawn, knock, handleCardClick, @@ -69,27 +68,24 @@ const GolfGame = ({ onGameIdChange, onPlayerIdChange, onPlayerNameChange, onConn joinNewGame } = useGolfGame({ onGameIdChange, onPlayerIdChange, onPlayerNameChange, onConnectionChange, permalinkParams }) - const [drawnFromRaw, setDrawnFromRaw] = useState<'deck' | 'discard' | null>(null) - const drawnFrom = gameState?.drawnCard ? drawnFromRaw : null - - const handleDrawCard = () => { - setDrawnFromRaw('deck') - drawCard() - } - - const handleTakeFromDiscard = () => { - setDrawnFromRaw('discard') - takeFromDiscard() - } + useEffect(() => { + if (gameState?.gamePhase === 'ended') { + const hideTimer = setTimeout(() => setShowScores(false), 0) + const showTimer = setTimeout(() => setShowScores(true), 3000) + return () => { + clearTimeout(hideTimer) + clearTimeout(showTimer) + } + } + }, [gameState?.gamePhase]) const renderCard = (card: Card | null, index: number, isRevealed: boolean, isPlayer: boolean) => { - const isSelected = selectedCardIndex === index const canInteract = isPlayer && (isMyTurn || (currentPlayer && !currentPlayer.hasPeeked && gameState?.gamePhase === 'playing')) return (
canInteract && handleCardClick(index)} onTouchEnd={(e) => { e.preventDefault() @@ -97,16 +93,21 @@ const GolfGame = ({ onGameIdChange, onPlayerIdChange, onPlayerNameChange, onConn }} > {isRevealed && card ? ( - <> - - {card.rank} - - +
+
+ {card.rank} + {card.suit} +
+ {card.suit} - +
+ {card.rank} + {card.suit} +
+
) : ( - ? +
)}
) @@ -132,6 +133,7 @@ const GolfGame = ({ onGameIdChange, onPlayerIdChange, onPlayerNameChange, onConn onChange={(e) => setRoomCode(e.target.value.toUpperCase())} className={styles.input} maxLength={6} + autoFocus /> )}
)} - {gameState.gamePhase === 'ended' && ( + {gameState.gamePhase === 'ended' && !showScores && ( +
setShowScores(true)}> +
+
+ {currentPlayer && getDisplayName(currentPlayer) === winner ? '🏆' : '😤'} +
+

+ {currentPlayer && getDisplayName(currentPlayer) === winner + ? 'You won!' + : `${winner} wins!`} +

+

Tap to see scores

+
+
+ )} + + {gameState.gamePhase === 'ended' && showScores && (
-
- {currentPlayer && getDisplayName(currentPlayer) === winner ? ( - 🏆 - ) : ( - 😢 - )} -

Game Over!

-
- {winner && ( -
- {currentPlayer && getDisplayName(currentPlayer) === winner ? ( -
🎉 🎊 🎉
- ) : ( -
- )} -
- {currentPlayer && getDisplayName(currentPlayer) === winner ? ( - <> - Congratulations! - You've won with the lowest score! - - ) : ( - <> - Game Over - {winner} wins! - Better luck next time! - - )} -
-
- )} + +

Final Scores

{gameState.players @@ -452,29 +443,29 @@ const GolfGame = ({ onGameIdChange, onPlayerIdChange, onPlayerNameChange, onConn
))}
-
- Lower scores win • Pairs cancel out -
+ {roomState && ( -
-

Room Totals

- {roomState.players - .sort((a, b) => (a.gamesPlayed > 0 ? a.totalScore / a.gamesPlayed : 0) - (b.gamesPlayed > 0 ? b.totalScore / b.gamesPlayed : 0)) - .map((player, index) => ( -
0 ? styles.bestAverage : ''}`}> - {getDisplayName(player)} - {player.gamesPlayed} games - {player.gamesWon} wins - {player.totalScore} total - - {player.gamesPlayed > 0 ? (player.totalScore / player.gamesPlayed).toFixed(1) : 'N/A'} avg - -
- ))} -
+
+ Room Totals +
+ {roomState.players + .sort((a, b) => (a.gamesPlayed > 0 ? a.totalScore / a.gamesPlayed : 0) - (b.gamesPlayed > 0 ? b.totalScore / b.gamesPlayed : 0)) + .map((player, index) => ( +
0 ? styles.bestAverage : ''}`}> + {getDisplayName(player)} + {player.gamesPlayed} games + {player.gamesWon} wins + {player.totalScore} total + + {player.gamesPlayed > 0 ? (player.totalScore / player.gamesPlayed).toFixed(1) : 'N/A'} avg + +
+ ))} +
+
)} - {/* New Game Join Options */} + {/* New Game Join Options - keep if they exist */} {roomState && newGameNotifications.filter(n => !n.dismissed).length > 0 && (

🎮 Join New Games

@@ -482,7 +473,7 @@ const GolfGame = ({ onGameIdChange, onPlayerIdChange, onPlayerNameChange, onConn {newGameNotifications .filter(n => !n.dismissed) .sort((a, b) => b.timestamp - a.timestamp) - .slice(0, 3) // Show max 3 most recent + .slice(0, 3) .map(notification => (
@@ -503,24 +494,12 @@ const GolfGame = ({ onGameIdChange, onPlayerIdChange, onPlayerNameChange, onConn
)} -
- - -
@@ -529,42 +508,45 @@ const GolfGame = ({ onGameIdChange, onPlayerIdChange, onPlayerNameChange, onConn {gameState.gamePhase !== 'waiting' && currentPlayer && (
+ {/* Turn indicator */} +
+ {!isMyTurn ? ( + Waiting for {gameState.players[gameState.currentPlayerIndex]?.id}... + ) : !currentPlayer.hasPeeked && gameState.gamePhase === 'playing' ? ( + Tap {2 - currentPlayer.revealedCards.length} cards to peek + ) : !gameState.drawnCard ? ( + Your turn — tap a pile to draw + ) : ( + Tap a card to swap, or discard + )} +
+
-

Draw Pile

- {gameState.drawnCard && isMyTurn && drawnFrom === 'deck' ? ( -
- {renderCard(gameState.drawnCard, -2, true, false)} -
- ) : ( -
isMyTurn && !gameState.drawnCard && handleDrawCard()} - onTouchEnd={(e) => { - e.preventDefault() - if (isMyTurn && !gameState.drawnCard) handleDrawCard() - }} - > - ? - {gameState.drawPile} -
- )} +

Deck

+
isMyTurn && !gameState.drawnCard && drawCard()} + onTouchEnd={(e) => { + e.preventDefault() + if (isMyTurn && !gameState.drawnCard) drawCard() + }} + > +
+ {gameState.drawPile} +
-

Discard Pile

- {gameState.drawnCard && isMyTurn && drawnFrom === 'discard' ? ( -
- {renderCard(gameState.drawnCard, -2, true, false)} -
- ) : gameState.discardPile.length > 0 ? ( +

Discard

+ {gameState.discardPile.length > 0 ? (
0 ? styles.clickable : ''}`} - onClick={() => isMyTurn && !gameState.drawnCard && gameState.discardPile.length > 0 && handleTakeFromDiscard()} + className={`${isMyTurn && !gameState.drawnCard && gameState.discardPile.length > 0 ? styles.clickable : ''} ${gameState.drawnCard ? styles.pileDepleted : ''}`} + onClick={() => isMyTurn && !gameState.drawnCard && gameState.discardPile.length > 0 && takeFromDiscard()} onTouchEnd={(e) => { e.preventDefault() - if (isMyTurn && !gameState.drawnCard && gameState.discardPile.length > 0) handleTakeFromDiscard() + if (isMyTurn && !gameState.drawnCard && gameState.discardPile.length > 0) takeFromDiscard() }} > {renderCard(gameState.discardPile[gameState.discardPile.length - 1], -1, true, false)} @@ -576,40 +558,30 @@ const GolfGame = ({ onGameIdChange, onPlayerIdChange, onPlayerNameChange, onConn
+ {/* Floating held card */} + {gameState.drawnCard && isMyTurn && ( +
+ Held card + {renderCard(gameState.drawnCard, -2, true, false)} +
+ )} +

Your Cards

-
+
{currentPlayer.cards.map((card, index) => renderCard(card, index, currentPlayer.revealedCards.includes(index), true) )}
- {!gameState.drawnCard ? ( - <> - - {gameState.discardPile.length > 0 && ( - - )} - - ) : ( + {gameState.drawnCard ? ( <> - - )} + ) : null} {gameState.gamePhase === 'playing' && !gameState.drawnCard && ( )}
- - {currentPlayer.revealedCards.length < 2 && gameState.gamePhase === 'playing' && !currentPlayer.hasPeeked && ( -
- Click on {2 - currentPlayer.revealedCards.length} cards to peek at them -
- )}
)} diff --git a/src/hooks/useGolfGame.ts b/src/hooks/useGolfGame.ts index 6d1ac38..e97d865 100644 --- a/src/hooks/useGolfGame.ts +++ b/src/hooks/useGolfGame.ts @@ -66,7 +66,7 @@ interface UseGolfGameReturn { peekCard: (index: number) => void drawCard: () => void takeFromDiscard: () => void - swapCard: () => void + swapCard: (index?: number) => void discardDrawn: () => void knock: () => void handleCardClick: (index: number) => void @@ -230,8 +230,9 @@ export const useGolfGame = ({ networkAdapterRef.current.takeFromDiscard() }, [showNotification]) - const swapCard = useCallback(() => { - if (selectedCardIndex === null) { + const swapCard = useCallback((index?: number) => { + const swapIndex = index ?? selectedCardIndex + if (swapIndex === null) { showNotification('Select a card to swap first') return } @@ -239,7 +240,7 @@ export const useGolfGame = ({ showNotification('Not connected to server') return } - networkAdapterRef.current.swapCard(selectedCardIndex) + networkAdapterRef.current.swapCard(swapIndex) setSelectedCardIndex(null) }, [selectedCardIndex, showNotification]) @@ -271,9 +272,9 @@ export const useGolfGame = ({ if (currentPlayer.revealedCards.length < 2 && !currentPlayer.hasPeeked && !currentPlayer.revealedCards.includes(index)) { peekCard(index) } else if (isPlayersTurn && gameState?.drawnCard) { - setSelectedCardIndex(index) + swapCard(index) } - }, [gameState, playerId, peekCard, peekCountdown]) + }, [gameState, playerId, peekCard, peekCountdown, swapCard]) const clearGameState = useCallback(() => { setGameState(null)