make this client more modern 
fix grid and loading spinner still load after other player join and add animations dont miss with server functions to not make problems with the game
<!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()