about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--html/rogue/assets/home/goblin-01.pngbin0 -> 11889 bytes
-rw-r--r--html/rogue/assets/home/goblin-02.pngbin0 -> 11875 bytes
-rw-r--r--html/rogue/assets/home/goblin.json617
-rw-r--r--html/rogue/index.html6
-rw-r--r--html/rogue/js/animation.js53
-rw-r--r--html/rogue/js/camera.js79
-rw-r--r--html/rogue/js/config.js75
-rw-r--r--html/rogue/js/debug.js113
-rw-r--r--html/rogue/js/events.js16
-rw-r--r--html/rogue/js/fow.js41
-rw-r--r--html/rogue/js/game.js83
-rw-r--r--html/rogue/js/hex.js43
-rw-r--r--html/rogue/js/inventory-ui.js37
-rw-r--r--html/rogue/js/player.js79
-rw-r--r--html/rogue/js/renderer.js29
-rw-r--r--html/rogue/js/state.js17
-rw-r--r--html/rogue/js/utils.js25
-rw-r--r--html/voxels/index.html22
-rw-r--r--html/voxels/js/game.js362
19 files changed, 1584 insertions, 113 deletions
diff --git a/html/rogue/assets/home/goblin-01.png b/html/rogue/assets/home/goblin-01.png
new file mode 100644
index 0000000..c70d2fd
--- /dev/null
+++ b/html/rogue/assets/home/goblin-01.png
Binary files differdiff --git a/html/rogue/assets/home/goblin-02.png b/html/rogue/assets/home/goblin-02.png
new file mode 100644
index 0000000..19411c8
--- /dev/null
+++ b/html/rogue/assets/home/goblin-02.png
Binary files differdiff --git a/html/rogue/assets/home/goblin.json b/html/rogue/assets/home/goblin.json
new file mode 100644
index 0000000..8b0e418
--- /dev/null
+++ b/html/rogue/assets/home/goblin.json
@@ -0,0 +1,617 @@
+{
+  "__projectHeader": "pppppp_v1",
+  "timestamp": "2024-12-28T22:11:34.632Z",
+  "data": {
+    "gridWidth": 16,
+    "gridHeight": 16,
+    "cellSize": 29.296875,
+    "colorHistory": [
+      "#eeb243",
+      "#ffbe47",
+      "#ff5eeb",
+      "#ff5feb",
+      "#9db13a",
+      "#000000",
+      "#e7ad41",
+      "#f5fff6",
+      "#af2866",
+      "#a87d2e"
+    ],
+    "currentColor": "#9db13a",
+    "canvases": [
+      {
+        "grid": [
+          [
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null
+          ],
+          [
+            null,
+            null,
+            null,
+            "#000000",
+            "#000000",
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null
+          ],
+          [
+            null,
+            null,
+            "#000000",
+            "#9db13a",
+            "#99b213",
+            "#000000",
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            "#000000",
+            "#000000",
+            null
+          ],
+          [
+            null,
+            null,
+            "#000000",
+            "#9db13a",
+            "#ff5feb",
+            "#99b213",
+            "#000000",
+            "#000000",
+            "#000000",
+            "#000000",
+            null,
+            null,
+            "#000000",
+            "#99b213",
+            "#000000",
+            null
+          ],
+          [
+            null,
+            "#000000",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#000000",
+            null,
+            "#000000",
+            "#99b213",
+            "#000000",
+            null
+          ],
+          [
+            null,
+            "#000000",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#000000",
+            "#c00f68",
+            "#c00f68",
+            "#b07b12",
+            "#000000"
+          ],
+          [
+            null,
+            "#000000",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#000000",
+            "#000000",
+            "#99b213",
+            "#f5fff6",
+            "#f5fff6",
+            "#99b213",
+            "#000000",
+            "#c00f68",
+            "#c00f68",
+            "#b07b12",
+            "#000000"
+          ],
+          [
+            null,
+            "#000000",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#000000",
+            "#99b213",
+            "#000000",
+            "#c00f68",
+            "#c00f68",
+            "#000000",
+            null
+          ],
+          [
+            null,
+            "#000000",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#000000",
+            "#99b213",
+            "#000000",
+            "#c00f68",
+            "#c00f68",
+            "#000000",
+            null
+          ],
+          [
+            null,
+            "#000000",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#000000",
+            "#000000",
+            "#99b213",
+            "#99b213",
+            "#f5fff6",
+            "#99b213",
+            "#000000",
+            "#c00f68",
+            "#c00f68",
+            "#b07b12",
+            "#000000"
+          ],
+          [
+            null,
+            "#000000",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#000000",
+            "#c00f68",
+            "#c00f68",
+            "#b07b12",
+            "#000000"
+          ],
+          [
+            null,
+            "#000000",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#99b213",
+            "#000000",
+            null,
+            "#000000",
+            "#99b213",
+            "#000000",
+            null
+          ],
+          [
+            null,
+            null,
+            "#000000",
+            "#9db13a",
+            "#ff5feb",
+            "#99b213",
+            "#000000",
+            "#000000",
+            "#000000",
+            "#000000",
+            null,
+            null,
+            "#000000",
+            "#99b213",
+            "#000000",
+            null
+          ],
+          [
+            null,
+            null,
+            "#000000",
+            "#9db13a",
+            "#99b213",
+            "#000000",
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            "#000000",
+            "#000000",
+            null
+          ],
+          [
+            null,
+            null,
+            null,
+            "#000000",
+            "#000000",
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null
+          ],
+          [
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null
+          ]
+        ],
+        "offsetX": 143.1015625,
+        "offsetY": 218.625,
+        "hasPixels": false
+      },
+      {
+        "grid": [
+          [
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null
+          ],
+          [
+            null,
+            null,
+            null,
+            "#000000",
+            "#000000",
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null
+          ],
+          [
+            null,
+            null,
+            "#000000",
+            "#9db13a",
+            "#9db13a",
+            "#000000",
+            null,
+            null,
+            null,
+            null,
+            null,
+            "#000000",
+            "#000000",
+            null,
+            null,
+            null
+          ],
+          [
+            null,
+            null,
+            "#000000",
+            "#9db13a",
+            "#ff5eeb",
+            "#9db13a",
+            "#000000",
+            "#000000",
+            "#000000",
+            "#000000",
+            null,
+            "#000000",
+            "#9db13a",
+            "#000000",
+            null,
+            null
+          ],
+          [
+            null,
+            "#000000",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#000000",
+            "#000000",
+            "#9db13a",
+            "#000000",
+            "#000000",
+            null
+          ],
+          [
+            null,
+            "#000000",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#000000",
+            "#af2866",
+            "#af2866",
+            "#a87d2e",
+            "#000000"
+          ],
+          [
+            null,
+            "#000000",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#000000",
+            "#000000",
+            "#9db13a",
+            "#f5fff6",
+            "#f5fff6",
+            "#9db13a",
+            "#000000",
+            "#af2866",
+            "#af2866",
+            "#a87d2e",
+            "#000000"
+          ],
+          [
+            null,
+            "#000000",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#000000",
+            "#9db13a",
+            "#000000",
+            "#af2866",
+            "#af2866",
+            "#000000",
+            null
+          ],
+          [
+            null,
+            "#000000",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#000000",
+            "#9db13a",
+            "#000000",
+            "#af2866",
+            "#af2866",
+            "#000000",
+            null
+          ],
+          [
+            null,
+            "#000000",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#000000",
+            "#000000",
+            "#9db13a",
+            "#9db13a",
+            "#f5fff6",
+            "#9db13a",
+            "#000000",
+            "#af2866",
+            "#af2866",
+            "#a87d2e",
+            "#000000"
+          ],
+          [
+            null,
+            "#000000",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#000000",
+            "#af2866",
+            "#af2866",
+            "#a87d2e",
+            "#000000"
+          ],
+          [
+            null,
+            "#000000",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#9db13a",
+            "#000000",
+            "#000000",
+            "#9db13a",
+            "#000000",
+            "#000000",
+            null
+          ],
+          [
+            null,
+            null,
+            "#000000",
+            "#9db13a",
+            "#ff5eeb",
+            "#9db13a",
+            "#000000",
+            "#000000",
+            "#000000",
+            "#000000",
+            null,
+            "#000000",
+            "#9db13a",
+            "#000000",
+            null,
+            null
+          ],
+          [
+            null,
+            null,
+            "#000000",
+            "#9db13a",
+            "#9db13a",
+            "#000000",
+            null,
+            null,
+            null,
+            null,
+            null,
+            "#000000",
+            "#000000",
+            null,
+            null,
+            null
+          ],
+          [
+            null,
+            null,
+            null,
+            "#000000",
+            "#000000",
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null
+          ],
+          [
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null
+          ]
+        ],
+        "offsetX": 641.1484375,
+        "offsetY": 218.625,
+        "hasPixels": false
+      }
+    ],
+    "isPaletteVisible": true,
+    "globalOffsetX": -87.890625,
+    "globalOffsetY": 0
+  }
+}
\ No newline at end of file
diff --git a/html/rogue/index.html b/html/rogue/index.html
index 362fa7a..655c4cb 100644
--- a/html/rogue/index.html
+++ b/html/rogue/index.html
@@ -20,12 +20,18 @@
 <body>
     <canvas id="gameCanvas"></canvas>
     <script src="js/config.js"></script>
+    <script src="js/animation.js"></script>
     <script src="js/hex.js"></script>
     <script src="js/camera.js"></script>
     <script src="js/fow.js"></script>
     <script src="js/items.js"></script>
     <script src="js/inventory-ui.js"></script>
     <script src="js/player.js"></script>
+    <script src="js/debug.js"></script>
     <script src="js/game.js"></script>
+    <script src="js/utils.js"></script>
+    <script src="js/state.js"></script>
+    <script src="js/renderer.js"></script>
+    <script src="js/events.js"></script>
 </body>
 </html>
\ No newline at end of file
diff --git a/html/rogue/js/animation.js b/html/rogue/js/animation.js
new file mode 100644
index 0000000..d682b01
--- /dev/null
+++ b/html/rogue/js/animation.js
@@ -0,0 +1,53 @@
+const Animation = {
+    // Track loaded images with their paths as keys
+    images: new Map(),
+    
+    // Load an image and return a promise
+    loadImage(path) {
+        if (this.images.has(path)) {
+            return Promise.resolve(this.images.get(path));
+        }
+        
+        return new Promise((resolve, reject) => {
+            const img = new Image();
+            img.onload = () => {
+                this.images.set(path, img);
+                resolve(img);
+            };
+            img.onerror = () => reject(new Error(`Failed to load image: ${path}`));
+            img.src = path;
+        });
+    },
+    
+    // Animation class to handle sprite animations
+    createAnimation(frames, frameTime) {
+        return {
+            frames,
+            frameTime,
+            currentFrame: 0,
+            lastFrameTime: 0,
+            
+            update(currentTime) {
+                if (currentTime - this.lastFrameTime >= this.frameTime) {
+                    this.currentFrame = (this.currentFrame + 1) % this.frames.length;
+                    this.lastFrameTime = currentTime;
+                }
+                return this.frames[this.currentFrame];
+            }
+        };
+    },
+    
+    // Add this new method to scale sprites to fit within bounds
+    scaleToFit(image, maxWidth, maxHeight) {
+        const scale = Math.min(
+            maxWidth / image.width,
+            maxHeight / image.height
+        );
+        
+        return {
+            width: image.width * scale,
+            height: image.height * scale,
+            scale
+        };
+    }
+}; 
\ No newline at end of file
diff --git a/html/rogue/js/camera.js b/html/rogue/js/camera.js
index aaeeea9..05e0f28 100644
--- a/html/rogue/js/camera.js
+++ b/html/rogue/js/camera.js
@@ -4,31 +4,90 @@ const Camera = {
     
     centerOn(hex) {
         const pixelCoord = HexGrid.toPixel(hex);
+        
+        // Calculate desired camera position
         this.x = pixelCoord.x - state.canvas.width / 2;
         this.y = pixelCoord.y - state.canvas.height / 2;
+        
+        // Calculate grid dimensions
+        const gridPixelWidth = Config.hex.GRID_SIZE * Config.hex.WIDTH;
+        const gridPixelHeight = Config.hex.GRID_SIZE * Config.hex.HEIGHT;
+        
+        // Calculate grid bounds (accounting for centered grid)
+        const minX = -gridPixelWidth / 2;
+        const maxX = gridPixelWidth / 2 - state.canvas.width;
+        const minY = -gridPixelHeight / 2;
+        const maxY = gridPixelHeight / 2 - state.canvas.height;
+        
+        // Keep camera within grid bounds
+        this.x = Math.max(minX, Math.min(this.x, maxX));
+        this.y = Math.max(minY, Math.min(this.y, maxY));
+        
+        // Round to prevent sub-pixel rendering
+        this.x = Math.round(this.x);
+        this.y = Math.round(this.y);
     },
 
     smoothFollow(target) {
         const targetPixel = HexGrid.toPixel(target);
-        const screenX = targetPixel.x - this.x;
-        const screenY = targetPixel.y - this.y;
+        const screenX = Math.round(targetPixel.x - this.x);
+        const screenY = Math.round(targetPixel.y - this.y);
         
         const centerX = state.canvas.width / 2;
         const centerY = state.canvas.height / 2;
         
-        // Distance from center of the screen
+        // Determine if we're on a narrow screen
+        const isNarrowScreen = state.canvas.width <= Config.camera.NARROW_SCREEN_THRESHOLD;
+        
+        // Calculate dynamic deadzones based on screen size
+        const deadzoneX = Math.min(
+            Math.max(
+                state.canvas.width * (
+                    isNarrowScreen ? 
+                    Config.camera.DEADZONE_RATIO_X.NARROW : 
+                    Config.camera.DEADZONE_RATIO_X.WIDE
+                ),
+                Config.camera.MIN_DEADZONE
+            ),
+            Config.camera.MAX_DEADZONE
+        );
+        
+        const deadzoneY = Math.min(
+            Math.max(state.canvas.height * Config.camera.DEADZONE_RATIO_Y, 
+                Config.camera.MIN_DEADZONE
+            ),
+            Config.camera.MAX_DEADZONE
+        );
+        
         const deltaX = screenX - centerX;
         const deltaY = screenY - centerY;
         
-        // Only move the camera if the player is outside of the deadzone
-        if (Math.abs(deltaX) > Config.camera.DEADZONE_X) {
-            const adjustX = deltaX - (deltaX > 0 ? Config.camera.DEADZONE_X : -Config.camera.DEADZONE_X);
-            this.x += adjustX * Config.camera.FOLLOW_SPEED;
+        // Use more aggressive follow speed for narrow screens
+        const followSpeed = isNarrowScreen ? 
+            Config.camera.FOLLOW_SPEED * 1.5 : 
+            Config.camera.FOLLOW_SPEED;
+        
+        // Ensure camera moves if player is near screen edges
+        if (Math.abs(deltaX) > deadzoneX) {
+            const adjustX = deltaX - (deltaX > 0 ? deadzoneX : -deadzoneX);
+            this.x += Math.round(adjustX * followSpeed);
         }
         
-        if (Math.abs(deltaY) > Config.camera.DEADZONE_Y) {
-            const adjustY = deltaY - (deltaY > 0 ? Config.camera.DEADZONE_Y : -Config.camera.DEADZONE_Y);
-            this.y += adjustY * Config.camera.FOLLOW_SPEED;
+        if (Math.abs(deltaY) > deadzoneY) {
+            const adjustY = deltaY - (deltaY > 0 ? deadzoneY : -deadzoneY);
+            this.y += Math.round(adjustY * followSpeed);
         }
+        
+        // Calculate grid bounds (accounting for centered grid)
+        const gridPixelWidth = Config.hex.GRID_SIZE * Config.hex.WIDTH;
+        const gridPixelHeight = Config.hex.GRID_SIZE * Config.hex.HEIGHT;
+        const minX = -gridPixelWidth / 2;
+        const maxX = gridPixelWidth / 2 - state.canvas.width;
+        const minY = -gridPixelHeight / 2;
+        const maxY = gridPixelHeight / 2 - state.canvas.height;
+        
+        // Keep camera within grid bounds
+        this.x = Math.max(minX, Math.min(this.x, maxX));
+        this.y = Math.max(minY, Math.min(this.y, maxY));
     }
 }; 
\ No newline at end of file
diff --git a/html/rogue/js/config.js b/html/rogue/js/config.js
index 90c1f49..6ed925f 100644
--- a/html/rogue/js/config.js
+++ b/html/rogue/js/config.js
@@ -1,14 +1,30 @@
 const Config = {
     colors: {
-        BACKGROUND: 'rgba(135, 206, 235, 0.3)',
-        GRID: '#333333',
+        BACKGROUND: 'rgba(135, 207, 235, 1)',
+        GRID: 'rgba(0, 0, 0, 0.25)',
         PLAYER: 'red',
-        HEX_FILL: '#ffffff'
+        HEX_FILL: '#ffffff',
+        FOG: {
+            HIDDEN: 'rgba(0, 0, 0, 1)',
+            REVEALED: 'rgba(0, 0, 0, 0.25)',
+            GRID_DIM: 'rgba(0, 0, 0, 0.0)'
+        },
+        UI: {
+            INVENTORY: {
+                BACKGROUND: 'rgba(0, 0, 0, 0.7)',
+                WINDOW: '#ffffff',
+                TEXT: '#000000'
+            }
+        },
+        ITEMS: {
+            COIN: '#FFD700',
+            GEM: '#50C878'
+        }
     },
     
     hex: {
         SIZE: 40, // Size of a single hex
-        GRID_SIZE: 10, // Number of hexes in the grid (width/height)
+        GRID_SIZE: 30, // Number of hexes in the grid (width/height)
         get WIDTH() { // Computed hex width
             return this.SIZE * 2;
         },
@@ -27,12 +43,55 @@ const Config = {
     player: {
         MOVE_SPEED: 0.1,
         SIZE_RATIO: 1/3,
-        VISION_RANGE: 3
+        VISION_RANGE: 3,
+        SPRITE_SCALE: 0.8,
+        ANIMATION_SPEED: 500
     },
 
     camera: {
-        FOLLOW_SPEED: 0.1, // Camera smoothing factor
-        DEADZONE_X: 200, // Horizontal deadzone in pixels
-        DEADZONE_Y: 150 // Vertical deadzone in pixels
+        FOLLOW_SPEED: 0.1,
+        DEADZONE_RATIO_X: {
+            NARROW: 0.1,
+            WIDE: 0.2
+        },
+        DEADZONE_RATIO_Y: 0.2,
+        MIN_DEADZONE: 30,
+        MAX_DEADZONE: 200,
+        NARROW_SCREEN_THRESHOLD: 600
+    },
+
+    ui: {
+        inventory: {
+            PADDING: 20,
+            WIDTH: 300,
+            HEIGHT: 400,
+            TITLE_FONT: '20px Arial',
+            ITEM_FONT: '16px Arial',
+            ITEM_SPACING: 30,
+            TITLE_OFFSET: 20,
+            ITEMS_START_OFFSET: 60
+        }
+    },
+    
+    items: {
+        SPAWN_COUNT: 10,
+        types: {
+            COIN: {
+                name: 'Coin',
+                size: 0.2
+            },
+            GEM: {
+                name: 'Gem',
+                size: 0.25
+            }
+        }
+    },
+
+    fog: {
+        states: {
+            HIDDEN: { alpha: 1.0 },
+            REVEALED: { alpha: 0.5 },
+            VISIBLE: { alpha: 0 }
+        }
     }
 }; 
\ No newline at end of file
diff --git a/html/rogue/js/debug.js b/html/rogue/js/debug.js
new file mode 100644
index 0000000..f2e0b02
--- /dev/null
+++ b/html/rogue/js/debug.js
@@ -0,0 +1,113 @@
+const Debug = {
+    isEnabled: false,
+    lastFrameTime: performance.now(),
+    frameCount: 0,
+    fps: 0,
+    fpsUpdateInterval: 500, // Update FPS display every 500ms
+    lastFpsUpdate: 0,
+    
+    init() {
+        // Add keyboard listener for debug toggle
+        window.addEventListener('keydown', (e) => {
+            if (e.key.toLowerCase() === 'd') {
+                this.isEnabled = !this.isEnabled;
+            }
+        });
+    },
+    
+    update(currentTime) {
+        if (!this.isEnabled) return;
+        
+        this.frameCount++;
+        
+        // Update FPS counter every 500ms
+        if (currentTime - this.lastFpsUpdate >= this.fpsUpdateInterval) {
+            this.fps = Math.round((this.frameCount * 1000) / (currentTime - this.lastFpsUpdate));
+            this.frameCount = 0;
+            this.lastFpsUpdate = currentTime;
+        }
+        
+        this.lastFrameTime = currentTime;
+    },
+    
+    draw(ctx) {
+        if (!this.isEnabled) return;
+        
+        const padding = 30;
+        const lineHeight = 20;
+        let y = padding;
+        
+        // Save context state
+        ctx.save();
+        
+        // Set up debug text style
+        ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
+        ctx.fillRect(0, 0, 300, 200);
+        ctx.font = '14px monospace';
+        ctx.fillStyle = '#00FF00';
+        
+        // Display debug information
+        const debugInfo = [
+            `FPS: ${this.fps}`,
+            `Camera: (${Math.round(Camera.x)}, ${Math.round(Camera.y)})`,
+            `Player Hex: (${player.position.q}, ${player.position.r})`,
+            `Screen: ${state.canvas.width}x${state.canvas.height}`,
+            `Inventory Items: ${player.inventory.length}`,
+            `Revealed Hexes: ${FogOfWar.revealed.size}`,
+            `Moving: ${player.target ? 'Yes' : 'No'}`,
+            `Narrow Screen: ${state.canvas.width <= Config.camera.NARROW_SCREEN_THRESHOLD}`
+        ];
+        
+        debugInfo.forEach(info => {
+            ctx.fillText(info, padding, y);
+            y += lineHeight;
+        });
+        
+        // Draw deadzone visualization
+        if (player.target) {
+            const isNarrowScreen = state.canvas.width <= Config.camera.NARROW_SCREEN_THRESHOLD;
+            const deadzoneX = Math.min(
+                Math.max(
+                    state.canvas.width * (
+                        isNarrowScreen ? 
+                        Config.camera.DEADZONE_RATIO_X.NARROW : 
+                        Config.camera.DEADZONE_RATIO_X.WIDE
+                    ),
+                    Config.camera.MIN_DEADZONE
+                ),
+                Config.camera.MAX_DEADZONE
+            );
+            const deadzoneY = Math.min(
+                Math.max(state.canvas.height * Config.camera.DEADZONE_RATIO_Y, 
+                    Config.camera.MIN_DEADZONE
+                ),
+                Config.camera.MAX_DEADZONE
+            );
+            
+            // Draw camera deadzone
+            ctx.strokeStyle = 'rgba(255, 162, 0, 1)';
+            ctx.lineWidth = 2;
+            ctx.strokeRect(
+                state.canvas.width/2 - deadzoneX,
+                state.canvas.height/2 - deadzoneY,
+                deadzoneX * 2,
+                deadzoneY * 2
+            );
+
+            // Draw a small cross at the center of the camera deadzone
+            const centerX = state.canvas.width / 2;
+            const centerY = state.canvas.height / 2;
+            const crossSize = 10; // Size of the cross arms
+
+            ctx.beginPath();
+            ctx.moveTo(centerX - crossSize, centerY);
+            ctx.lineTo(centerX + crossSize, centerY);
+            ctx.moveTo(centerX, centerY - crossSize);
+            ctx.lineTo(centerX, centerY + crossSize);
+            ctx.stroke();
+        }
+        
+        // Restore context state
+        ctx.restore();
+    }
+}; 
\ No newline at end of file
diff --git a/html/rogue/js/events.js b/html/rogue/js/events.js
new file mode 100644
index 0000000..9ae8241
--- /dev/null
+++ b/html/rogue/js/events.js
@@ -0,0 +1,16 @@
+const EventSystem = {
+    listeners: new Map(),
+    
+    on(event, callback) {
+        if (!this.listeners.has(event)) {
+            this.listeners.set(event, new Set());
+        }
+        this.listeners.get(event).add(callback);
+    },
+    
+    emit(event, data) {
+        if (this.listeners.has(event)) {
+            this.listeners.get(event).forEach(callback => callback(data));
+        }
+    }
+}; 
\ No newline at end of file
diff --git a/html/rogue/js/fow.js b/html/rogue/js/fow.js
index 77f55c0..291c862 100644
--- a/html/rogue/js/fow.js
+++ b/html/rogue/js/fow.js
@@ -53,33 +53,32 @@ const FogOfWar = {
         }
     },
     
+    getFogState(hex) {
+        if (!this.isRevealed(hex)) return Config.fog.states.HIDDEN;
+        if (!this.isVisible(hex)) return Config.fog.states.REVEALED;
+        return Config.fog.states.VISIBLE;
+    },
+    
     // Draw fog of war effect
     draw(ctx) {
-        // Draw fog over unrevealed areas
         HexGrid.getViewportHexes().forEach(hex => {
-            if (!this.isRevealed(hex) || !this.isVisible(hex)) {
-                const pixel = HexGrid.toPixel(hex);
-                const screenX = pixel.x - Camera.x;
-                const screenY = pixel.y - Camera.y;
+            const fogState = this.getFogState(hex);
+            if (fogState.alpha > 0) {
+                const screen = HexGrid.toScreenCoordinates(hex, Camera);
                 
-                ctx.fillStyle = this.isRevealed(hex) ? 
-                    'rgba(0, 0, 0, 0.5)' :  // Darker fog for unexplored areas
-                    'rgba(0, 0, 0, 0.8)';   // Lighter fog for explored but not visible
+                // Draw fog fill
+                ctx.fillStyle = fogState === Config.fog.states.HIDDEN ? 
+                    Config.colors.FOG.HIDDEN : 
+                    Config.colors.FOG.REVEALED;
+                HexGrid.drawHexPath(ctx, screen.x, screen.y, HexGrid.SIZE, 1);
+                ctx.fill();
                 
-                // Draw fog hex
-                ctx.beginPath();
-                for (let i = 0; i < 6; i++) {
-                    const angle = 2 * Math.PI / 6 * i;
-                    const xPos = screenX + HexGrid.SIZE * Math.cos(angle);
-                    const yPos = screenY + HexGrid.SIZE * Math.sin(angle);
-                    if (i === 0) {
-                        ctx.moveTo(xPos, yPos);
-                    } else {
-                        ctx.lineTo(xPos, yPos);
-                    }
+                // Draw grid lines only for revealed but not visible hexes
+                if (fogState === Config.fog.states.REVEALED) {
+                    ctx.strokeStyle = Config.colors.FOG.GRID_DIM;
+                    ctx.lineWidth = 1;
+                    ctx.stroke();
                 }
-                ctx.closePath();
-                ctx.fill();
             }
         });
     }
diff --git a/html/rogue/js/game.js b/html/rogue/js/game.js
index 918f8e5..48133d8 100644
--- a/html/rogue/js/game.js
+++ b/html/rogue/js/game.js
@@ -7,11 +7,12 @@ const state = {
     ctx: null
 };
 
-function init() {
+async function init() {
     state.canvas = document.getElementById('gameCanvas');
     state.ctx = state.canvas.getContext('2d');
     
-    player.init();
+    await player.init();
+    Debug.init();
     
     function resize() {
         state.canvas.width = window.innerWidth;
@@ -29,86 +30,72 @@ function init() {
 }
 
 function drawHex(ctx, x, y, hex) {
-    const screenX = x - Camera.x;
-    const screenY = y - Camera.y;
+    const screen = HexGrid.toScreenCoordinates(hex, Camera);
     
-    // Only draw if hex is visible on screen (with some padding)
-    if (screenX < -HexGrid.WIDTH || screenX > state.canvas.width + HexGrid.WIDTH ||
-        screenY < -HexGrid.HEIGHT || screenY > state.canvas.height + HexGrid.HEIGHT) {
+    // Only draw if hex is visible on screen (with padding)
+    if (!HexGrid.isInViewport(screen.x, screen.y, state.canvas)) {
         return;
     }
     
-    ctx.beginPath();
-    for (let i = 0; i < 6; i++) {
-        const angle = 2 * Math.PI / 6 * i;
-        const xPos = screenX + HexGrid.SIZE * Math.cos(angle);
-        const yPos = screenY + HexGrid.SIZE * Math.sin(angle);
-        if (i === 0) {
-            ctx.moveTo(xPos, yPos);
-        } else {
-            ctx.lineTo(xPos, yPos);
-        }
-    }
-    ctx.closePath();
-
-    // Fill hex with appropriate color
-    if (HexGrid.isPassable(hex)) {
-        ctx.fillStyle = Config.colors.HEX_FILL;
-    } else {
-        ctx.fillStyle = Config.colors.BACKGROUND;
+    // Skip drawing completely if hex hasn't been revealed
+    if (!FogOfWar.isRevealed(hex)) {
+        return;
     }
+    
+    // Draw the hex fill
+    HexGrid.drawHexPath(ctx, screen.x, screen.y);
+    ctx.fillStyle = Config.colors.HEX_FILL;
     ctx.fill();
     
-    // Draw border
-    ctx.strokeStyle = HexGrid.COLOR;
-    ctx.lineWidth = 1;
-    ctx.stroke();
+    // Only draw grid lines for currently visible hexes
+    // (fog of war will handle the grid lines for revealed but not visible hexes)
+    if (FogOfWar.isVisible(hex)) {
+        ctx.strokeStyle = Config.colors.GRID;
+        ctx.lineWidth = 1;
+        ctx.stroke();
+    }
 }
 
 function gameLoop(currentTime) {
+    requestAnimationFrame(gameLoop);
+    
     if (currentTime - lastFrameTime < Config.game.FRAME_TIME) {
-        requestAnimationFrame(gameLoop);
         return;
     }
     
-    const deltaTime = currentTime - lastFrameTime;
+    const deltaTime = Math.min(currentTime - lastFrameTime, Config.game.FRAME_TIME * 2);
     lastFrameTime = currentTime;
 
-    // Clear the entire canvas first
-    state.ctx.clearRect(0, 0, state.canvas.width, state.canvas.height);
-    
-    // Then fill with background color
+    // Update debug information
+    Debug.update(currentTime);
+
+    // Clear with background
     state.ctx.fillStyle = Config.colors.BACKGROUND;
     state.ctx.fillRect(0, 0, state.canvas.width, state.canvas.height);
     
+    // Round camera position to prevent sub-pixel rendering
+    Camera.x = Math.round(Camera.x);
+    Camera.y = Math.round(Camera.y);
+    
     player.update();
     Camera.smoothFollow(player.getCurrentPosition());
     
-    // Update fog of war when player moves
     if (player.hasMoved) {
         FogOfWar.updateVisibility(player.position);
         player.hasMoved = false;
     }
     
-    // Draw layers in correct order
+    // Draw everything in one pass to prevent flicker
     HexGrid.getViewportHexes().forEach(hex => {
         const pixel = HexGrid.toPixel(hex);
-        drawHex(state.ctx, pixel.x, pixel.y, hex);
+        drawHex(state.ctx, Math.round(pixel.x), Math.round(pixel.y), hex);
     });
     
-    // Draw items
     Items.draw(state.ctx, HexGrid.toPixel.bind(HexGrid), Camera, HexGrid.SIZE);
-    
-    // Draw player
     player.draw(state.ctx, HexGrid.toPixel.bind(HexGrid), Camera, HexGrid.SIZE);
-    
-    // Draw fog of war
     FogOfWar.draw(state.ctx);
-    
-    // Draw inventory UI last
     InventoryUI.draw(state.ctx);
-    
-    requestAnimationFrame(gameLoop);
+    Debug.draw(state.ctx);
 }
 
 function handleClick(event) {
@@ -131,4 +118,4 @@ function handleClick(event) {
     }
 }
 
-init();
\ No newline at end of file
+init().catch(console.error);
\ No newline at end of file
diff --git a/html/rogue/js/hex.js b/html/rogue/js/hex.js
index 0d1c2e5..fa08e3d 100644
--- a/html/rogue/js/hex.js
+++ b/html/rogue/js/hex.js
@@ -1,20 +1,20 @@
-// Hex grid utilities and calculations
+// This that witchy shit -- we be hexin!
+
 const HexGrid = {
     get SIZE() { return Config.hex.SIZE },
     get WIDTH() { return Config.hex.WIDTH },
     get HEIGHT() { return Config.hex.HEIGHT },
     get GRID_SIZE() { return Config.hex.GRID_SIZE },
     COLOR: Config.colors.GRID,
-    IMPASSABLE_COLOR: Config.colors.BACKGROUND,
 
-    // Convert hex coordinates to pixel coordinates
+    // hex to pixel
     toPixel(hex) {
         const x = this.SIZE * (3/2 * hex.q);
         const y = this.SIZE * (Math.sqrt(3)/2 * hex.q + Math.sqrt(3) * hex.r);
         return {x, y};
     },
 
-    // Convert pixel coordinates to hex coordinates
+    // pixel to hex
     fromPixel(x, y) {
         const q = (2/3 * x) / this.SIZE;
         const r = (-1/3 * x + Math.sqrt(3)/3 * y) / this.SIZE;
@@ -46,7 +46,7 @@ const HexGrid = {
         return {q: rx, r: rz};
     },
 
-    // Calculate visible hexes
+    // Is this hex in the viewport?
     getViewportHexes() {
         const hexes = [];
         const halfGrid = Math.floor(this.GRID_SIZE / 2);
@@ -59,9 +59,40 @@ const HexGrid = {
         return hexes;
     },
 
-    // Add this method to check if a hex is passable
+    // Check if a hex is passable
     isPassable(hex) {
         const halfGrid = Math.floor(this.GRID_SIZE / 2);
         return Math.abs(hex.q) <= halfGrid && Math.abs(hex.r) <= halfGrid;
+    },
+
+    // Centralized hex drawing function
+    drawHexPath(ctx, x, y, size = this.SIZE, padding = 0) {
+        ctx.beginPath();
+        for (let i = 0; i < 6; i++) {
+            const angle = 2 * Math.PI / 6 * i;
+            const xPos = Math.round(x + (size + padding) * Math.cos(angle));
+            const yPos = Math.round(y + (size + padding) * Math.sin(angle));
+            if (i === 0) {
+                ctx.moveTo(xPos, yPos);
+            } else {
+                ctx.lineTo(xPos, yPos);
+            }
+        }
+        ctx.closePath();
+    },
+
+    toScreenCoordinates(hex, camera) {
+        const pixel = this.toPixel(hex);
+        return {
+            x: Math.round(pixel.x - camera.x),
+            y: Math.round(pixel.y - camera.y)
+        };
+    },
+
+    isInViewport(screenX, screenY, canvas) {
+        return !(screenX < -this.WIDTH || 
+                screenX > canvas.width + this.WIDTH ||
+                screenY < -this.HEIGHT || 
+                screenY > canvas.height + this.HEIGHT);
     }
 }; 
\ No newline at end of file
diff --git a/html/rogue/js/inventory-ui.js b/html/rogue/js/inventory-ui.js
index a5c7c3f..c7ce63c 100644
--- a/html/rogue/js/inventory-ui.js
+++ b/html/rogue/js/inventory-ui.js
@@ -21,33 +21,42 @@ const InventoryUI = {
         if (!this.isOpen) return;
         
         // Draw semi-transparent background
-        ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
+        ctx.fillStyle = Config.colors.UI.INVENTORY.BACKGROUND;
         ctx.fillRect(0, 0, state.canvas.width, state.canvas.height);
         
-        // Draw inventory window
-        const padding = 20;
-        const width = 300;
-        const height = 400;
-        const x = (state.canvas.width - width) / 2;
-        const y = (state.canvas.height - height) / 2;
+        // Calculate positions ensuring integer values
+        const padding = Config.ui.inventory.PADDING;
+        const width = Config.ui.inventory.WIDTH;
+        const height = Config.ui.inventory.HEIGHT;
+        const x = Math.round((state.canvas.width - width) / 2);
+        const y = Math.round((state.canvas.height - height) / 2);
         
         // Draw window background
-        ctx.fillStyle = '#ffffff';
+        ctx.fillStyle = Config.colors.UI.INVENTORY.WINDOW;
         ctx.fillRect(x, y, width, height);
         
+        // Set text rendering properties for sharper text
+        ctx.textBaseline = 'top';
+        ctx.textAlign = 'left';
+        ctx.imageSmoothingEnabled = false;
+        
         // Draw title
-        ctx.fillStyle = '#000000';
-        ctx.font = '20px Arial';
-        ctx.fillText('Inventory', x + padding, y + padding + 20);
+        ctx.fillStyle = Config.colors.UI.INVENTORY.TEXT;
+        ctx.font = Config.ui.inventory.TITLE_FONT;
+        const titleX = Math.round(x + padding);
+        const titleY = Math.round(y + padding);
+        ctx.fillText('Inventory', titleX, titleY);
         
         // Get item counts and draw items with quantities
         const itemCounts = this.getItemCounts();
         let index = 0;
         
         itemCounts.forEach((count, itemName) => {
-            const itemY = y + padding + 60 + (index * 30);
-            ctx.font = '16px Arial';
-            ctx.fillText(`${itemName} x${count}`, x + padding, itemY);
+            const itemX = Math.round(x + padding);
+            const itemY = Math.round(y + Config.ui.inventory.ITEMS_START_OFFSET + 
+                                   (index * Config.ui.inventory.ITEM_SPACING));
+            ctx.font = Config.ui.inventory.ITEM_FONT;
+            ctx.fillText(`${itemName} x${count}`, itemX, itemY);
             index++;
         });
     }
diff --git a/html/rogue/js/player.js b/html/rogue/js/player.js
index 3c1c383..efecbdf 100644
--- a/html/rogue/js/player.js
+++ b/html/rogue/js/player.js
@@ -7,12 +7,30 @@ const player = {
     moveSpeed: Config.player.MOVE_SPEED,              // Movement speed (0 to 1 per frame)
     inventory: [],
     
+    // Animation properties
+    animation: null,
+    sprites: [],
+    
     // Initialize player
-    init() {
+    async init() {
         this.position = { q: 0, r: 0 };
         this.target = null;
         this.path = [];
         this.inventory = [];
+        
+        // Load sprites
+        try {
+            const [sprite1, sprite2] = await Promise.all([
+                Animation.loadImage('assets/home/goblin-01.png'),
+                Animation.loadImage('assets/home/goblin-02.png')
+            ]);
+            
+            this.sprites = [sprite1, sprite2];
+            this.animation = Animation.createAnimation(this.sprites, 500); // 500ms per frame
+        } catch (error) {
+            console.error('Failed to load player sprites:', error);
+        }
+        
         return this;
     },
 
@@ -151,14 +169,63 @@ const player = {
         const screenX = pixelPos.x - camera.x;
         const screenY = pixelPos.y - camera.y;
 
-        ctx.fillStyle = Config.colors.PLAYER;
-        ctx.beginPath();
-        ctx.arc(screenX, screenY, HEX_SIZE * Config.player.SIZE_RATIO, 0, Math.PI * 2);
-        ctx.fill();
+        if (this.animation && this.sprites.length > 0) {
+            // Get current sprite from animation
+            const currentSprite = this.animation.update(performance.now());
+            
+            // Scale sprite to fit within hex
+            // Use slightly smaller than hex size to ensure it fits visually
+            const hexInnerSize = HEX_SIZE * 0.8; // 80% of hex size
+            const { width, height, scale } = Animation.scaleToFit(
+                currentSprite, 
+                hexInnerSize * 2, // width
+                hexInnerSize * Math.sqrt(3) // height (hex height)
+            );
+            
+            // Calculate position to center the sprite in the hex
+            const spriteX = screenX - width / 2;
+            const spriteY = screenY - height / 2;
+            
+            // Save context state
+            ctx.save();
+            
+            // Optional: add a small bounce effect when moving
+            if (this.target) {
+                const bounce = Math.sin(performance.now() / 100) * 2;
+                ctx.translate(spriteX, spriteY + bounce);
+            } else {
+                ctx.translate(spriteX, spriteY);
+            }
+            
+            // Draw the sprite
+            ctx.drawImage(
+                currentSprite,
+                0, 0,
+                width,
+                height
+            );
+            
+            // Restore context state
+            ctx.restore();
+            
+            // Debug: draw hex bounds if debug is enabled
+            if (Debug.isEnabled) {
+                ctx.strokeStyle = 'rgba(255, 0, 0, 0.5)';
+                ctx.beginPath();
+                HexGrid.drawHexPath(ctx, screenX, screenY, HEX_SIZE * 0.8);
+                ctx.stroke();
+            }
+        } else {
+            // Fallback to circle if sprites aren't loaded
+            ctx.fillStyle = Config.colors.PLAYER;
+            ctx.beginPath();
+            ctx.arc(screenX, screenY, HEX_SIZE * Config.player.SIZE_RATIO, 0, Math.PI * 2);
+            ctx.fill();
+        }
         
         // Draw path if needed
         if (this.path.length > 0) {
-            ctx.strokeStyle = Config.colors.PLAYER + '4D'; // 30% opacity version of player color
+            ctx.strokeStyle = Config.colors.PLAYER + '4D';
             ctx.beginPath();
             let lastPos = this.target || this.position;
             this.path.forEach(point => {
diff --git a/html/rogue/js/renderer.js b/html/rogue/js/renderer.js
new file mode 100644
index 0000000..3e64666
--- /dev/null
+++ b/html/rogue/js/renderer.js
@@ -0,0 +1,29 @@
+const Renderer = {
+    drawHex(ctx, hex, x, y, size, fillStyle, strokeStyle) {
+        ctx.beginPath();
+        for (let i = 0; i < 6; i++) {
+            const angle = 2 * Math.PI / 6 * i;
+            const xPos = x + size * Math.cos(angle);
+            const yPos = y + size * Math.sin(angle);
+            if (i === 0) ctx.moveTo(xPos, yPos);
+            else ctx.lineTo(xPos, yPos);
+        }
+        ctx.closePath();
+        
+        if (fillStyle) {
+            ctx.fillStyle = fillStyle;
+            ctx.fill();
+        }
+        if (strokeStyle) {
+            ctx.strokeStyle = strokeStyle;
+            ctx.stroke();
+        }
+    },
+    
+    drawCircle(ctx, x, y, radius, fillStyle) {
+        ctx.fillStyle = fillStyle;
+        ctx.beginPath();
+        ctx.arc(x, y, radius, 0, Math.PI * 2);
+        ctx.fill();
+    }
+}; 
\ No newline at end of file
diff --git a/html/rogue/js/state.js b/html/rogue/js/state.js
new file mode 100644
index 0000000..f5c713d
--- /dev/null
+++ b/html/rogue/js/state.js
@@ -0,0 +1,17 @@
+const GameState = {
+    canvas: null,
+    ctx: null,
+    lastFrameTime: 0,
+    
+    init() {
+        this.canvas = document.getElementById('gameCanvas');
+        this.ctx = this.canvas.getContext('2d');
+        this.lastFrameTime = 0;
+    },
+    
+    resize() {
+        this.canvas.width = window.innerWidth;
+        this.canvas.height = window.innerHeight;
+        Camera.centerOn(player.position);
+    }
+}; 
\ No newline at end of file
diff --git a/html/rogue/js/utils.js b/html/rogue/js/utils.js
new file mode 100644
index 0000000..bd329fb
--- /dev/null
+++ b/html/rogue/js/utils.js
@@ -0,0 +1,25 @@
+const Utils = {
+    hexToKey(hex) {
+        return `${hex.q},${hex.r}`;
+    },
+    
+    keyToHex(key) {
+        const [q, r] = key.split(',').map(Number);
+        return { q, r };
+    },
+    
+    // Screen/canvas coordinate utilities
+    screenToCanvas(x, y, camera) {
+        return {
+            x: x + camera.x,
+            y: y + camera.y
+        };
+    },
+    
+    canvasToScreen(x, y, camera) {
+        return {
+            x: x - camera.x,
+            y: y - camera.y
+        };
+    }
+}; 
\ No newline at end of file
diff --git a/html/voxels/index.html b/html/voxels/index.html
new file mode 100644
index 0000000..fda7eba
--- /dev/null
+++ b/html/voxels/index.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <title>Isometric Game</title>
+    <style>
+        body {
+            margin: 0;
+            overflow: hidden;
+        }
+        canvas {
+            display: block;
+            width: 100vw;
+            height: 100vh;
+            background: #bce8ff;
+        }
+    </style>
+</head>
+<body>
+    <canvas id="gameCanvas"></canvas>
+    <script src="js/game.js"></script>
+</body>
+</html>
\ No newline at end of file
diff --git a/html/voxels/js/game.js b/html/voxels/js/game.js
new file mode 100644
index 0000000..2ced6fd
--- /dev/null
+++ b/html/voxels/js/game.js
@@ -0,0 +1,362 @@
+class IsometricGame {
+    constructor() {
+        this.canvas = document.getElementById('gameCanvas');
+        this.ctx = this.canvas.getContext('2d');
+        
+        // Grid properties
+        this.gridSize = 10;
+        this.tileWidth = 50;
+        this.tileHeight = 25;
+        
+        // Player properties
+        this.player = {
+            x: 0,
+            y: 0,
+            targetX: 0,
+            targetY: 0,
+            size: 20,
+            path: [], // Array to store waypoints
+            currentWaypoint: null,
+            jumpHeight: 0,
+            jumpProgress: 0,
+            isJumping: false,
+            startX: 0,
+            startY: 0
+        };
+        
+        // Add particle system
+        this.particles = [];
+        
+        // Handle window resize
+        this.resizeCanvas();
+        window.addEventListener('resize', () => this.resizeCanvas());
+        
+        this.setupEventListeners();
+        this.gameLoop();
+    }
+    
+    resizeCanvas() {
+        this.canvas.width = window.innerWidth;
+        this.canvas.height = window.innerHeight;
+        
+        // Recalculate grid offset to center it
+        this.offsetX = this.canvas.width / 2;
+        this.offsetY = this.canvas.height / 3;
+        
+        // Scale tile size based on screen size
+        const minDimension = Math.min(this.canvas.width, this.canvas.height);
+        const scaleFactor = minDimension / 800; // 800 is our reference size
+        this.tileWidth = 50 * scaleFactor;
+        this.tileHeight = 25 * scaleFactor;
+        this.player.size = 20 * scaleFactor;
+    }
+    
+    toIsometric(x, y) {
+        return {
+            x: (x - y) * this.tileWidth / 2,
+            y: (x + y) * this.tileHeight / 2
+        };
+    }
+    
+    fromIsometric(screenX, screenY) {
+        // Convert screen coordinates back to grid coordinates
+        screenX -= this.offsetX;
+        screenY -= this.offsetY;
+        
+        const x = (screenX / this.tileWidth + screenY / this.tileHeight) / 1;
+        const y = (screenY / this.tileHeight - screenX / this.tileWidth) / 1;
+        
+        return { x: Math.round(x), y: Math.round(y) };
+    }
+    
+    drawGrid() {
+        for (let x = 0; x < this.gridSize; x++) {
+            for (let y = 0; y < this.gridSize; y++) {
+                const iso = this.toIsometric(x, y);
+                
+                // Draw tile
+                this.ctx.beginPath();
+                this.ctx.moveTo(iso.x + this.offsetX, iso.y + this.offsetY - this.tileHeight/2);
+                this.ctx.lineTo(iso.x + this.offsetX + this.tileWidth/2, iso.y + this.offsetY);
+                this.ctx.lineTo(iso.x + this.offsetX, iso.y + this.offsetY + this.tileHeight/2);
+                this.ctx.lineTo(iso.x + this.offsetX - this.tileWidth/2, iso.y + this.offsetY);
+                this.ctx.closePath();
+                
+                this.ctx.strokeStyle = '#666';
+                this.ctx.stroke();
+                this.ctx.fillStyle = '#fff';
+                this.ctx.fill();
+            }
+        }
+    }
+    
+    drawPlayer() {
+        // Convert player grid position to isometric coordinates
+        const iso = this.toIsometric(this.player.x, this.player.y);
+        
+        // Apply jump height offset
+        const jumpOffset = this.player.jumpHeight || 0;
+        
+        // Calculate squash and stretch based on jump progress
+        let squashStretch = 1;
+        if (this.player.isJumping) {
+            // Stretch at the start and middle of jump, squash at landing
+            const jumpPhase = Math.sin(this.player.jumpProgress * Math.PI);
+            if (this.player.jumpProgress < 0.2) {
+                // Initial stretch when jumping
+                squashStretch = 1 + (0.3 * (1 - this.player.jumpProgress / 0.2));
+            } else if (this.player.jumpProgress > 0.8) {
+                // Squash when landing
+                squashStretch = 1 - (0.3 * ((this.player.jumpProgress - 0.8) / 0.2));
+            } else {
+                // Slight stretch at peak of jump
+                squashStretch = 1 + (0.1 * jumpPhase);
+            }
+        }
+        
+        // Draw player shadow (gets smaller when jumping)
+        const shadowScale = Math.max(0.2, 1 - (jumpOffset / this.tileHeight));
+        this.ctx.beginPath();
+        this.ctx.ellipse(
+            iso.x + this.offsetX,
+            iso.y + this.offsetY + 2,
+            this.player.size * 0.8 * shadowScale,
+            this.player.size * 0.3 * shadowScale,
+            0,
+            0,
+            Math.PI * 2
+        );
+        this.ctx.fillStyle = `rgba(0,0,0,${0.2 * shadowScale})`;
+        this.ctx.fill();
+
+        // Draw player body with jump offset and squash/stretch
+        this.ctx.beginPath();
+        this.ctx.fillStyle = '#ff4444';
+        this.ctx.strokeStyle = '#aa0000';
+        
+        const bodyHeight = this.player.size * 2 * squashStretch;
+        const bodyWidth = this.player.size * 0.8 * (1 / squashStretch); // Inverse stretch for width
+        
+        this.ctx.save();
+        this.ctx.translate(iso.x + this.offsetX, iso.y + this.offsetY - jumpOffset);
+        this.ctx.scale(1, 0.5); // Apply isometric perspective
+        this.ctx.fillRect(-bodyWidth/2, -bodyHeight, bodyWidth, bodyHeight);
+        this.ctx.strokeRect(-bodyWidth/2, -bodyHeight, bodyWidth, bodyHeight);
+        this.ctx.restore();
+
+        // Draw player head with jump offset and squash/stretch
+        this.ctx.beginPath();
+        this.ctx.ellipse(
+            iso.x + this.offsetX,
+            iso.y + this.offsetY - this.player.size * squashStretch - jumpOffset,
+            this.player.size * (1 / squashStretch),
+            this.player.size * 0.5 * squashStretch,
+            0,
+            0,
+            Math.PI * 2
+        );
+        this.ctx.fillStyle = '#ff4444';
+        this.ctx.fill();
+        this.ctx.strokeStyle = '#aa0000';
+        this.ctx.stroke();
+    }
+    
+    findPath(startX, startY, endX, endY) {
+        // Simple pathfinding that follows grid edges
+        const path = [];
+        
+        // First move along X axis
+        if (startX !== endX) {
+            const stepX = startX < endX ? 1 : -1;
+            for (let x = startX + stepX; stepX > 0 ? x <= endX : x >= endX; x += stepX) {
+                path.push({ x: x, y: startY });
+            }
+        }
+        
+        // Then move along Y axis
+        if (startY !== endY) {
+            const stepY = startY < endY ? 1 : -1;
+            for (let y = startY + stepY; stepY > 0 ? y <= endY : y >= endY; y += stepY) {
+                path.push({ x: endX, y: y });
+            }
+        }
+
+        return path;
+    }
+    
+    updatePlayer() {
+        const jumpDuration = 0.1; // Faster jump for snappier movement
+        const maxJumpHeight = this.tileHeight;
+
+        // If we don't have a current waypoint but have a path, get next waypoint
+        if (!this.player.currentWaypoint && this.player.path.length > 0) {
+            this.player.currentWaypoint = this.player.path.shift();
+            this.player.isJumping = true;
+            this.player.jumpProgress = 0;
+            
+            // Store starting position for interpolation
+            this.player.startX = this.player.x;
+            this.player.startY = this.player.y;
+        }
+
+        // Move towards current waypoint
+        if (this.player.currentWaypoint) {
+            // Update jump animation
+            if (this.player.isJumping) {
+                this.player.jumpProgress += jumpDuration;
+                
+                // Clamp progress to 1
+                if (this.player.jumpProgress > 1) this.player.jumpProgress = 1;
+                
+                // Parabolic jump arc
+                this.player.jumpHeight = Math.sin(this.player.jumpProgress * Math.PI) * maxJumpHeight;
+                
+                // Precise interpolation between points
+                this.player.x = this.player.startX + (this.player.currentWaypoint.x - this.player.startX) * this.player.jumpProgress;
+                this.player.y = this.player.startY + (this.player.currentWaypoint.y - this.player.startY) * this.player.jumpProgress;
+                
+                // Landing
+                if (this.player.jumpProgress >= 1) {
+                    this.player.isJumping = false;
+                    this.player.jumpHeight = 0;
+                    this.player.x = this.player.currentWaypoint.x;
+                    this.player.y = this.player.currentWaypoint.y;
+                    this.createDustParticles(this.player.x, this.player.y);
+                    this.player.currentWaypoint = null;
+                }
+            }
+        }
+    }
+    
+    setupEventListeners() {
+        this.canvas.addEventListener('click', (e) => {
+            const rect = this.canvas.getBoundingClientRect();
+            const clickX = e.clientX - rect.left;
+            const clickY = e.clientY - rect.top;
+            
+            const gridPos = this.fromIsometric(clickX, clickY);
+            
+            // Only move if within grid bounds
+            if (gridPos.x >= 0 && gridPos.x < this.gridSize &&
+                gridPos.y >= 0 && gridPos.y < this.gridSize) {
+                
+                // Set target and calculate path
+                this.player.targetX = Math.round(gridPos.x);
+                this.player.targetY = Math.round(gridPos.y);
+                
+                // Calculate new path
+                this.player.path = this.findPath(
+                    Math.round(this.player.x),
+                    Math.round(this.player.y),
+                    this.player.targetX,
+                    this.player.targetY
+                );
+                
+                // Clear current waypoint to start new path
+                this.player.currentWaypoint = null;
+            }
+        });
+    }
+    
+    gameLoop() {
+        // Clear canvas
+        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
+        
+        // Draw game elements
+        this.drawGrid();
+        this.updateParticles();
+        this.drawParticles();
+        this.updatePlayer();
+        this.drawPlayer();
+        
+        // Continue game loop
+        requestAnimationFrame(() => this.gameLoop());
+    }
+
+    // Add new particle system methods
+    createDustParticles(x, y) {
+        const particleCount = 12; // Increased for more particles
+        for (let i = 0; i < particleCount; i++) {
+            // Randomize the angle slightly
+            const baseAngle = (Math.PI * 2 * i) / particleCount;
+            const randomAngle = baseAngle + (Math.random() - 0.5) * 0.5;
+            
+            // Random speed and size variations
+            const speed = 0.3 + Math.random() * 0.4;
+            const initialSize = (this.player.size * 0.15) + (Math.random() * this.player.size * 0.15);
+            
+            // Random grey color
+            const greyValue = 220 + Math.floor(Math.random() * 35);
+            const color = `rgb(${greyValue}, ${greyValue}, ${greyValue})`;
+            
+            this.particles.push({
+                x: x,
+                y: y,
+                dx: Math.cos(randomAngle) * speed,
+                dy: Math.sin(randomAngle) * speed,
+                life: 0.8 + Math.random() * 0.4, // Random initial life
+                size: initialSize,
+                color: color,
+                initialSize: initialSize,
+                rotationSpeed: (Math.random() - 0.5) * 0.2,
+                rotation: Math.random() * Math.PI * 2
+            });
+        }
+    }
+
+    updateParticles() {
+        for (let i = this.particles.length - 1; i >= 0; i--) {
+            const particle = this.particles[i];
+            
+            // Update position with slight gravity effect
+            particle.x += particle.dx;
+            particle.y += particle.dy;
+            particle.dy += 0.01; // Slight upward drift
+            
+            // Update rotation
+            particle.rotation += particle.rotationSpeed;
+            
+            // Non-linear fade out
+            particle.life -= 0.03;
+            particle.size = particle.initialSize * (particle.life * 1.5); // Grow slightly as they fade
+            
+            // Remove dead particles
+            if (particle.life <= 0) {
+                this.particles.splice(i, 1);
+            }
+        }
+    }
+
+    drawParticles() {
+        for (const particle of this.particles) {
+            const iso = this.toIsometric(particle.x, particle.y);
+            
+            this.ctx.save();
+            this.ctx.translate(iso.x + this.offsetX, iso.y + this.offsetY);
+            this.ctx.rotate(particle.rotation);
+            
+            // Draw a slightly irregular dust puff
+            this.ctx.beginPath();
+            const points = 5;
+            for (let i = 0; i < points * 2; i++) {
+                const angle = (i * Math.PI) / points;
+                const radius = particle.size * (i % 2 ? 0.7 : 1);
+                const x = Math.cos(angle) * radius;
+                const y = Math.sin(angle) * radius;
+                if (i === 0) this.ctx.moveTo(x, y);
+                else this.ctx.lineTo(x, y);
+            }
+            this.ctx.closePath();
+            
+            this.ctx.fillStyle = `rgba(${particle.color.slice(4, -1)}, ${particle.life * 0.5})`;
+            this.ctx.fill();
+            
+            this.ctx.restore();
+        }
+    }
+}
+
+// Start the game when the page loads
+window.onload = () => {
+    new IsometricGame();
+};
\ No newline at end of file