<!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: #ffffff;
        }

        /* 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;
        }

        /* 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');

        // Constants
        const CHUNK_SIZE = 16;
        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 = 0, offsetY = 0;
        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;
            canvas.width = window.innerWidth - drawerWidth;
            canvas.height = window.innerHeight - document.querySelector('header').offsetHeight;
            bufferCanvas.width = canvas.width;
            bufferCanvas.height = canvas.height;
            minimap.width = 200;
            minimap.height = 200;
            loadChunks();
            drawGrid();
        }

        // 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);
        window.addEventListener('load', resizeCanvas);

        // 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;
            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.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);
            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';
            minimapCtx.lineWidth = 1;
            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]) => {
                    const [x, y] = pixelKey.split(',').map(Number);
                    chunks.get(chunkKey)[`${x},${y}`] = { x, y, color: data.color, username: data.username, time: data.time };
                    drawPixel(chunkKey, x, y, data.color);
                });
            });
            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}`] = { 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 / 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.5, Math.min(newZoom, 5)); // Clamp zoom between 0.5 and 5

            // 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 = '';
                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
        resizeCanvas();
    </script>
</body>
</html>

spot and fix the issue where on a small strip of the map, the pixels are not being drawn. outside that strip, the drawing gets offset from the mouse.
respond with the full fixed code