make this client more modern 
fix grid and loading spinner still load after other player join
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Connect Four Online</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- Google Fonts for better typography -->
    <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet"> 
    <style>
        /* CSS Variables for easy theming */
        :root {
            --background-color: #1e272e;
            --primary-color: #00a8ff;
            --secondary-color: #485460;
            --accent-color: #e74c3c;
            --neutral-light: #d2dae2;
            --neutral-dark: #2f3640;
            --text-color: #d2dae2;
            --success-color: #2ecc71;
            --error-color: #e74c3c;
        }

        /* Global Styles */
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }

        body {
            font-family: 'Roboto', sans-serif;
            background-color: var(--background-color);
            color: var(--text-color);
            display: flex;
            flex-direction: column;
            align-items: center;
            padding: 20px;
            min-height: 100vh;
        }

        h1, h2 {
            color: var(--primary-color);
            margin-bottom: 20px;
        }

        /* Container Styles */
        .container {
            background-color: var(--secondary-color);
            padding: 30px;
            border-radius: 12px;
            width: 100%;
            max-width: 500px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
            transition: transform 0.3s;
        }

        .container:hover {
            transform: translateY(-5px);
        }

        /* Input Styles */
        input[type="text"], input[type="password"] {
            width: 100%;
            padding: 12px 15px;
            margin: 10px 0;
            border: none;
            border-radius: 8px;
            font-size: 16px;
        }

        input[type="text"]:focus, input[type="password"]:focus {
            outline: none;
            border: 2px solid var(--primary-color);
        }

        /* Button Styles */
        button {
            padding: 12px 25px;
            margin: 10px 5px 0 0;
            border: none;
            border-radius: 8px;
            background-color: var(--primary-color);
            color: white;
            font-size: 16px;
            cursor: pointer;
            transition: background-color 0.3s, transform 0.2s;
        }

        button:hover {
            background-color: darken(var(--primary-color), 10%);
        }

        button:active {
            transform: scale(0.98);
        }

        /* Game Board Styles */
        #game {
            display: grid;
            grid-template-columns: repeat(7, 60px);
            grid-template-rows: repeat(6, 60px);
            gap: 5px;
            background-color: var(--neutral-dark);
            padding: 15px;
            border-radius: 12px;
            box-shadow: inset 0 0 10px rgba(0,0,0,0.5);
            margin-top: 20px;
            position: relative;
        }

        .cell {
            width: 60px;
            height: 60px;
            background-color: #dcdde1;
            border-radius: 50%;
            position: relative;
            cursor: pointer;
            transition: transform 0.2s, background-color 0.3s;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .cell:hover {
            transform: scale(1.1);
        }

        .cell::after {
            content: '';
            width: 50px;
            height: 50px;
            border-radius: 50%;
            background-color: transparent;
            transition: background-color 0.3s;
        }

        .cell.red::after {
            background-color: var(--accent-color);
        }

        .cell.blue::after {
            background-color: var(--primary-color);
        }

        /* Status and Points */
        #status, #points {
            margin-top: 20px;
            text-align: center;
            font-size: 18px;
        }

        /* Rooms List */
        #rooms-list {
            max-height: 250px;
            overflow-y: auto;
            margin-top: 15px;
        }

        .room-item {
            background-color: var(--secondary-color);
            padding: 12px;
            border-radius: 8px;
            margin-bottom: 10px;
            cursor: pointer;
            transition: background-color 0.3s, transform 0.2s;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .room-item:hover {
            background-color: var(--primary-color);
            transform: translateX(5px);
        }

        /* Hidden Class */
        .hidden {
            display: none;
        }

        /* Loading Spinner */
        .spinner {
            border: 4px solid rgba(255, 255, 255, 0.3);
            border-top: 4px solid var(--primary-color);
            border-radius: 50%;
            width: 30px;
            height: 30px;
            animation: spin 1s linear infinite;
            margin: 20px auto;
        }

        @keyframes spin {
            to { transform: rotate(360deg); }
        }

        /* Responsive Design */
        @media (max-width: 768px) {
            #game {
                grid-template-columns: repeat(7, 50px);
                grid-template-rows: repeat(6, 50px);
                gap: 4px;
                padding: 10px;
            }

            .cell {
                width: 50px;
                height: 50px;
            }
        }

        @media (max-width: 480px) {
            .container {
                padding: 20px;
            }

            #game {
                grid-template-columns: repeat(7, 40px);
                grid-template-rows: repeat(6, 40px);
                gap: 3px;
                padding: 8px;
            }

            .cell {
                width: 40px;
                height: 40px;
            }

            button {
                width: 100%;
                margin: 10px 0 0 0;
            }
        }
    </style>
</head>
<body>
    <h1>Connect Four Online</h1>

    <div id="room-selection" class="container" aria-labelledby="room-selection-header">
        <h2 id="room-selection-header">Join or Create a Room</h2>
        <input type="text" id="room-name" placeholder="Room Name" aria-label="Room Name">
        <input type="password" id="room-password" placeholder="Room Password (Optional)" aria-label="Room Password">
        <div style="display: flex; flex-wrap: wrap;">
            <button onclick="createRoom()">Create Room</button>
            <button onclick="searchRooms()">Search Rooms</button>
        </div>

        <div id="rooms-list" class="hidden" aria-live="polite"></div>
    </div>

    <div id="join-game" class="container hidden" aria-labelledby="join-game-header">
        <h2 id="join-game-header">Enter Your Details</h2>
        <input type="text" id="player-name" placeholder="Enter your name" aria-label="Player Name">
        <div style="display: flex; flex-wrap: wrap;">
            <button onclick="joinGame()">Join Game</button>
        </div>
    </div>

    <div id="game" class="hidden" aria-label="Game Board"></div>

    <div id="status" class="hidden" aria-live="polite"></div>
    <div id="points" class="hidden" aria-live="polite"></div>

    <div id="loading" class="spinner hidden" aria-hidden="true"></div>

    <script>
        const socketUrl = 'ws://localhost:6789'; // Update to 'wss://' if using SSL
        let socket;
        let playerColor = null;
        let currentRoom = null;

        const roomSelectionDiv = document.getElementById('room-selection');
        const joinGameDiv = document.getElementById('join-game');
        const roomsListDiv = document.getElementById('rooms-list');
        const gameDiv = document.getElementById('game');
        const statusDiv = document.getElementById('status');
        const pointsDiv = document.getElementById('points');
        const loadingSpinner = document.getElementById('loading');

        // Utility Functions
        const showElement = (element) => element.classList.remove('hidden');
        const hideElement = (element) => element.classList.add('hidden');
        const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);

        // WebSocket Initialization
        const initializeSocket = () => {
            hideElement(roomsListDiv);
            showElement(loadingSpinner);
            socket = new WebSocket(socketUrl);

            socket.onopen = () => {
                hideElement(loadingSpinner);
                console.log('Connected to the server.');
            };

            socket.onmessage = (event) => {
                const data = JSON.parse(event.data);
                handleServerMessage(data);
            };

            socket.onerror = (error) => {
                console.error('WebSocket error:', error);
            };

            socket.onclose = () => {
                console.log('Connection closed.');
                showStatus('Disconnected from the server.', 'error');
            };
        };

        // Handle Server Messages
        const handleServerMessage = (data) => {
            switch (data.type) {
                case 'error':
                    showStatus(data.message, 'error');
                    break;
                case 'room_created':
                    currentRoom = data.room;
                    hideElement(roomSelectionDiv);
                    showElement(joinGameDiv);
                    showStatus(`Room "${currentRoom}" created. Join with your name.`, 'success');
                    break;
                case 'room_list':
                    displayRooms(data.rooms);
                    break;
                case 'joined_room':
                    currentRoom = data.room;
                    hideElement(roomSelectionDiv);
                    hideElement(joinGameDiv);
                    showElement(gameDiv);
                    showElement(statusDiv);
                    showElement(pointsDiv);
                    showStatus(`Joined room "${currentRoom}". Waiting for players...`, 'success');
                    break;
                case 'update':
                    updateGame(data);
                    break;
                default:
                    console.warn('Unknown message type:', data.type);
            }
        };

        // Display Rooms
        const displayRooms = (rooms) => {
            roomsListDiv.innerHTML = '';
            if (rooms.length === 0) {
                roomsListDiv.innerHTML = '<p>No available rooms.</p>';
            } else {
                rooms.forEach(room => {
                    const roomItem = document.createElement('div');
                    roomItem.classList.add('room-item');
                    roomItem.setAttribute('tabindex', '0');
                    roomItem.setAttribute('role', 'button');
                    roomItem.setAttribute('aria-pressed', 'false');
                    roomItem.innerHTML = `<span>${room.name}</span><span>${room.players}/2</span>`;
                    roomItem.onclick = () => joinSelectedRoom(room.name);
                    roomItem.onkeypress = (e) => {
                        if (e.key === 'Enter') joinSelectedRoom(room.name);
                    };
                    roomsListDiv.appendChild(roomItem);
                });
            }
            showElement(roomsListDiv);
        };

        // Show Status Messages
        const showStatus = (message, type) => {
            statusDiv.innerText = message;
            statusDiv.style.color = type === 'error' ? 'var(--error-color)' : 'var(--success-color)';
            showElement(statusDiv);
            setTimeout(() => {
                statusDiv.innerText = '';
                hideElement(statusDiv);
            }, 5000);
        };

        // Create Room
        const createRoom = () => {
            const roomName = document.getElementById('room-name').value.trim();
            const roomPassword = document.getElementById('room-password').value;

            if (!roomName) {
                showStatus("Please enter a room name.", 'error');
                return;
            }

            initializeSocket();

            socket.onopen = () => {
                socket.send(JSON.stringify({ type: 'create_room', name: roomName, password: roomPassword }));
            };
        };

        // Search Rooms
        const searchRooms = () => {
            initializeSocket();

            socket.onopen = () => {
                socket.send(JSON.stringify({ type: 'list_rooms' }));
            };
        };

        // Join Selected Room
        const joinSelectedRoom = (roomName) => {
            const roomPassword = prompt("Enter room password (if any):") || '';
            const playerName = prompt("Enter your name:") || 'Player';

            if (!playerName) {
                showStatus("Name is required.", 'error');
                return;
            }

            initializeSocket();

            socket.onopen = () => {
                socket.send(JSON.stringify({ type: 'join_room', room: roomName, password: roomPassword, name: playerName }));
            };
        };

        // Join Game
        const joinGame = () => {
            const playerName = document.getElementById('player-name').value.trim();
            if (!playerName) {
                showStatus("Please enter your name.", 'error');
                return;
            }
            const roomPassword = document.getElementById('room-password').value;

            if (socket && socket.readyState === WebSocket.OPEN) {
                socket.send(JSON.stringify({ type: 'join', name: playerName }));
                hideElement(joinGameDiv);
                showElement(gameDiv);
                showElement(statusDiv);
                showElement(pointsDiv);
            } else {
                showStatus("Not connected to the server.", 'error');
            }
        };

        // Update Game Board
        const updateGame = (data) => {
            // Update board
            gameDiv.innerHTML = '';
            data.board.forEach((row, rowIndex) => {
                row.forEach((cell, colIndex) => {
                    const cellDiv = document.createElement('div');
                    cellDiv.classList.add('cell');
                    if (cell) {
                        cellDiv.classList.add(cell);
                    }
                    cellDiv.dataset.column = colIndex;
                    cellDiv.onclick = () => makeMove(colIndex);
                    gameDiv.appendChild(cellDiv);
                });
            });

            // Update current turn
            statusDiv.innerText = `Current Turn: ${capitalize(data.current_turn)}`;

            // Update player info
            pointsDiv.innerHTML = '';
            for (const [color, info] of Object.entries(data.players)) {
                if (!playerColor && socket.readyState === WebSocket.OPEN) {
                    // Assign player color based on first connected player
                    playerColor = color;
                }
                pointsDiv.innerHTML += `<p>${capitalize(color)} Player: ${info.name} | Points: ${info.points}</p>`;
            }
        };

        // Make a Move
        const makeMove = (column) => {
            if (!socket || socket.readyState !== WebSocket.OPEN) return;
            socket.send(JSON.stringify({ type: 'move', column }));
        };

        // Initialize Socket on Page Load
        window.onload = () => {
            // Optionally, you can auto-connect or leave it to user actions
        };
    </script>
</body>
</html>
server.py
import asyncio
import websockets
import json
from collections import defaultdict

# Game constants
GAME_ROWS = 6
GAME_COLS = 8

class Game:
    def __init__(self):
        self.reset_game()
        self.players = {}  # websocket: { 'name': str, 'color': 'red'/'blue', 'points': int }

    def reset_game(self):
        self.board = [[None for _ in range(GAME_COLS)] for _ in range(GAME_ROWS)]
        self.current_turn = 'red'  # red starts
        self.game_over = False

    def add_player(self, websocket, name):
        if len(self.players) >= 2:
            return False  # Game full
        color = 'red' if not any(p['color'] == 'red' for p in self.players.values()) else 'blue'
        self.players[websocket] = {'name': name, 'color': color, 'points': 0}
        return True

    def remove_player(self, websocket):
        if websocket in self.players:
            del self.players[websocket]

    def make_move(self, column, color):
        if self.game_over or color != self.current_turn:
            return False, "Not your turn or game over."

        # Place the piece in the column
        for row in reversed(range(GAME_ROWS)):
            if self.board[row][column] is None:
                self.board[row][column] = color
                if self.check_winner(row, column, color):
                    self.game_over = True
                    return True, "win"
                self.current_turn = 'blue' if self.current_turn == 'red' else 'red'
                return True, "success"
        return False, "Column full."

    def check_winner(self, row, col, color):
        # Check horizontal
        count = 0
        for c in range(GAME_COLS):
            if self.board[row][c] == color:
                count += 1
                if count == 4:
                    return True
            else:
                count = 0

        # Check vertical
        count = 0
        for r in range(GAME_ROWS):
            if self.board[r][col] == color:
                count += 1
                if count == 4:
                    return True
            else:
                count = 0

        # Check diagonal (/)
        count = 0
        start_row = row
        start_col = col
        while start_row > 0 and start_col < GAME_COLS - 1:
            start_row -= 1
            start_col += 1
        while start_row < GAME_ROWS and start_col >= 0:
            if self.board[start_row][start_col] == color:
                count += 1
                if count == 4:
                    return True
            else:
                count = 0
            start_row += 1
            start_col -= 1

        # Check diagonal (\)
        count = 0
        start_row = row
        start_col = col
        while start_row > 0 and start_col > 0:
            start_row -= 1
            start_col -= 1
        while start_row < GAME_ROWS and start_col < GAME_COLS:
            if self.board[start_row][start_col] == color:
                count += 1
                if count == 4:
                    return True
            else:
                count = 0
            start_row += 1
            start_col += 1

        return False

    def to_json(self):
        return json.dumps({
            'type': 'update',
            'board': self.board,
            'current_turn': self.current_turn,
            'players': {p['color']: {'name': p['name'], 'points': p['points']} for p in self.players.values()}
        })

class Room:
    def __init__(self, name, password=''):
        self.name = name
        self.password = password
        self.game = Game()
        self.players = {}  # websocket: player_info

    def add_player(self, websocket, name, password):
        if self.password and self.password != password:
            return False, "Incorrect password."
        if len(self.players) >= 2:
            return False, "Room is full."
        color = 'red' if not any(p['color'] == 'red' for p in self.players.values()) else 'blue'
        self.players[websocket] = {'name': name, 'color': color, 'points': 0}
        self.game.players = self.players
        return True, color

    def remove_player(self, websocket):
        if websocket in self.players:
            del self.players[websocket]
            self.game.remove_player(websocket)

    def to_json(self):
        return json.dumps({
            'type': 'update',
            'board': self.game.board,
            'current_turn': self.game.current_turn,
            'players': {p['color']: {'name': p['name'], 'points': p['points']} for p in self.players.values()}
        })

rooms = defaultdict(lambda: None)  # room_name: Room

async def notify_all_players(room):
    if room.players:
        message = room.to_json()
        await asyncio.gather(*[ws.send(message) for ws in room.players])

async def handler(websocket, path):
    current_room = None

    try:
        async for message in websocket:
            data = json.loads(message)

            if data['type'] == 'create_room':
                room_name = data['name']
                room_password = data.get('password', '')
                if room_name in rooms and rooms[room_name]:
                    await websocket.send(json.dumps({'type': 'error', 'message': 'Room already exists.'}))
                else:
                    room = Room(room_name, room_password)
                    rooms[room_name] = room
                    success = room.add_player(websocket, 'Host', room_password)
                    if success:
                        current_room = room
                        await websocket.send(json.dumps({'type': 'room_created', 'room': room_name}))
                    else:
                        await websocket.send(json.dumps({'type': 'error', 'message': 'Failed to create room.'}))

            elif data['type'] == 'list_rooms':
                available_rooms = []
                for room in rooms.values():
                    if room and len(room.players) < 2:
                        available_rooms.append({'name': room.name, 'players': len(room.players)})
                await websocket.send(json.dumps({'type': 'room_list', 'rooms': available_rooms}))

            elif data['type'] == 'join_room':
                room_name = data['room']
                room_password = data.get('password', '')
                player_name = data.get('name', 'Player')

                if room_name not in rooms or not rooms[room_name]:
                    await websocket.send(json.dumps({'type': 'error', 'message': 'Room does not exist.'}))
                else:
                    room = rooms[room_name]
                    success, info = room.add_player(websocket, player_name, room_password)
                    if not success:
                        await websocket.send(json.dumps({'type': 'error', 'message': info}))
                    else:
                        current_room = room
                        await websocket.send(json.dumps({'type': 'joined_room', 'room': room_name}))
                        # Notify all players in the room
                        await notify_all_players(room)

            elif data['type'] == 'join':
                if not current_room:
                    await websocket.send(json.dumps({'type': 'error', 'message': 'No room joined.'}))
                    continue
                player_name = data['name']
                success = current_room.game.add_player(websocket, player_name)
                if not success:
                    await websocket.send(json.dumps({'type': 'error', 'message': 'Room is full.'}))
                    continue
                await websocket.send(current_room.game.to_json())
                await notify_all_players(current_room)

            elif data['type'] == 'move':
                if not current_room:
                    await websocket.send(json.dumps({'type': 'error', 'message': 'No room joined.'}))
                    continue
                column = data['column']
                player = current_room.players.get(websocket)
                if not player:
                    await websocket.send(json.dumps({'type': 'error', 'message': 'Player not found in room.'}))
                    continue
                color = player['color']
                success, status = current_room.game.make_move(column, color)
                if not success:
                    await websocket.send(json.dumps({'type': 'error', 'message': status}))
                    continue
                if status == "win":
                    # Update points
                    player['points'] += 1
                    # Notify all players
                    await notify_all_players(current_room)
                    # Reset the game after a short delay to allow clients to update
                    await asyncio.sleep(1)  # Optional: adjust as needed
                    current_room.game.reset_game()
                    await notify_all_players(current_room)
                else:
                    # Notify all players
                    await notify_all_players(current_room)

    except websockets.exceptions.ConnectionClosed:
        pass
    finally:
        if current_room:
            current_room.remove_player(websocket)
            # Notify remaining players
            if current_room.players:
                await notify_all_players(current_room)
            else:
                # Remove the room if empty
                rooms.pop(current_room.name, None)

start_server = websockets.serve(handler, "0.0.0.0", 6789)

print("Server started on ws://0.0.0.0:6789")
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()