<!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>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 0;
            display: flex;
            flex-direction: column;
            height: 100vh;
            background-color: #f0f0f0;
        }
        header {
            background-color: #333;
            color: white;
            padding: 10px;
            text-align: center;
        }
        main {
            display: flex;
            flex: 1;
            overflow: hidden;
        }
        #canvasContainer {
            flex: 1;
            overflow: hidden;
            position: relative;
        }
        #grid {
            position: absolute;
            cursor: crosshair;
        }
        #grid:active {
            cursor: grabbing;
        }
        #minimap {
            position: absolute;
            bottom: 10px;
            right: 10px;
            border: 2px solid #333;
        }
        #controls {
            width: 220px; /* Adjusted to match canvas resizing */
            padding: 10px;
            background-color: #ddd;
            box-sizing: border-box;
        }
        .color-btn {
            width: 30px;
            height: 30px;
            margin: 2px;
            border: none;
            cursor: pointer;
        }
        #userInfo {
            margin-top: 10px;
        }
        #pixelInfo {
            position: absolute;
            background-color: rgba(0, 0, 0, 0.7);
            color: white;
            padding: 5px;
            border-radius: 3px;
            pointer-events: none;
            white-space: pre-line; /* To display multiline text */
            display: none;
            z-index: 10;
            font-size: 12px;
        }
    </style>
</head>
<body>
    <header>
        <h1>k/place</h1>
        <p>Collaborate on an infinite canvas, one pixel at a time!</p>
    </header>
    <main>
        <div id="canvasContainer">
            <canvas id="grid"></canvas>
            <canvas id="minimap"></canvas>
            <div id="pixelInfo"></div>
        </div>
        <div id="controls">
            <h3>Color Palette</h3>
            <div id="colorPalette"></div>
            <div id="userInfo">
                <input type="text" id="username" placeholder="Enter username">
                <button id="setUsername">Set Username</button>
                <p>Online users: <span id="onlineUsers">0</span></p>
            </div>
        </div>
    </main>
    <!-- Include Socket.io client library -->
    <script src="/socket.io/socket.io.js"></script>
    <script>
        const socket = io();
        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');
        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 CHUNK_SIZE = 16;
        const pixelSize = 10;
        const colors = ['#000000', '#FFFFFF', '#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#00FFFF', '#FF00FF'];
        let offsetX = 0, offsetY = 0;
        let zoom = 1;
        let selectedColor = colors[0];
        let username = 'Anonymous';
        let chunks = new Map();

        // Variables for panning
        let isDragging = false;
        let lastX, lastY;

        // Initialize color palette
        colors.forEach(color => {
            const btn = document.createElement('button');
            btn.className = 'color-btn';
            btn.style.backgroundColor = color;
            btn.onclick = () => {
                selectedColor = color;
                // Highlight selected color
                document.querySelectorAll('.color-btn').forEach(b => b.style.border = 'none');
                btn.style.border = '2px solid #555';
            };
            colorPalette.appendChild(btn);
        });
        // Highlight the first color initially
        colorPalette.firstChild.style.border = '2px solid #555';

        // Resize canvases based on window size
        function resizeCanvas() {
            canvas.width = window.innerWidth - 220; // Account for controls width
            canvas.height = window.innerHeight - 100; // Account for header height
            bufferCanvas.width = canvas.width;
            bufferCanvas.height = canvas.height;
            minimap.width = 200;
            minimap.height = 200;
            loadChunks();
            drawGrid();
        }

        // Draw grid lines (optional, can be removed for pure pixel art)
        function drawGrid() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            bufferCtx.clearRect(0, 0, bufferCanvas.width, bufferCanvas.height);

            // Optionally, draw background or grid
            // For better performance, consider removing grid lines
        }

        window.addEventListener('resize', resizeCanvas);
        resizeCanvas();

        // Handle drawing pixels
        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;
            bufferCtx.fillStyle = color;
            bufferCtx.fillRect(
                Math.round((pixelX * pixelSize - offsetX) * zoom),
                Math.round((pixelY * pixelSize - offsetY) * zoom),
                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.entries(chunkData).forEach(([pixelKey, data]) => {
                        const [x, y] = pixelKey.split(',').map(Number);
                        drawPixel(chunkKey, x, y, data.color);
                    });
                }
            });

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

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

            const scaleFactor = minimap.width / (CHUNK_SIZE * pixelSize * 100); // Adjust denominator for minimap zoom
            chunks.forEach((chunkData, chunkKey) => {
                const [chunkX, chunkY] = chunkKey.split(',').map(Number);
                Object.values(chunkData).forEach(pixel => {
                    const pixelX = chunkX * CHUNK_SIZE + pixel.x;
                    const pixelY = chunkY * CHUNK_SIZE + pixel.y;
                    minimapCtx.fillStyle = pixel.color;
                    minimapCtx.fillRect(
                        pixelX * scaleFactor,
                        pixelY * scaleFactor,
                        1,
                        1
                    );
                });
            });

            // Draw viewport rectangle on minimap
            minimapCtx.strokeStyle = 'red';
            const viewportWidth = canvas.width / zoom * scaleFactor;
            const viewportHeight = canvas.height / zoom * scaleFactor;
            minimapCtx.strokeRect(
                offsetX * scaleFactor,
                offsetY * scaleFactor,
                viewportWidth,
                viewportHeight
            );
        }

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

            const visibleChunks = [];
            for (let x = startX; x <= endX; x++) {
                for (let y = startY; y <= endY; y++) {
                    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]) => {
                    chunks.get(chunkKey)[pixelKey] = data;
                });
            });
            updateCanvas();
        });

        // Handle pixel updates from other users
        socket.on('pixelUpdate', (data) => {
            const { chunkKey, x, y, color, username, time } = data;
            if (!chunks.has(chunkKey)) {
                chunks.set(chunkKey, {});
            }
            chunks.get(chunkKey)[`${x},${y}`] = { color, username, time };
            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 / zoom) + offsetX;
            const worldY = (screenY / zoom) + offsetY;

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

            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 / zoom;
                offsetY -= deltaY / zoom;
                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 / zoom) + offsetX;
            const worldY = (screenY / zoom) + offsetY;

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

            socket.emit('getPixelInfo', { x, y });

            // 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 / zoom) + offsetX;
            const worldY = (mouseY / zoom) + offsetY;

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

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

            loadChunks();
        });

        // Handle incoming pixel info
        socket.on('pixelInfo', (info) => {
            if (info) {
                pixelInfo.style.display = 'block';
                pixelInfo.textContent = `Placed by: ${info.username}\nColor: ${info.color}\nPosition: (${info.x}, ${info.y})\nTime: ${new Date(info.time).toLocaleString()}`;
            } 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 = '';
            }
        });

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

        // Initial load
        loadChunks();
    </script>
</body>
</html>

improve styles, write full code with improved styles to make it way more modern. add a drawer on the left side that can be pulled up with an animation when hovering over it with the mouse. it will choose the colors (predefined 16 colors or any hex color), name, etc. write the full code with these changes.