{/* Left side - Game board and controls */}
{/* Game info */}
-
+
)}
-
+ )}
+
-
+
{/* Game over modal */}
{gameState.isGameOver && (
diff --git a/components/GameBoard.tsx b/components/GameBoard.tsx
index 0de9f07..408846d 100644
--- a/components/GameBoard.tsx
+++ b/components/GameBoard.tsx
@@ -163,7 +163,7 @@ export function GameBoard({ placedTiles, width = 1200, height = 700, className =
};
return (
- Current Turn
@@ -255,11 +265,12 @@ export default function Home() {
)}
+
- ))}
-
@@ -267,6 +278,7 @@ export default function Home() {
onClick={() => handlePlaceTile('right')}
disabled={!selectedTile}
className="flex-1 bg-gradient-to-r from-purple-500 to-purple-600 text-white py-3 rounded-lg font-semibold disabled:opacity-50 disabled:cursor-not-allowed hover:shadow-lg transition-shadow"
+ aria-label={selectedTile ? `Place tile ${selectedTile.left}-${selectedTile.right} on the right side` : 'Place tile on right side (select a tile first)'}
>
Place Right
@@ -274,6 +286,7 @@ export default function Home() {
@@ -282,6 +295,7 @@ export default function Home() {
@@ -303,7 +317,7 @@ export default function Home() {
{/* Right side - Other players */}
-
+
+
{placedTiles.length > 0 && (
diff --git a/components/GameOver.tsx b/components/GameOver.tsx
index 9cf29e7..64bf675 100644
--- a/components/GameOver.tsx
+++ b/components/GameOver.tsx
@@ -17,6 +17,9 @@ export function GameOver({ winner, players, onPlayAgain, onLeave }: GameOverProp
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50"
+ role="dialog"
+ aria-modal="true"
+ aria-labelledby="game-over-title"
>
{winner ? '🏆' : '🤝'}
-
+
diff --git a/components/Lobby.tsx b/components/Lobby.tsx
index 6b04a15..43835c3 100644
--- a/components/Lobby.tsx
+++ b/components/Lobby.tsx
@@ -32,7 +32,7 @@ export function Lobby({ onCreateRoom, onJoinRoom, onStartAI, roomId }: LobbyProp
if (mode === 'menu') {
return (
-
+
{winner ? 'Game Over!' : 'Game Blocked!'}
{winner && (
@@ -91,6 +94,7 @@ export function GameOver({ winner, players, onPlayAgain, onLeave }: GameOverProp
whileTap={{ scale: 0.98 }}
onClick={onPlayAgain}
className="w-full bg-gradient-to-r from-green-500 to-green-600 text-white py-3 rounded-lg font-semibold shadow-lg hover:shadow-xl transition-shadow"
+ aria-label="Start a new game"
>
Play Again
@@ -100,8 +104,9 @@ export function GameOver({ winner, players, onPlayAgain, onLeave }: GameOverProp
whileTap={{ scale: 0.98 }}
onClick={onLeave}
className="w-full bg-gray-300 text-gray-700 py-3 rounded-lg font-semibold hover:bg-gray-400 transition-colors"
+ aria-label="Leave game and return to main menu"
>
- Back to Menu
+ Leave Game
+
Online Multiplayer Game
);
}
if (mode === 'join') {
return (
-
+
);
}
diff --git a/components/PlayerHand.tsx b/components/PlayerHand.tsx
index ebf0bee..1f07c2e 100644
--- a/components/PlayerHand.tsx
+++ b/components/PlayerHand.tsx
@@ -20,7 +20,7 @@ export function PlayerHand({
validTileIds,
}: PlayerHandProps) {
return (
-
+
setPlayerName(e.target.value)}
className="w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:outline-none transition-colors"
maxLength={20}
+ aria-label="Enter your player name"
+ aria-required="true"
/>
-
+
Create Room
@@ -75,6 +80,7 @@ export function Lobby({ onCreateRoom, onJoinRoom, onStartAI, roomId }: LobbyProp
onClick={() => setMode('join')}
disabled={!playerName.trim()}
className="w-full bg-gradient-to-r from-purple-500 to-purple-600 text-white py-3 rounded-lg font-semibold shadow-lg hover:shadow-xl transition-shadow disabled:opacity-50 disabled:cursor-not-allowed"
+ aria-label="Join an existing multiplayer room"
>
Join Room
@@ -85,6 +91,7 @@ export function Lobby({ onCreateRoom, onJoinRoom, onStartAI, roomId }: LobbyProp
onClick={handleAI}
disabled={!playerName.trim()}
className="w-full bg-gradient-to-r from-green-500 to-green-600 text-white py-3 rounded-lg font-semibold shadow-lg hover:shadow-xl transition-shadow disabled:opacity-50 disabled:cursor-not-allowed"
+ aria-label="Start a game against computer AI"
>
Play vs AI
@@ -94,13 +101,13 @@ export function Lobby({ onCreateRoom, onJoinRoom, onStartAI, roomId }: LobbyProp
-
+ Built with Next.js, Canvas & Socket.IO
+
Join Room
-
+
setJoinRoomId(e.target.value.toUpperCase())}
className="w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:border-purple-500 focus:outline-none transition-colors uppercase"
maxLength={6}
+ aria-label="Enter the 6-character room ID"
+ aria-required="true"
/>
@@ -126,6 +137,7 @@ export function Lobby({ onCreateRoom, onJoinRoom, onStartAI, roomId }: LobbyProp
onClick={handleJoin}
disabled={!joinRoomId.trim()}
className="w-full bg-gradient-to-r from-purple-500 to-purple-600 text-white py-3 rounded-lg font-semibold shadow-lg hover:shadow-xl transition-shadow disabled:opacity-50 disabled:cursor-not-allowed"
+ aria-label="Join the room with entered ID"
>
Join Game
@@ -135,12 +147,13 @@ export function Lobby({ onCreateRoom, onJoinRoom, onStartAI, roomId }: LobbyProp
whileTap={{ scale: 0.98 }}
onClick={() => setMode('menu')}
className="w-full bg-gray-300 text-gray-700 py-3 rounded-lg font-semibold hover:bg-gray-400 transition-colors"
+ aria-label="Go back to main menu"
>
Back
+
{
+ if ((e.key === 'Enter' || e.key === ' ') && isPlayable && isCurrentPlayer) {
+ e.preventDefault();
+ onTileClick(tile.id);
+ }
+ }}
>
{/* Background */}
= 2 && players.every(p => p.isReady);
return (
-
+
Ready to Play
) : (
-
);
}
+
-
{canStart ? 'Starting game...' : players.length < 2 ? 'Waiting for at least 1 more player...' : 'Waiting for other players...'}
)}
@@ -116,12 +117,13 @@ export function WaitingRoom({ roomId, players, currentPlayerId, onReady, onLeave
whileTap={{ scale: 0.98 }}
onClick={onLeave}
className="w-full bg-gray-300 text-gray-700 py-3 rounded-lg font-semibold hover:bg-gray-400 transition-colors"
+ aria-label="Leave the waiting room and return to menu"
>
Leave Room
+
-
+ Game Rules
- • 2-4 players can play (minimum 2 required) @@ -132,6 +134,6 @@ export function WaitingRoom({ roomId, players, currentPlayerId, onReady, onLeave