<!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 = `${info.username}: ${info.color}\n[${info.x}, ${info.y}]\n(${timeString})`;
            } else {
                const timeString = new Date().toLocaleString();
                pixelInfo.textContent = `No one: #FFFFFF\n[?, ?]\n(${timeString}?)`;
            }
        });

        // 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) => {
    if (socket.username) {
        console.log(`${socket.username} changed their username to ${username}`);
    } else {
        console.log(`A user changed their username to ${username}`);
    }
    
    socket.username = username;
  });

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

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

make 10 improvements to the app, such as seeing the cursor position of other players live, a chat in the sidebar, multiple rooms, creating rooms, friends, DMs