From 7c4087a34cbac46905c676831c55d4d1f5a3088c Mon Sep 17 00:00:00 2001 From: ale Date: Tue, 11 Nov 2025 23:39:16 +0100 Subject: [PATCH] accessibility Signed-off-by: ale --- app/page.tsx | 32 +++++++++++++++++++++++--------- components/GameBoard.tsx | 4 +++- components/GameOver.tsx | 9 +++++++-- components/Lobby.tsx | 23 ++++++++++++++++++----- components/PlayerHand.tsx | 14 +++++++++++++- components/WaitingRoom.tsx | 10 ++++++---- 6 files changed, 70 insertions(+), 22 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index 4908edd..d033a6d 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -131,7 +131,7 @@ export default function Home() { return (
{/* Header */} -
+

@@ -148,12 +148,15 @@ export default function Home() { @@ -169,6 +172,9 @@ export default function Home() { animate={{ y: 0, opacity: 1 }} exit={{ y: -100, opacity: 0 }} className="fixed top-20 left-1/2 transform -translate-x-1/2 bg-red-500 text-white px-6 py-3 rounded-lg shadow-lg z-50" + role="alert" + aria-live="assertive" + aria-atomic="true" > {error} @@ -184,6 +190,9 @@ export default function Home() { exit={{ opacity: 0 }} className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50" onClick={() => setShowRules(false)} + role="dialog" + aria-modal="true" + aria-labelledby="rules-title" > e.stopPropagation()} > -

How to Play

+

How to Play

  • • Click on a tile to select it
  • • Click "Place Left" or "Place Right" to place it
  • @@ -204,6 +213,7 @@ export default function Home() { @@ -213,12 +223,12 @@ export default function Home() { {/* Main game area */} -
    +
    {/* Left side - Game board and controls */}
    {/* Game info */} -
    +
    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 */} -
    +
    - ))} -
    + )} +
    -
    +
    {/* 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 ( -
    +
    {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 ? '🏆' : '🤝'} -

    +

    {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
    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 ( -
    +
    Online Multiplayer Game

    + 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

    -
    +
    ); } if (mode === 'join') { return ( -
    +
    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
-
+ ); } 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 ( -
+
{ + 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
-
+
); }