<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>k/place</title>
    <!-- Google Fonts for Modern Typography -->
    <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
    <style>
        /* CSS Variables for Easy Theming */
        :root {
            --primary-color: #2c3e50;
            --secondary-color: #34495e;
            --accent-color: #e74c3c;
            --background-color: #ecf0f1;
            --drawer-width: 250px;
            --transition-speed: 0.3s;
            --button-hover: #2980b9;
            --input-background: #ffffff;
            --input-border: #bdc3c7;
            --text-color: #2c3e50;
            --header-background: #2c3e50;
            --drawer-background: #34495e;
            --drawer-tab-color: #e74c3c;
            --minimap-border: #2c3e50;
            --tooltip-background: rgba(0, 0, 0, 0.8);
            --tooltip-color: #ffffff;
        }

        /* Reset and Base Styles */
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Roboto', sans-serif;
            background-color: var(--background-color);
            color: var(--text-color);
            display: flex;
            flex-direction: column;
            height: 100vh;
            overflow: hidden;
        }

        /* Header Styles */
        header {
            background-color: var(--header-background);
            color: white;
            padding: 15px 20px;
            text-align: center;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
            z-index: 100;
            position: relative;
        }

        header h1 {
            font-weight: 700;
            margin-bottom: 5px;
        }

        header p {
            font-weight: 400;
            font-size: 1rem;
        }

        /* Main Layout */
        main {
            display: flex;
            flex: 1;
            position: relative;
        }

        /* Left Drawer Styles */
        #drawer {
            position: fixed;
            top: 0;
            left: 0;
            width: var(--drawer-width);
            height: 100%;
            background-color: var(--drawer-background);
            color: white;
            padding: 20px;
            transform: translateX(-220px);
            transition: transform var(--transition-speed) ease;
            z-index: 1000;
            box-shadow: 2px 0 5px rgba(0,0,0,0.1);
        }

        #drawer:hover {
            transform: translateX(0);
        }

        /* Drawer Tab */
        #drawer::before {
            content: '☰';
            position: absolute;
            right: -40px;
            top: 50%;
            transform: translateY(-50%);
            background-color: var(--drawer-tab-color);
            color: white;
            width: 40px;
            height: 60px;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            border-top-right-radius: 8px;
            border-bottom-right-radius: 8px;
            font-size: 1.5rem;
            box-shadow: 2px 2px 5px rgba(0,0,0,0.2);
            transition: background-color var(--transition-speed) ease;
        }

        #drawer:hover::before {
            background-color: #c0392b; /* Slightly darker shade */
        }

        /* Drawer Content */
        #drawer h3 {
            margin-bottom: 15px;
            font-weight: 500;
        }

        .color-palette {
            display: flex;
            flex-wrap: wrap;
            gap: 8px;
            margin-bottom: 20px;
        }

        .color-btn {
            width: 30px;
            height: 30px;
            border: 2px solid transparent;
            border-radius: 4px;
            cursor: pointer;
            transition: border 0.2s ease, transform 0.2s ease;
        }

        .color-btn.selected {
            border: 2px solid white;
            transform: scale(1.1);
        }

        /* Color Picker */
        .color-picker {
            display: flex;
            align-items: center;
            gap: 10px;
            margin-bottom: 20px;
        }

        .color-picker input[type="color"] {
            width: 40px;
            height: 40px;
            border: none;
            cursor: pointer;
            padding: 0;
            border-radius: 4px;
        }

        .color-picker label {
            font-size: 0.9rem;
        }

        /* Username Section */
        #usernameSection {
            display: flex;
            flex-direction: column;
            gap: 10px;
            margin-bottom: 20px;
        }

        #usernameSection input {
            padding: 8px 10px;
            border: 1px solid var(--input-border);
            border-radius: 4px;
            font-size: 1rem;
            background-color: var(--input-background);
            color: var(--text-color);
        }

        #usernameSection button {
            padding: 10px;
            background-color: var(--accent-color);
            border: none;
            border-radius: 4px;
            color: white;
            font-size: 1rem;
            cursor: pointer;
            transition: background-color 0.2s ease;
        }

        #usernameSection button:hover {
            background-color: #c0392b; /* Slightly darker shade */
        }

        /* Online Users */
        #onlineUsers {
            font-weight: 500;
            margin-top: 10px;
        }

        /* Canvas Container */
        #canvasContainer {
            flex: 1;
            margin-left: var(--drawer-width);
            position: relative;
            overflow: hidden;
            background-color: #000000; /* Set black as the background beyond the map */
        }

        /* Grid Canvas */
        #grid {
            width: 100%;
            height: 100%;
            background-color: #ffffff;
            cursor: crosshair;
            display: block;
            position: absolute;
            top: 0;
            left: 0;
            z-index: 1;
        }

        /* Minimap Canvas */
        #minimap {
            position: absolute;
            bottom: 20px;
            right: 20px;
            width: 200px;
            height: 200px;
            border: 2px solid var(--minimap-border);
            background-color: rgba(255, 255, 255, 0.9);
            border-radius: 8px;
            z-index: 500;
            cursor: pointer;
        }

        /* Pixel Info Tooltip */
        #pixelInfo {
            position: absolute;
            background-color: var(--tooltip-background);
            color: var(--tooltip-color);
            padding: 8px;
            border-radius: 4px;
            pointer-events: none;
            white-space: pre-line;
            display: none;
            z-index: 1001;
            font-size: 0.9rem;
            max-width: 200px;
        }

        /* Button Hover Effects */
        .color-btn:hover {
            opacity: 0.8;
            transform: scale(1.1);
        }

        /* Responsive Adjustments */
        @media (max-width: 768px) {
            #drawer {
                width: 200px;
                transform: translateX(-180px);
            }

            #drawer::before {
                width: 36px;
                height: 50px;
                font-size: 1.2rem;
            }

            #minimap {
                width: 150px;
                height: 150px;
                bottom: 15px;
                right: 15px;
            }
        }

        /* Ensure No Overlapping */
        header, #drawer, #canvasContainer, #minimap, #pixelInfo {
            user-select: none;
        }
    </style>
</head>
<body>
    <header>
        <h1>k/place</h1>
    </header>
    <main>
        <!-- Left Side Drawer -->
        <div id="drawer">
            <h3>Tools</h3>
            <!-- Color Palette -->
            <div class="color-palette" id="colorPalette">
                <!-- Predefined 16 Colors -->
                <button class="color-btn selected" style="background-color: #000000;" data-color="#000000"></button>
                <button class="color-btn" style="background-color: #FFFFFF;" data-color="#FFFFFF"></button>
                <button class="color-btn" style="background-color: #FF0000;" data-color="#FF0000"></button>
                <button class="color-btn" style="background-color: #00FF00;" data-color="#00FF00"></button>
                <button class="color-btn" style="background-color: #0000FF;" data-color="#0000FF"></button>
                <button class="color-btn" style="background-color: #FFFF00;" data-color="#FFFF00"></button>
                <button class="color-btn" style="background-color: #00FFFF;" data-color="#00FFFF"></button>
                <button class="color-btn" style="background-color: #FF00FF;" data-color="#FF00FF"></button>
                <button class="color-btn" style="background-color: #800000;" data-color="#800000"></button>
                <button class="color-btn" style="background-color: #808000;" data-color="#808000"></button>
                <button class="color-btn" style="background-color: #008000;" data-color="#008000"></button>
                <button class="color-btn" style="background-color: #800080;" data-color="#800080"></button>
                <button class="color-btn" style="background-color: #008080;" data-color="#008080"></button>
                <button class="color-btn" style="background-color: #000080;" data-color="#000080"></button>
                <button class="color-btn" style="background-color: #FFA500;" data-color="#FFA500"></button>
                <button class="color-btn" style="background-color: #A52A2A;" data-color="#A52A2A"></button>
            </div>
            <!-- Color Picker -->
            <div class="color-picker">
                <input type="color" id="customColor" value="#000000">
                <label for="customColor">Custom Color</label>
            </div>
            <!-- Username Section -->
            <div id="usernameSection">
                <input type="text" id="username" placeholder="Enter username">
                <button id="setUsername">Set Username</button>
            </div>
            <!-- Online Users -->
            <div>
                <p>Online users: <span id="onlineUsers">0</span></p>
            </div>
        </div>
        <!-- Canvas Container -->
        <div id="canvasContainer">
            <canvas id="grid"></canvas>
            <canvas id="minimap"></canvas>
            <div id="pixelInfo"></div>
        </div>
    </main>
    <!-- Include Socket.io client library -->
    <script src="/socket.io/socket.io.js"></script>
    <script>
        const socket = io();

        // Canvas Elements
        const canvas = document.getElementById('grid');
        const ctx = canvas.getContext('2d');
        const bufferCanvas = document.createElement('canvas');
        const bufferCtx = bufferCanvas.getContext('2d');
        const minimap = document.getElementById('minimap');
        const minimapCtx = minimap.getContext('2d');

        // UI Elements
        const colorPalette = document.getElementById('colorPalette');
        const pixelInfo = document.getElementById('pixelInfo');
        const usernameInput = document.getElementById('username');
        const setUsernameBtn = document.getElementById('setUsername');
        const onlineUsersSpan = document.getElementById('onlineUsers');
        const customColorPicker = document.getElementById('customColor');

        // Server-Provided Configuration
        let MAP_WIDTH = 10000;   // Default, will be updated from server
        let MAP_HEIGHT = 10000;  // Default, will be updated from server
        let CHUNK_SIZE = 16;    // Default, will be updated from server

        // Constants
        const pixelSize = 10;
        const predefinedColors = [
            '#000000', '#FFFFFF', '#FF0000', '#00FF00', '#0000FF',
            '#FFFF00', '#00FFFF', '#FF00FF', '#800000', '#808000',
            '#008000', '#800080', '#008080', '#000080', '#FFA500',
            '#A52A2A'
        ];

        // State Variables
        let offsetX = MAP_WIDTH / 2, offsetY = MAP_HEIGHT / 2;
        let zoom = 1;
        let selectedColor = predefinedColors[0];
        let username = 'Anonymous';
        let chunks = new Map();

        // Panning Variables
        let isDragging = false;
        let lastX, lastY;

        // Initialize color palette event listeners
        colorPalette.querySelectorAll('.color-btn').forEach(btn => {
            btn.addEventListener('click', () => {
                selectedColor = btn.getAttribute('data-color');
                // Highlight selected color
                colorPalette.querySelectorAll('.color-btn').forEach(b => b.classList.remove('selected'));
                btn.classList.add('selected');
                // Reset custom color picker
                customColorPicker.value = '#000000';
            });
        });

        // Handle custom color picker
        customColorPicker.addEventListener('input', (event) => {
            selectedColor = event.target.value;
            // Remove selection from predefined colors
            colorPalette.querySelectorAll('.color-btn').forEach(b => b.classList.remove('selected'));
        });

        // Resize canvases based on window size
        function resizeCanvas() {
            const drawerWidth = document.getElementById('drawer').offsetWidth;
            const headerHeight = document.querySelector('header').offsetHeight;
            canvas.width = window.innerWidth - drawerWidth;
            canvas.height = window.innerHeight - headerHeight;
            bufferCanvas.width = canvas.width;
            bufferCanvas.height = canvas.height;
            minimap.width = 200;
            minimap.height = 200;
            loadChunks();
            drawGrid();
            drawMinimapViewport();
        }

        // Draw grid lines if desired (optional, can be commented out for better performance)
        function drawGrid() {
            bufferCtx.clearRect(0, 0, bufferCanvas.width, bufferCanvas.height);
            // If you want grid lines, you can implement them here
            // For performance, it's recommended to skip grid lines for large canvases
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.drawImage(bufferCanvas, 0, 0);
        }

        window.addEventListener('resize', resizeCanvas);

        // Fetch map configuration from server
        socket.on('mapConfig', (config) => {
            MAP_WIDTH = config.MAP_WIDTH;
            MAP_HEIGHT = config.MAP_HEIGHT;
            CHUNK_SIZE = config.CHUNK_SIZE;
            // Center the camera
            offsetX = MAP_WIDTH / 2 - (canvas.width / (2 * pixelSize * zoom));
            offsetY = MAP_HEIGHT / 2 - (canvas.height / (2 * pixelSize * zoom));
            resizeCanvas();
        });

        window.addEventListener('load', () => {
            // Initial resize
            resizeCanvas();
        });

        // Function to convert grid coordinates to screen coordinates
        function gridToScreen(x, y) {
            return {
                screenX: Math.floor((x - offsetX) * pixelSize * zoom),
                screenY: Math.floor((y - offsetY) * pixelSize * zoom)
            };
        }

        // Handle drawing pixels on bufferCanvas
        function drawPixel(chunkKey, x, y, color) {
            const [chunkX, chunkY] = chunkKey.split(',').map(Number);
            const pixelX = chunkX * CHUNK_SIZE + x;
            const pixelY = chunkY * CHUNK_SIZE + y;

            // Check if pixel is within map boundaries
            if (pixelX < 0 || pixelX >= MAP_WIDTH || pixelY < 0 || pixelY >= MAP_HEIGHT) {
                return; // Do not draw pixels outside the map
            }

            bufferCtx.fillStyle = color;
            const pos = gridToScreen(pixelX, pixelY);
            bufferCtx.fillRect(
                pos.screenX,
                pos.screenY,
                Math.ceil(pixelSize * zoom),
                Math.ceil(pixelSize * zoom)
            );
        }

        // Update the main canvas
        function updateCanvas() {
            bufferCtx.clearRect(0, 0, bufferCanvas.width, bufferCanvas.height);

            const visibleChunks = getVisibleChunks();
            visibleChunks.forEach(chunkKey => {
                const chunkData = chunks.get(chunkKey);
                if (chunkData) {
                    Object.values(chunkData).forEach(pixel => {
                        drawPixel(chunkKey, pixel.x, pixel.y, pixel.color);
                    });
                }
            });

            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.drawImage(bufferCanvas, 0, 0);
            drawMinimap();
            drawMinimapViewport();
        }

        // Update the minimap
        function drawMinimap() {
            minimapCtx.clearRect(0, 0, minimap.width, minimap.height);

            const scaleFactor = minimap.width / MAP_WIDTH; // Scale minimap based on map size
            chunks.forEach((chunkData, chunkKey) => {
                Object.values(chunkData).forEach(pixel => {
                    const pixelX = chunkKey.split(',')[0] * CHUNK_SIZE + pixel.x;
                    const pixelY = chunkKey.split(',')[1] * CHUNK_SIZE + pixel.y;

                    // Ensure pixel is within map boundaries
                    if (pixelX < 0 || pixelX >= MAP_WIDTH || pixelY < 0 || pixelY >= MAP_HEIGHT) {
                        return;
                    }

                    minimapCtx.fillStyle = pixel.color;
                    minimapCtx.fillRect(
                        pixelX * scaleFactor,
                        pixelY * scaleFactor,
                        1,
                        1
                    );
                });
            });
        }

        // Draw the viewport rectangle on minimap
        function drawMinimapViewport() {
            minimapCtx.strokeStyle = 'red';
            minimapCtx.lineWidth = 1;
            const scaleFactor = minimap.width / MAP_WIDTH;

            const viewportWidth = (canvas.width / (pixelSize * zoom)) * scaleFactor;
            const viewportHeight = (canvas.height / (pixelSize * zoom)) * scaleFactor;

            let viewportX = offsetX * scaleFactor;
            let viewportY = offsetY * scaleFactor;

            // Clamp viewport within minimap
            viewportX = Math.max(0, Math.min(viewportX, minimap.width - viewportWidth));
            viewportY = Math.max(0, Math.min(viewportY, minimap.height - viewportHeight));

            minimapCtx.strokeRect(
                viewportX,
                viewportY,
                viewportWidth,
                viewportHeight
            );
        }

        // Determine which chunks are visible based on current offset and zoom
        function getVisibleChunks() {
            const startX = Math.floor(offsetX / CHUNK_SIZE);
            const startY = Math.floor(offsetY / CHUNK_SIZE);
            const endX = Math.ceil((offsetX + canvas.width / (pixelSize * zoom)) / CHUNK_SIZE);
            const endY = Math.ceil((offsetY + canvas.height / (pixelSize * zoom)) / CHUNK_SIZE);

            const visibleChunks = [];
            for (let x = startX; x <= endX; x++) {
                for (let y = startY; y <= endY; y++) {
                    // Ensure chunk coordinates are within map boundaries
                    if (x * CHUNK_SIZE >= 0 && x * CHUNK_SIZE < MAP_WIDTH &&
                        y * CHUNK_SIZE >= 0 && y * CHUNK_SIZE < MAP_HEIGHT) {
                        visibleChunks.push(`${x},${y}`);
                    }
                }
            }
            return visibleChunks;
        }

        // Request chunks from the server
        function loadChunks() {
            const visibleChunks = getVisibleChunks();
            const chunksToLoad = visibleChunks.filter(chunk => !chunks.has(chunk));
            if (chunksToLoad.length > 0) {
                socket.emit('getChunks', chunksToLoad);
            }
            updateCanvas();
        }

        // Handle incoming chunk data from the server
        socket.on('chunkData', (chunkData) => {
            Object.entries(chunkData).forEach(([chunkKey, pixelData]) => {
                if (!chunks.has(chunkKey)) {
                    chunks.set(chunkKey, {});
                }
                Object.entries(pixelData).forEach(([pixelKey, data]) => {
                    const [x, y] = pixelKey.split(',').map(Number);
                    // Ensure pixel is within map boundaries
                    if ((chunkKey.split(',')[0] * CHUNK_SIZE + x) < 0 || 
                        (chunkKey.split(',')[0] * CHUNK_SIZE + x) >= MAP_WIDTH ||
                        (chunkKey.split(',')[1] * CHUNK_SIZE + y) < 0 ||
                        (chunkKey.split(',')[1] * CHUNK_SIZE + y) >= MAP_HEIGHT) {
                        return;
                    }
                    chunks.get(chunkKey)[`${x},${y}`] = { x, y, color: data.color, username: data.username, time: data.time };
                });
            });
            updateCanvas();
        });

        // Handle pixel updates from other users
        socket.on('pixelUpdate', (data) => {
            const { chunkKey, x, y, color, username, time } = data;
            // Ensure pixel is within map boundaries
            const pixelX = chunkKey.split(',')[0] * CHUNK_SIZE + x;
            const pixelY = chunkKey.split(',')[1] * CHUNK_SIZE + y;
            if (pixelX < 0 || pixelX >= MAP_WIDTH || pixelY < 0 || pixelY >= MAP_HEIGHT) {
                return; // Do not process pixels outside the map
            }
            if (!chunks.has(chunkKey)) {
                chunks.set(chunkKey, {});
            }
            chunks.get(chunkKey)[`${x},${y}`] = { x, y, color, username, time };
            drawPixel(chunkKey, x, y, color);
            updateCanvas();
        });

        // Update online users count
        socket.on('onlineUsers', (count) => {
            onlineUsersSpan.textContent = count;
        });

        // Place a pixel on the canvas
        function placePixel(event) {
            const rect = canvas.getBoundingClientRect();
            const screenX = event.clientX - rect.left;
            const screenY = event.clientY - rect.top;

            // Convert screen coordinates to world coordinates
            const worldX = (screenX / (pixelSize * zoom)) + offsetX;
            const worldY = (screenY / (pixelSize * zoom)) + offsetY;

            // Convert world coordinates to grid coordinates
            const x = Math.floor(worldX);
            const y = Math.floor(worldY);

            // Ensure pixel is within map boundaries
            if (x < 0 || x >= MAP_WIDTH || y < 0 || y >= MAP_HEIGHT) {
                alert('Cannot place pixel outside the map boundaries!');
                return;
            }

            socket.emit('placePixel', { x, y, color: selectedColor, username });
        }

        // Handle mouse down events for placing pixels and panning
        canvas.addEventListener('mousedown', (event) => {
            if (event.button === 0) { // Left mouse button
                placePixel(event);
                canvas.addEventListener('mousemove', placePixel);
            } else if (event.button === 2) { // Right mouse button
                isDragging = true;
                lastX = event.clientX;
                lastY = event.clientY;
                canvas.style.cursor = 'grabbing';
            }
        });

        // Handle mouse up events
        canvas.addEventListener('mouseup', (event) => {
            if (event.button === 0) { // Left mouse button
                canvas.removeEventListener('mousemove', placePixel);
            } else if (event.button === 2) { // Right mouse button
                isDragging = false;
                canvas.style.cursor = 'crosshair';
            }
        });

        // Prevent context menu on right-click
        canvas.addEventListener('contextmenu', (event) => {
            event.preventDefault();
        });

        // Handle mouse move events for panning and displaying pixel info
        canvas.addEventListener('mousemove', (event) => {
            if (isDragging) {
                const deltaX = event.clientX - lastX;
                const deltaY = event.clientY - lastY;
                offsetX -= deltaX / (pixelSize * zoom);
                offsetY -= deltaY / (pixelSize * zoom);

                // Clamp offsets to map boundaries
                const maxOffsetX = MAP_WIDTH - canvas.width / (pixelSize * zoom);
                const maxOffsetY = MAP_HEIGHT - canvas.height / (pixelSize * zoom);
                offsetX = Math.max(0, Math.min(offsetX, maxOffsetX));
                offsetY = Math.max(0, Math.min(offsetY, maxOffsetY));

                lastX = event.clientX;
                lastY = event.clientY;

                loadChunks();
                return; // Skip pixel info when panning
            }

            const rect = canvas.getBoundingClientRect();
            const screenX = event.clientX - rect.left;
            const screenY = event.clientY - rect.top;

            // Convert screen coordinates to world coordinates
            const worldX = (screenX / (pixelSize * zoom)) + offsetX;
            const worldY = (screenY / (pixelSize * zoom)) + offsetY;

            // Convert world coordinates to grid coordinates
            const x = Math.floor(worldX);
            const y = Math.floor(worldY);

            // Ensure pixel is within map boundaries before requesting info
            if (x >= 0 && x < MAP_WIDTH && y >= 0 && y < MAP_HEIGHT) {
                socket.emit('getPixelInfo', { x, y });
            } else {
                pixelInfo.style.display = 'none';
                return;
            }

            // Position the pixelInfo tooltip near the cursor
            pixelInfo.style.left = `${event.clientX + 10}px`;
            pixelInfo.style.top = `${event.clientY + 10}px`;
        });

        // Handle zooming with the mouse wheel
        canvas.addEventListener('wheel', (event) => {
            event.preventDefault();
            const rect = canvas.getBoundingClientRect();
            const mouseX = event.clientX - rect.left;
            const mouseY = event.clientY - rect.top;

            // Convert screen coordinates to world coordinates before zoom
            const worldX = (mouseX / (pixelSize * zoom)) + offsetX;
            const worldY = (mouseY / (pixelSize * zoom)) + offsetY;

            // Determine zoom factor
            const zoomFactor = event.deltaY > 0 ? 0.9 : 1.1;
            const newZoom = zoom * zoomFactor;
            zoom = Math.max(0.2, Math.min(newZoom, 50)); // Clamp zoom

            // Adjust offset to keep the zoom centered on the mouse position
            offsetX = worldX - (mouseX / (pixelSize * zoom));
            offsetY = worldY - (mouseY / (pixelSize * zoom));

            // Clamp offsets to map boundaries after zoom
            const maxOffsetX = MAP_WIDTH - canvas.width / (pixelSize * zoom);
            const maxOffsetY = MAP_HEIGHT - canvas.height / (pixelSize * zoom);
            offsetX = Math.max(0, Math.min(offsetX, maxOffsetX));
            offsetY = Math.max(0, Math.min(offsetY, maxOffsetY));

            loadChunks();
        });

        // Handle incoming pixel info
        socket.on('pixelInfo', (info) => {
            if (info) {
                pixelInfo.style.display = 'block';
                const timeString = new Date(info.time).toLocaleString();
                pixelInfo.textContent = `Placed by: ${info.username}\nColor: ${info.color}\nPosition: (${info.x}, ${info.y})\nTime: ${timeString}`;
            } else {
                pixelInfo.style.display = 'none';
            }
        });

        // Handle setting the username
        setUsernameBtn.addEventListener('click', () => {
            const inputUsername = usernameInput.value.trim();
            if (inputUsername) {
                username = inputUsername;
                socket.emit('setUsername', username);
                usernameInput.value = '';
                alert(`Username set to ${username}`);
            }
        });

        // Optionally, set username on Enter key
        usernameInput.addEventListener('keypress', (event) => {
            if (event.key === 'Enter') {
                setUsernameBtn.click();
            }
        });

        // Ensure initial canvas renders any existing pixels
        function initialize() {
            resizeCanvas();
        }

        initialize();

        // Handle map updates if needed
        // Additional features can be implemented here
        socket.on('disconnect', () => {
            onlineUsersSpan.textContent = 0;
        });

        // Minimap Interactivity: Click to navigate
        minimap.addEventListener('click', (event) => {
            const rect = minimap.getBoundingClientRect();
            const clickX = event.clientX - rect.left;
            const clickY = event.clientY - rect.top;

            const mapClickX = clickX / minimap.width * MAP_WIDTH;
            const mapClickY = clickY / minimap.height * MAP_HEIGHT;

            // Center the main canvas on the clicked point
            offsetX = mapClickX - (canvas.width / (2 * pixelSize * zoom));
            offsetY = mapClickY - (canvas.height / (2 * pixelSize * zoom));

            // Clamp offsets
            const maxOffsetX = MAP_WIDTH - canvas.width / (pixelSize * zoom);
            const maxOffsetY = MAP_HEIGHT - canvas.height / (pixelSize * zoom);
            offsetX = Math.max(0, Math.min(offsetX, maxOffsetX));
            offsetY = Math.max(0, Math.min(offsetY, maxOffsetY));

            loadChunks();
        });

    </script>
</body>
</html>

const express = require('express');
const app = express();
const http = require('http').createServer(app);
const io = require('socket.io')(http);

const CHUNK_SIZE = 16; // Easily changeable chunk size
const grid = new Map();
let onlineUsers = 0;

app.get('/', (req, res) => {
  res.sendFile(__dirname + '/index.html');
});

function getChunkKey(x, y) {
  const chunkX = Math.floor(x / CHUNK_SIZE);
  const chunkY = Math.floor(y / CHUNK_SIZE);
  return `${chunkX},${chunkY}`;
}

io.on('connection', (socket) => {
  console.log('A user connected');
  onlineUsers++;
  io.emit('onlineUsers', onlineUsers);

  socket.on('getChunks', (chunks) => {
    const chunkData = {};
    chunks.forEach(chunk => {
      const [chunkX, chunkY] = chunk.split(',').map(Number);
      chunkData[chunk] = {};
      for (let x = 0; x < CHUNK_SIZE; x++) {
        for (let y = 0; y < CHUNK_SIZE; y++) {
          const pixelX = chunkX * CHUNK_SIZE + x;
          const pixelY = chunkY * CHUNK_SIZE + y;
          const key = `${pixelX},${pixelY}`;
          const pixelData = grid.get(key);
          if (pixelData) {
            chunkData[chunk][`${x},${y}`] = pixelData;
          }
        }
      }
    });
    socket.emit('chunkData', chunkData);
  });

  socket.on('placePixel', (data) => {
    const { x, y, color, username } = data;
    const key = `${x},${y}`;
    const pixelData = { color, username, time: Date.now() };
    grid.set(key, pixelData);
    const chunkKey = getChunkKey(x, y);
    io.emit('pixelUpdate', { chunkKey, x: x % CHUNK_SIZE, y: y % CHUNK_SIZE, color });
  });

  socket.on('getPixelInfo', (data) => {
    const { x, y } = data;
    const key = `${x},${y}`;
    const pixelData = grid.get(key);
    if (pixelData) {
      socket.emit('pixelInfo', { ...pixelData, x, y });
    } else {
      socket.emit('pixelInfo', null);
    }
  });

  socket.on('setUsername', (username) => {
    socket.username = username;
  });

  socket.on('disconnect', () => {
    console.log('User disconnected');
    onlineUsers--;
    io.emit('onlineUsers', onlineUsers);
  });
});

const PORT = process.env.PORT || 3000;
http.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

add various improvements, such as functionalities, features, style improvements, QoL improvements, and more. Here is one suggestion: make other players' positions visible live, by indicating their cursor position along with their brush color and username and ID. Implement way more improvements than that and respond with the full code with improvements made.