{"asset_map": {"spongebob_character": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/spongebob_character_c904293a-6fae-414d-b0a6-c4d41a334661.webp", "type": "image", "prompt": "Generate a top-down bird's eye view of SpongeBob SquarePants for a 2D game. Style: cartoon 2D art, bright colors, thick black outlines, cel-shaded. Full body illustration showing his iconic yellow square sponge body, big round eyes looking up, red tie, and brown square pants. High resolution, transparent background, standing illustration, front-view from above.", "description": "The playable SpongeBob character viewed from a top-down perspective.", "subtype": "character", "aspect_ratio": [617, 636], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "patrick_enemy": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/patrick_enemy_f2f7fd56-e34d-4375-ba2f-e7ad42ad3216.webp", "type": "image", "prompt": "Generate a top-down bird's eye view of Patrick Star as a game enemy. Style: cartoon 2D art, thick black outlines, cel-shaded, vibrant pink colors. Show his five-pointed starfish body with simple eyes and mouth visible from the top. High resolution, transparent background, standing illustration, front-view from above.", "description": "Patrick Star enemy character viewed from a top-down perspective.", "subtype": "character", "aspect_ratio": [700, 910], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "squidward_enemy": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/squidward_enemy_5104f2f9-e53a-4a79-864b-8eaf89778613.webp", "type": "image", "prompt": "Generate a top-down bird's eye view of Squidward Tentacles. Style: cartoon 2D art, teal/cyan colors, thick black outlines, cel-shaded. Show his long squid head, large nose, and grumpy eyes from above. High resolution, transparent background, standing illustration, front-view from above.", "description": "Squidward enemy character with a grumpy expression viewed from above.", "subtype": "character", "aspect_ratio": [739, 1369], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "sandy_enemy": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/sandy_enemy_cb41095b-1b66-4681-94dd-713818b7770c.webp", "type": "image", "prompt": "Generate a top-down bird's eye view of Sandy Cheeks. Style: cartoon 2D art, thick black outlines, cel-shaded. Show her white squirrel head inside a clear circular glass diving helmet, buck teeth visible. High resolution, transparent background, standing illustration, front-view from above.", "description": "Sandy Cheeks enemy character in her diving suit viewed from above.", "subtype": "character", "aspect_ratio": [903, 1360], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "hard_block_coral": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/hard_block_coral_af6b2957-b792-4620-8198-bb7301e332ce.webp", "type": "image", "prompt": "Generate a top-down view of a solid coral rock formation for a game block. Style: bright pink and orange coral, organic textures, thick black outlines, cartoon 2D art. Must look heavy, permanent, and indestructible. Square shape, clean edges, transparent background.", "description": "Indestructible coral wall tile for the game grid.", "subtype": "ground", "aspect_ratio": [850, 868], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "soft_block_crate": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/soft_block_crate_2b5bff2e-f9ab-4e8c-93a6-7f0ca667e273.webp", "type": "image", "prompt": "Generate a top-down view of a wooden crate for a game block. Style: brown wood planks, metal nails and hinges, thick black outlines, cartoon 2D art. Should look like it can be broken. Square shape, transparent background.", "description": "Destructible wooden crate tile that can be blown up.", "subtype": "object", "aspect_ratio": [882, 884], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "bubble_bomb": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/bubble_bomb_506f2df6-117b-41df-86f6-919a72d81b2b.webp", "type": "image", "prompt": "Generate a translucent blue bubble bomb for a game. Style: cartoon 2D art, glossy highlights, internal sparkles, thick black outlines. Spherical shape, high shine, iridescent surface, transparent background.", "description": "A translucent bubble that acts as the bomb in the game.", "subtype": "effect", "aspect_ratio": [859, 994], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "explosion_effect": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/explosion_effect_c98ca557-fdce-411b-ba6b-35ae6b8e1089.webp", "type": "image", "prompt": "Generate a vibrant explosion effect for a cartoon game. Style: bright yellow and orange energy burst, radiating blue bubbles and white sparkles, thick lines, dynamic motion. High resolution, transparent background.", "description": "A bright, playful explosion burst with bubbles.", "subtype": "effect", "aspect_ratio": [924, 1109], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "powerup_hamburger": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/powerup_hamburger_f12fc6f8-5658-4450-8ef0-3d347ef5fd94.webp", "type": "image", "prompt": "Generate a top-down view of a Krabby Patty hamburger. Style: golden bun with sesame seeds, green lettuce, thick black outlines, cartoon 2D art. Small glowing aura or sparkles to show it is a power-up. Transparent background.", "description": "Krabby Patty power-up item.", "subtype": "object", "aspect_ratio": [713, 675], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "powerup_jellyfish": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/powerup_jellyfish_4f3ced1b-1fa7-4f02-a471-46e7b7b74525.webp", "type": "image", "prompt": "Generate a top-down view of a pink jellyfish. Style: translucent pink body, flowing tentacles, thick black outlines, cartoon 2D art. Glow effect, transparent background.", "description": "Pink jellyfish power-up item.", "subtype": "object", "aspect_ratio": [684, 954], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "powerup_spatula": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/powerup_spatula_281a1f5e-9e89-4f79-bf54-c96745eed3f0.webp", "type": "image", "prompt": "Generate a top-down view of a shiny golden spatula. Style: bright metallic gold, high-contrast highlights, sparkles, thick black outlines, cartoon 2D art. Transparent background.", "description": "Golden spatula power-up item.", "subtype": "object", "aspect_ratio": [913, 1125], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "background_ocean_floor": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/background_ocean_floor_7a5fd646-47d7-4ab9-9bdf-3923892bf984.webp", "type": "image", "prompt": "Generate a fully tileable texture of a sandy ocean floor for a 2D game. Style: sandy beige tones, subtle shells and pebbles, soft muted colors, cartoon 2D art. Designed to repeat seamlessly in both horizontal and vertical directions. No visible seams, low contrast.", "description": "Tileable sand texture for the underwater arena floor.", "subtype": "background", "aspect_ratio": [1024, 1024], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "ui_button_play": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/ui_button_play_f4080461-1bd9-465b-943f-d9f6bf76258c.webp", "type": "image", "prompt": "Create a rounded rectangular game button with the word 'START' or a play icon. Style: bright yellow and blue, chunky design, thick black outlines, cartoon 3D pop effect. Transparent background.", "description": "Cartoon style play button for the main menu.", "subtype": "ui", "aspect_ratio": [1038, 602], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "ui_button_restart": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/ui_button_restart_caec6e88-c547-4c6d-a21c-a4ec00ad631d.webp", "type": "image", "prompt": "Create a rounded rectangular game button with the word 'RESTART' or a circular arrow icon. Style: bright green and orange, chunky design, thick black outlines, cartoon 3D pop effect. Transparent background.", "description": "Cartoon style restart button for the end screen.", "subtype": "ui", "aspect_ratio": [1332, 586], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "victory_spongebob": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/victory_spongebob_429f1e01-5415-4b78-8305-ceb3faec0514.webp", "type": "image", "prompt": "Generate a full body illustration of SpongeBob SquarePants celebrating. Pose: arms raised, huge smile, wide eyes, very happy. Style: cartoon 2D art, thick black outlines, vibrant colors. Include confetti or sparkles. Transparent background.", "description": "SpongeBob celebrating victory, used for win screen.", "subtype": "other", "aspect_ratio": [889, 912], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "defeat_spongebob": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/defeat_spongebob_80f3a2cd-883a-4f16-baca-a961b9d449df.webp", "type": "image", "prompt": "Generate a full body illustration of SpongeBob SquarePants looking sad and defeated. Pose: slumped shoulders, sad eyes, frowning. Style: cartoon 2D art, thick black outlines, vibrant but slightly muted colors. Transparent background.", "description": "SpongeBob looking sad, used for game over screen.", "subtype": "other", "aspect_ratio": [682, 910], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "sound_place_bomb": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/sound_place_bomb_42aca311-b0e7-4749-8f9a-f0ecbdec0aa7.mp3", "type": "audio", "prompt": "A whimsical underwater 'bloop' sound effect, soft bubble pop with a subtle magical sparkle and light resonance. Cartoon style, aquatic, short and satisfying, no harsh frequencies, high-quality 2D platformer aesthetic.", "description": "Playful bubble-popping sound when SpongeBob places a bomb.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 1.46, "loop": false, "animation": null}, "sound_explosion": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/sound_explosion_8afc1257-48e0-4026-8e03-bce31d686de6.mp3", "type": "audio", "prompt": "Cartoon explosion sound effect, aquatic themed. A mix of a large 'pop' and a watery 'whoosh' with multiple bubble burst layers. Impactful but playful, non-realistic, bright and energetic, suitable for a Bikini Bottom aesthetic.", "description": "Bubbly cartoon explosion for bomb detonations.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 1.33, "loop": false, "animation": null}, "sound_collect_powerup": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/sound_collect_powerup_296d95ce-c74f-4892-ba1b-85aeb83d88a7.mp3", "type": "audio", "prompt": "Bright ascending magical chime, 4-note melodic sequence ending in a clear 'ding'. Sparkling textures, glockenspiel and harp blend, rewarding and positive, classic 90s cartoon power-up sound.", "description": "Cheerful rewarding chime for picking up items.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 1.43, "loop": false, "animation": null}, "sound_player_death": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/sound_player_death_c214ed12-9677-46e7-877f-43b44d024036.mp3", "type": "audio", "prompt": "Comical cartoon defeat sound, descending 'wah-wah-wah-wah' trombone slides with a subtle 'boing' effect. Lighthearted, silly, aquatic echo, evocative of classic slapstick animation failure.", "description": "Silly defeat sound effect for when the player loses.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 2.07, "loop": false, "animation": null}, "sound_enemy_defeat": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/sound_enemy_defeat_35207f76-f872-4d14-8f4e-8aff12c90993.mp3", "type": "audio", "prompt": "Quick, punchy 'poof' sound effect with a watery bubble splash tail. High-pitched and rewarding, indicating a character disappearing in a cloud of bubbles. Clean, snappy, and satisfying.", "description": "Satisfying pop sound when an enemy is defeated.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 1.45, "loop": false, "animation": null}, "sound_victory": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/sound_victory_0497ae52-1688-4dc4-b2a4-9865d1a42a8f.mp3", "type": "audio", "prompt": "Upbeat 3-second victory fanfare, SpongeBob-inspired style. Fast ukulele strumming, bright bells, and a triumphant brass swell. Major key, joyful, celebratory, high-energy cartoon resolution.", "description": "Triumphant celebratory jingle for level completion.", "subtype": "music", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 4, "loop": false, "animation": null}, "music_gameplay": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/music_gameplay_02fcea9d-32b8-4290-aa74-aed5687098d1.mp3", "type": "audio", "prompt": "Loopable upbeat tropical background music, 130 BPM, C Major. Instrumentation: bouncy ukulele rhythm, steel drums, light woodblock percussion, and melodic glockenspiel. Playful, underwater 'Bikini Bottom' vibe, adventurous and quirky, consistent energy for a puzzle-action game. Instrumental only.", "description": "Upbeat, ukulele-driven background music for the main game levels.", "subtype": "music", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 30, "loop": true, "animation": null}}, "game_code": "<!DOCTYPE html>\n<html>\n<head>\n    <title>Bikini Bottom Blast - SpongeBob Bubble Bomber</title>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n    <link href=\"https://fonts.googleapis.com/css2?family=Fredoka:wght@400;600;700&family=Poppins:wght@400;600&display=swap\" rel=\"stylesheet\">\n    <style>\n        /* CSS Reset & Base Styles */\n        *, *::before, *::after {\n            box-sizing: border-box;\n        }\n\n        body, html {\n            margin: 0;\n            padding: 0;\n            width: 100%;\n            height: 100%;\n            overflow: hidden;\n            touch-action: none;\n            -ms-touch-action: none;\n            font-family: 'Poppins', sans-serif;\n        }\n\n        /* CSS Custom Properties */\n        :root {\n            --transition-fast: 150ms;\n            --transition-normal: 300ms;\n            --transition-slow: 500ms;\n            --ease-out: cubic-bezier(0.0, 0, 0.2, 1);\n            --ease-bounce: cubic-bezier(0.68, -0.6, 0.32, 1.6);\n            --space-xs: 8px;\n            --space-sm: 16px;\n            --space-md: 24px;\n            --space-lg: 32px;\n            --space-xl: 48px;\n        }\n\n        /* Main Game Container */\n        #game-world {\n            position: relative;\n            width: 100%;\n            height: 100%;\n            background: #87CEEB;\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            justify-content: center;\n        }\n\n        /* Game Canvas */\n        #game-canvas {\n            display: block;\n            image-rendering: crisp-edges;\n            image-rendering: pixelated;\n        }\n\n        /* HUD Overlay */\n        .hud-overlay {\n            position: absolute;\n            top: 0;\n            left: 0;\n            right: 0;\n            pointer-events: none;\n            padding: var(--space-sm);\n            z-index: 100;\n        }\n\n        .level-hud {\n            display: inline-flex;\n            align-items: center;\n            gap: var(--space-xs);\n            background: rgba(0, 0, 0, 0.6);\n            color: white;\n            font-family: 'Fredoka', sans-serif;\n            font-size: 26px;\n            font-weight: 700;\n            padding: 10px 16px;\n            border: 4px solid rgba(255, 215, 0, 0.9);\n            border-radius: var(--space-sm);\n            text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);\n            backdrop-filter: blur(8px);\n        }\n\n        .level-hud span {\n            color: #FFD700;\n        }\n\n        /* Power-up Display */\n        .powerup-display {\n            display: flex;\n            gap: var(--space-sm);\n            background: rgba(0, 0, 0, 0.6);\n            padding: var(--space-sm);\n            border-radius: var(--space-sm);\n            backdrop-filter: blur(8px);\n        }\n\n        .powerup-item {\n            display: flex;\n            align-items: center;\n            gap: var(--space-xs);\n            color: white;\n            font-size: 24px;\n            font-weight: 600;\n            text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);\n        }\n\n        .powerup-icon {\n            width: 32px;\n            height: 32px;\n            object-fit: contain;\n        }\n\n        /* Touch Controls */\n        .touch-controls {\n            position: absolute;\n            bottom: 0;\n            left: 0;\n            right: 0;\n            height: 320px;\n            pointer-events: none;\n            z-index: 200;\n        }\n\n        .joystick-container {\n            position: absolute;\n            left: 40px;\n            bottom: 120px;\n            width: 160px;\n            height: 160px;\n            pointer-events: auto;\n        }\n\n        .joystick-base {\n            position: absolute;\n            width: 100%;\n            height: 100%;\n            background: radial-gradient(circle, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.1));\n            border: 4px solid rgba(255, 255, 255, 0.5);\n            border-radius: 50%;\n            transition: opacity var(--transition-fast);\n        }\n\n        .joystick-knob {\n            position: absolute;\n            width: 80px;\n            height: 80px;\n            background: radial-gradient(circle, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.6));\n            border: 4px solid rgba(0, 0, 0, 0.3);\n            border-radius: 50%;\n            top: 50%;\n            left: 50%;\n            transform: translate(-50%, -50%);\n            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);\n        }\n\n        .bomb-button {\n            position: absolute;\n            right: 40px;\n            bottom: 120px;\n            width: 120px;\n            height: 120px;\n            background: linear-gradient(135deg, #FFD700, #FFA500);\n            border: 6px solid rgba(255, 255, 255, 0.8);\n            border-radius: 50%;\n            pointer-events: auto;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            font-size: 40px;\n            font-weight: 700;\n            color: white;\n            text-shadow: 3px 3px 6px rgba(0, 0, 0, 0.5);\n            font-family: 'Fredoka', sans-serif;\n            box-shadow: 0 8px 16px rgba(0, 0, 0, 0.4);\n            transition: transform var(--transition-fast), opacity var(--transition-fast);\n            user-select: none;\n        }\n\n        .bomb-button:active {\n            transform: scale(0.9);\n        }\n\n        .bomb-button.disabled {\n            opacity: 0.5;\n            background: linear-gradient(135deg, #888, #666);\n        }\n\n        /* Modal Overlays */\n        .modal-overlay {\n            position: absolute;\n            top: 0;\n            left: 0;\n            right: 0;\n            bottom: 0;\n            background: rgba(0, 0, 0, 0.8);\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            justify-content: center;\n            z-index: 300;\n            padding: var(--space-lg);\n        }\n\n        .modal-content {\n            background: linear-gradient(135deg, #4A90E2, #357ABD);\n            border: 8px solid #FFD700;\n            border-radius: var(--space-md);\n            padding: var(--space-xl);\n            text-align: center;\n            max-width: 500px;\n            box-shadow: 0 16px 32px rgba(0, 0, 0, 0.6);\n        }\n\n        .modal-title {\n            font-family: 'Fredoka', sans-serif;\n            font-size: 56px;\n            font-weight: 700;\n            color: #FFD700;\n            text-shadow: 4px 4px 8px rgba(0, 0, 0, 0.8);\n            margin: 0 0 var(--space-md) 0;\n        }\n\n        .modal-image {\n            width: 200px;\n            height: 200px;\n            object-fit: contain;\n            margin: var(--space-md) 0;\n            filter: drop-shadow(0 8px 16px rgba(0, 0, 0, 0.5));\n        }\n\n        .modal-text {\n            font-size: 28px;\n            color: white;\n            text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);\n            margin: var(--space-md) 0;\n        }\n\n        .modal-button {\n            background: linear-gradient(135deg, #FFD700, #FFA500);\n            border: 6px solid white;\n            border-radius: var(--space-md);\n            padding: var(--space-md) var(--space-xl);\n            font-family: 'Fredoka', sans-serif;\n            font-size: 32px;\n            font-weight: 700;\n            color: white;\n            text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);\n            cursor: pointer;\n            pointer-events: auto;\n            box-shadow: 0 8px 16px rgba(0, 0, 0, 0.4);\n            transition: transform var(--transition-fast);\n            user-select: none;\n            margin: var(--space-sm);\n        }\n\n        .modal-button:hover {\n            transform: scale(1.05);\n        }\n\n        .modal-button:active {\n            transform: scale(0.95);\n        }\n\n        /* Edit Mode Styles */\n        .edit-toolbar {\n            position: absolute;\n            top: var(--space-sm);\n            right: var(--space-sm);\n            display: flex;\n            gap: var(--space-sm);\n            z-index: 100;\n            pointer-events: auto;\n        }\n\n        .tool-button {\n            width: 80px;\n            height: 80px;\n            background: rgba(255, 255, 255, 0.9);\n            border: 4px solid #333;\n            border-radius: var(--space-sm);\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            cursor: pointer;\n            transition: transform var(--transition-fast), background var(--transition-fast);\n            font-size: 32px;\n        }\n\n        .tool-button:hover {\n            transform: scale(1.05);\n        }\n\n        .tool-button.active {\n            background: #FFD700;\n            border-color: #FFA500;\n        }\n\n        .edit-help {\n            position: absolute;\n            bottom: var(--space-sm);\n            left: 50%;\n            transform: translateX(-50%);\n            background: rgba(0, 0, 0, 0.8);\n            color: white;\n            padding: var(--space-sm) var(--space-md);\n            border-radius: var(--space-sm);\n            font-size: 20px;\n            text-align: center;\n            pointer-events: none;\n            backdrop-filter: blur(8px);\n        }\n\n        /* Hidden class */\n        .hidden {\n            display: none !important;\n        }\n\n        /* Animations */\n        @keyframes pulse {\n            0%, 100% { transform: scale(1); }\n            50% { transform: scale(1.1); }\n        }\n\n        @keyframes float {\n            0%, 100% { transform: translateY(0); }\n            50% { transform: translateY(-8px); }\n        }\n\n        .pulse {\n            animation: pulse 1s ease-in-out infinite;\n        }\n\n        @media (prefers-reduced-motion: reduce) {\n            .pulse, .float {\n                animation: none;\n            }\n        }\n    </style>\n</head>\n<body>\n    <div id=\"game-world\">\n        <canvas id=\"game-canvas\"></canvas>\n        \n        <!-- HUD Overlay -->\n        <div class=\"hud-overlay hidden\" id=\"hud-overlay\">\n            <div class=\"level-hud\" id=\"level-hud\">Level 1 / 5</div>\n        </div>\n\n        <!-- Touch Controls -->\n        <div class=\"touch-controls hidden\" id=\"touch-controls\">\n            <div class=\"joystick-container\" id=\"joystick-container\">\n                <div class=\"joystick-base\"></div>\n                <div class=\"joystick-knob\" id=\"joystick-knob\"></div>\n            </div>\n            <button class=\"bomb-button\" id=\"bomb-button\">\ud83d\udca3</button>\n        </div>\n\n        <!-- Edit Mode Toolbar -->\n        <div class=\"edit-toolbar hidden\" id=\"edit-toolbar\">\n            <button class=\"tool-button active\" data-tool=\"empty\" title=\"Empty\">\u2b1c</button>\n            <button class=\"tool-button\" data-tool=\"soft\" title=\"Soft Block\">\ud83d\udce6</button>\n            <button class=\"tool-button\" data-tool=\"hard\" title=\"Hard Block\">\ud83e\udea8</button>\n            <button class=\"tool-button\" data-tool=\"enemy\" title=\"Enemy\">\ud83d\udc7e</button>\n        </div>\n\n        <!-- Edit Mode Help -->\n        <div class=\"edit-help hidden\" id=\"edit-help\">\n            Tap tiles to edit \u2022 Drag SpongeBob to move\n        </div>\n\n        <!-- Victory Modal -->\n        <div class=\"modal-overlay\" id=\"splash-screen\">\n            <div class=\"modal-content\" style=\"background: none; border: none; box-shadow: none;\">\n                <h1 class=\"modal-title pulse\" style=\"font-size: 80px;\">Bikini Bottom Blast</h1>\n                <p class=\"modal-text\">SpongeBob Bubble Bomber</p>\n            </div>\n        </div>\n\n        <div class=\"modal-overlay hidden\" id=\"main-menu\">\n            <div class=\"modal-content\">\n                <h1 class=\"modal-title\">Bikini Bottom Blast</h1>\n                <img class=\"modal-image\" id=\"menu-image\" alt=\"Bikini Bottom\">\n                <p class=\"modal-text\" id=\"menu-level-text\">Clear 5 stages in Bikini Bottom!</p>\n                <button class=\"modal-button\" id=\"start-game-button\">Play</button>\n            </div>\n        </div>\n\n        \n        <div class=\"modal-overlay hidden\" id=\"victory-modal\">\n            <div class=\"modal-content\">\n                <h1 class=\"modal-title\" id=\"victory-title\">Victory!</h1>\n                <img class=\"modal-image\" id=\"victory-image\" alt=\"Victory\">\n                <p class=\"modal-text\" id=\"victory-text\">All enemies defeated!</p>\n                <button class=\"modal-button\" id=\"next-level-button\">Next Level</button>\n            </div>\n        </div>\n\n        <!-- Defeat Modal -->\n        <div class=\"modal-overlay hidden\" id=\"defeat-modal\">\n            <div class=\"modal-content\">\n                <h1 class=\"modal-title\">Game Over</h1>\n                <img class=\"modal-image\" id=\"defeat-image\" alt=\"Defeat\">\n                <p class=\"modal-text\">Try again!</p>\n                <button class=\"modal-button\" id=\"restart-defeat\">Restart</button>\n            </div>\n        </div>\n    </div>\n\n    <script>\n        /**\n         * SpongeBob Bubble Bomber - Bikini Bottom Blast\n         * \n         * A Bomberman-style game featuring SpongeBob and friends in Bikini Bottom.\n         * Player movement patch: strict four-direction, tile-centred movement.\n         * \n         * Core Mechanics:\n         * - Grid-based movement on 11x11 tiles\n         * - Place bubble bombs that explode in cross patterns after 3 seconds\n         * - Collect power-ups: Hamburger (range), Jellyfish (speed), Golden Spatula (bomb count)\n         * - Defeat all enemies to win\n         * - Avoid explosions and enemy contact\n         * \n         * Modes:\n         * - Play: Full gameplay with controls and mechanics\n         * - Edit: Level editor to customize layout and parameters\n         */\n\n        async function run(mode) {\n            const gameWorld = document.getElementById('game-world');\n            const canvas = document.getElementById('game-canvas');\n            const ctx = canvas.getContext('2d');\n\n            // Constants\n            const GRID_SIZE = 11;\n            const TILE_SIZE = 65;\n            const CANVAS_SIZE = GRID_SIZE * TILE_SIZE; // 715px\n            const BOMB_TIMER = 3000; // 3 seconds default\n            const EXPLOSION_DURATION = 500; // 0.5 seconds\n\n            // Initialize canvas\n            canvas.width = CANVAS_SIZE;\n            canvas.height = CANVAS_SIZE;\n\n            // Asset cache\n            const assets = {};\n            const audioContext = new (window.AudioContext || window.webkitAudioContext)();\n            const audioBuffers = {};\n\n            // Preload all assets\n            await preloadAssets();\n\n            async function preloadAssets() {\n                const assetIds = [\n                    'spongebob_character', 'patrick_enemy', 'squidward_enemy', 'sandy_enemy',\n                    'hard_block_coral', 'soft_block_crate', 'bubble_bomb', 'explosion_effect',\n                    'powerup_hamburger', 'powerup_jellyfish', 'powerup_spatula',\n                    'background_ocean_floor', 'victory_spongebob', 'defeat_spongebob',\n                    'sound_place_bomb', 'sound_explosion', 'sound_collect_powerup',\n                    'sound_player_death', 'sound_enemy_defeat', 'sound_victory', 'music_gameplay'\n                ];\n\n                const loadPromises = assetIds.map(async id => {\n                    const assetInfo = lib.getAsset(id);\n                    if (!assetInfo) return;\n\n                    if (assetInfo.type === 'image') {\n                        const img = new Image();\n                        img.src = assetInfo.url;\n                        await new Promise((resolve, reject) => {\n                            img.onload = resolve;\n                            img.onerror = reject;\n                        });\n                        assets[id] = { img, ...assetInfo };\n                    } else if (assetInfo.type === 'audio') {\n                        try {\n                            const response = await fetch(assetInfo.url);\n                            const arrayBuffer = await response.arrayBuffer();\n                            const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);\n                            audioBuffers[id] = audioBuffer;\n                        } catch (e) {\n                            lib.log(`Failed to load audio: ${id}`);\n                        }\n                    }\n                });\n\n                await Promise.all(loadPromises);\n\n                // Set UI icons\n                document.getElementById('victory-image').src = assets.victory_spongebob.img.src;\n                document.getElementById('defeat-image').src = assets.defeat_spongebob.img.src;\n                if (assets.victory_spongebob) {\n                    document.getElementById('menu-image').src = assets.victory_spongebob.img.src;\n                }\n            }\n\n            // Audio helper\n            function playSound(soundId, volume = 1.0) {\n                if (!audioBuffers[soundId]) return;\n                try {\n                    const source = audioContext.createBufferSource();\n                    const gainNode = audioContext.createGain();\n                    source.buffer = audioBuffers[soundId];\n                    gainNode.gain.value = volume;\n                    source.connect(gainNode);\n                    gainNode.connect(audioContext.destination);\n                    source.start(0);\n                } catch (e) {\n                    lib.log(`Failed to play sound: ${soundId}`);\n                }\n            }\n\n            // Background music\n            let musicSource = null;\n            function playBackgroundMusic() {\n                if (!audioBuffers.music_gameplay || musicSource) return;\n                try {\n                    musicSource = audioContext.createBufferSource();\n                    const gainNode = audioContext.createGain();\n                    musicSource.buffer = audioBuffers.music_gameplay;\n                    musicSource.loop = true;\n                    gainNode.gain.value = 0.3;\n                    musicSource.connect(gainNode);\n                    gainNode.connect(audioContext.destination);\n                    musicSource.start(0);\n                } catch (e) {\n                    lib.log('Failed to play background music');\n                }\n            }\n\n            function stopBackgroundMusic() {\n                if (musicSource) {\n                    try {\n                        musicSource.stop();\n                        musicSource = null;\n                    } catch (e) {}\n                }\n            }\n\n            // Initialize and normalize gameConfig.\n            // CONFIG_VERSION forces old cached/raw level arrays to be replaced by the playable level set below.\n            const CONFIG_VERSION = 'multi-level-playable-v2';\n            const defaultConfig = getDefaultConfig();\n            const oldConfig = window.gameConfig || {};\n            const hasInvalidOrOldLevels =\n                !Array.isArray(oldConfig.levels) ||\n                oldConfig.levels.length !== defaultConfig.levels.length ||\n                oldConfig.levels.some(level => Array.isArray(level) || !level || !Array.isArray(level.grid));\n\n            if (!window.gameConfig || oldConfig.configVersion !== CONFIG_VERSION || hasInvalidOrOldLevels) {\n                window.gameConfig = {\n                    ...defaultConfig,\n                    currentLevelIndex: Math.min(\n                        Number.isInteger(oldConfig.currentLevelIndex) ? oldConfig.currentLevelIndex : 0,\n                        defaultConfig.levels.length - 1\n                    ),\n                    bombTimer: oldConfig.bombTimer || defaultConfig.bombTimer,\n                    startingBombCapacity: oldConfig.startingBombCapacity || defaultConfig.startingBombCapacity,\n                    startingBombRange: oldConfig.startingBombRange || defaultConfig.startingBombRange,\n                    playerSpeed: oldConfig.playerSpeed || defaultConfig.playerSpeed,\n                    enemySpeed: oldConfig.enemySpeed || defaultConfig.enemySpeed\n                };\n            } else {\n                window.gameConfig.configVersion = CONFIG_VERSION;\n                window.gameConfig.currentLevelIndex = window.gameConfig.currentLevelIndex || 0;\n                window.gameConfig.bombTimer = window.gameConfig.bombTimer || defaultConfig.bombTimer;\n                window.gameConfig.startingBombCapacity = window.gameConfig.startingBombCapacity || defaultConfig.startingBombCapacity;\n                window.gameConfig.startingBombRange = window.gameConfig.startingBombRange || defaultConfig.startingBombRange;\n                window.gameConfig.playerSpeed = window.gameConfig.playerSpeed || defaultConfig.playerSpeed;\n                window.gameConfig.enemySpeed = window.gameConfig.enemySpeed || defaultConfig.enemySpeed;\n            }\n\n            function getDefaultConfig() {\n                return {\n                    configVersion: CONFIG_VERSION,\n                    // Tile meanings: 0 empty, 1 soft block, 2 hard block, 3 player, 4 enemy.\n                    levels: [\n                        {\n                            name: \"Level 1 \u00b7 Jellyfish Fields\",\n                            bombTimer: 3.4,\n                            enemySpeedMultiplier: 0.75,\n                            enemyAggression: 0.7,\n                            startingBombCapacity: 1,\n                            startingBombRange: 1,\n                            grid: [\n                                [2,2,2,2,2,2,2,2,2,2,2],\n                                [2,3,0,0,1,0,1,0,0,0,2],\n                                [2,0,2,0,2,0,2,0,2,0,2],\n                                [2,0,1,0,1,0,1,0,1,0,2],\n                                [2,1,2,0,2,1,2,0,2,1,2],\n                                [2,0,0,0,1,4,1,0,0,0,2],\n                                [2,1,2,0,2,1,2,0,2,1,2],\n                                [2,0,1,0,1,0,1,0,1,0,2],\n                                [2,0,2,0,2,0,2,0,2,0,2],\n                                [2,0,0,0,1,0,1,0,0,4,2],\n                                [2,2,2,2,2,2,2,2,2,2,2]\n                            ]\n                        },\n                        {\n                            name: \"Level 2 \u00b7 Krusty Krab Rush\",\n                            bombTimer: 3.2,\n                            enemySpeedMultiplier: 0.9,\n                            enemyAggression: 0.85,\n                            startingBombCapacity: 1,\n                            startingBombRange: 1,\n                            grid: [\n                                [2,2,2,2,2,2,2,2,2,2,2],\n                                [2,3,0,0,1,0,1,1,0,4,2],\n                                [2,0,2,0,2,0,2,1,2,0,2],\n                                [2,0,0,0,1,0,1,0,1,0,2],\n                                [2,1,2,0,2,1,2,0,2,1,2],\n                                [2,0,1,0,4,1,4,0,1,0,2],\n                                [2,1,2,0,2,1,2,0,2,1,2],\n                                [2,0,1,0,1,0,1,0,1,0,2],\n                                [2,0,2,1,2,0,2,1,2,0,2],\n                                [2,4,0,1,1,0,1,1,0,4,2],\n                                [2,2,2,2,2,2,2,2,2,2,2]\n                            ]\n                        },\n                        {\n                            name: \"Level 3 \u00b7 Goo Lagoon Maze\",\n                            bombTimer: 3.0,\n                            enemySpeedMultiplier: 1.05,\n                            enemyAggression: 1.0,\n                            startingBombCapacity: 1,\n                            startingBombRange: 2,\n                            grid: [\n                                [2,2,2,2,2,2,2,2,2,2,2],\n                                [2,3,0,0,4,0,1,0,1,0,2],\n                                [2,0,2,0,2,0,2,1,2,4,2],\n                                [2,0,0,0,1,0,1,0,1,0,2],\n                                [2,1,2,0,2,1,2,0,2,1,2],\n                                [2,0,0,0,4,1,4,0,0,0,2],\n                                [2,1,2,0,2,1,2,0,2,1,2],\n                                [2,0,1,0,1,0,1,0,1,0,2],\n                                [2,4,2,1,2,0,2,1,2,0,2],\n                                [2,0,1,0,1,0,4,0,1,4,2],\n                                [2,2,2,2,2,2,2,2,2,2,2]\n                            ]\n                        },\n                        {\n                            name: \"Level 4 \u00b7 Rock Bottom Trap\",\n                            bombTimer: 2.8,\n                            enemySpeedMultiplier: 1.2,\n                            enemyAggression: 1.15,\n                            startingBombCapacity: 2,\n                            startingBombRange: 2,\n                            grid: [\n                                [2,2,2,2,2,2,2,2,2,2,2],\n                                [2,3,0,0,4,1,0,1,4,0,2],\n                                [2,0,2,0,2,1,2,1,2,0,2],\n                                [2,0,0,0,1,0,1,0,1,1,2],\n                                [2,4,2,1,2,0,2,1,2,4,2],\n                                [2,0,1,0,4,1,4,0,1,0,2],\n                                [2,4,2,1,2,0,2,1,2,4,2],\n                                [2,1,1,0,1,0,1,0,1,1,2],\n                                [2,0,2,1,2,1,2,1,2,0,2],\n                                [2,4,0,1,0,1,4,1,0,4,2],\n                                [2,2,2,2,2,2,2,2,2,2,2]\n                            ]\n                        },\n                        {\n                            name: \"Level 5 \u00b7 Chum Bucket Finale\",\n                            bombTimer: 2.6,\n                            enemySpeedMultiplier: 1.35,\n                            enemyAggression: 1.35,\n                            startingBombCapacity: 2,\n                            startingBombRange: 2,\n                            enemyBombRange: 2,\n                            grid: [\n                                [2,2,2,2,2,2,2,2,2,2,2],\n                                [2,3,0,0,1,1,0,4,1,0,2],\n                                [2,0,2,0,2,1,2,1,2,4,2],\n                                [2,0,0,0,1,0,1,0,1,1,2],\n                                [2,0,2,1,2,4,2,1,2,0,2],\n                                [2,1,0,4,0,1,0,4,0,1,2],\n                                [2,0,2,1,2,4,2,1,2,0,2],\n                                [2,1,1,0,1,0,1,0,1,4,2],\n                                [2,4,2,1,2,1,2,1,2,1,2],\n                                [2,0,1,4,0,1,0,4,1,4,2],\n                                [2,2,2,2,2,2,2,2,2,2,2]\n                            ]\n                        }\n                    ],\n                    currentLevelIndex: 0,\n                    bombTimer: 3,\n                    startingBombCapacity: 1,\n                    startingBombRange: 1,\n                    playerSpeed: 1,\n                    enemySpeed: 1\n                };\n            }\n\n            function getLevelCount() {\n                return (window.gameConfig.levels || []).length;\n            }\n\n            function getLevelData(index) {\n                const levels = window.gameConfig.levels || [];\n                const safeIndex = Number.isInteger(index) ? index : (window.gameConfig.currentLevelIndex || 0);\n                const level = levels[safeIndex] || levels[0];\n                if (!level) return { name: \"Level 1\", grid: window.gameConfig.grid || [] };\n                // Backward compatible: old configs stored each level as a raw grid array.\n                if (Array.isArray(level)) {\n                    return { name: `Level ${safeIndex + 1}`, grid: level };\n                }\n                return level;\n            }\n\n            function getLevelGrid(index) {\n                return getLevelData(index).grid;\n            }\n\n            function getLevelName(index) {\n                return getLevelData(index).name || `Level ${index + 1}`;\n            }\n\n            function getLevelSetting(key, fallback, index) {\n                const level = getLevelData(index);\n                return level && level[key] !== undefined ? level[key] : fallback;\n            }\n\n            function getLevelBombTimer(index) {\n                return getLevelSetting('bombTimer', window.gameConfig.bombTimer || 3, index);\n            }\n\n            // Show game parameters\n            lib.showGameParameters({\n                name: \"Game Settings\",\n                params: {\n                    \"Level to Edit\": {\n                        key: 'currentLevelIndex',\n                        type: 'dropdown',\n                        options: window.gameConfig.levels.map((level, i) => ({ label: getLevelName(i), value: i })),\n                        onChange: (val) => {\n                            window.gameConfig.currentLevelIndex = val;\n                            currentLevelIndex = val;\n                            initializeEntities();\n                        }\n                    },\n                    \"Bomb Timer\": { key: 'bombTimer', type: 'slider', min: 2, max: 5, step: 0.5 },\n                    \"Starting Bombs\": { key: 'startingBombCapacity', type: 'slider', min: 1, max: 3, step: 1 },\n                    \"Starting Range\": { key: 'startingBombRange', type: 'slider', min: 1, max: 3, step: 1 },\n                    \"Player Speed\": { key: 'playerSpeed', type: 'slider', min: 0.5, max: 2, step: 0.1 },\n                    \"Enemy Speed\": { key: 'enemySpeed', type: 'slider', min: 0.5, max: 2, step: 0.1 }\n                }\n            });\n\n            // Game state\n            let player = null;\n            let enemies = [];\n            let bombs = [];\n            let explosions = [];\n            let blocks = [];\n            let powerups = [];\n            let gameOver = false;\n            let victory = false;\n            let lastTime = 0;\n            let currentLevelIndex = 0;\n            let gameStarted = false;\n\n            // Control state\n            const keys = {};\n            const directionInputOrder = [];\n            let joystickActive = false;\n            let joystickAngle = 0;\n            let joystickDistance = 0;\n            let joystickDirection = { x: 0, y: 0 };\n            const isTouchDevice = lib.shouldUseTouchControls();\n            let bombButton = null;\n\n            const CARDINAL_DIRECTIONS = {\n                up: { x: 0, y: -1 },\n                down: { x: 0, y: 1 },\n                left: { x: -1, y: 0 },\n                right: { x: 1, y: 0 }\n            };\n\n            function keyToDirectionName(key) {\n                if (key === 'w' || key === 'arrowup') return 'up';\n                if (key === 's' || key === 'arrowdown') return 'down';\n                if (key === 'a' || key === 'arrowleft') return 'left';\n                if (key === 'd' || key === 'arrowright') return 'right';\n                return null;\n            }\n\n            function isDirectionNameHeld(name) {\n                if (name === 'up') return !!(keys.w || keys.arrowup);\n                if (name === 'down') return !!(keys.s || keys.arrowdown);\n                if (name === 'left') return !!(keys.a || keys.arrowleft);\n                if (name === 'right') return !!(keys.d || keys.arrowright);\n                return false;\n            }\n\n            function getKeyboardDirection() {\n                for (let i = directionInputOrder.length - 1; i >= 0; i--) {\n                    const name = directionInputOrder[i];\n                    if (isDirectionNameHeld(name)) return CARDINAL_DIRECTIONS[name];\n                }\n                return { x: 0, y: 0 };\n            }\n\n            // Edit mode state\n            let editTool = 'empty';\n            let selectedTile = null;\n            let draggingPlayer = false;\n\n            // Initialize game based on mode\n            if (mode === 'edit') {\n                initEditMode();\n                hideModal('splash-screen');\n                hideModal('main-menu');\n            } else {\n                initPlayMode();\n            }\n\n            function initPlayMode() {\n                // Show splash then menu\n                showModal('splash-screen');\n                hideModal('main-menu');\n                \n                setTimeout(() => {\n                    hideModal('splash-screen');\n                    showModal('main-menu');\n                }, 2000);\n                \n                const menuLevelText = document.getElementById('menu-level-text');\n                if (menuLevelText) {\n                    menuLevelText.textContent = `Clear ${getLevelCount()} stages in Bikini Bottom!`;\n                }\n\n                // Show touch controls on mobile\n                if (isTouchDevice) {\n                    document.getElementById('touch-controls').classList.remove('hidden');\n                    setupTouchControls();\n                }\n                \n                // Setup keyboard controls\n                setupKeyboardControls();\n\n                // Initialize game entities\n                initializeEntities();\n\n                // Setup start button\n                document.getElementById('start-game-button').onclick = () => {\n                    hideModal('main-menu');\n                    document.getElementById('hud-overlay').classList.remove('hidden');\n                    gameStarted = true;\n                    updateHUD();\n                    playBackgroundMusic();\n                    // Audio context needs a user gesture to start\n                    if (audioContext.state === 'suspended') {\n                        audioContext.resume();\n                    }\n                };\n\n                // Setup restart/next buttons\n                document.getElementById('next-level-button').onclick = () => {\n                    hideModal('victory-modal');\n                    if (currentLevelIndex < getLevelCount() - 1) {\n                        currentLevelIndex++;\n                        window.gameConfig.currentLevelIndex = currentLevelIndex;\n                        resetGame();\n                    } else {\n                        // After the final level, start a new run from Level 1.\n                        currentLevelIndex = 0;\n                        window.gameConfig.currentLevelIndex = currentLevelIndex;\n                        resetGame();\n                    }\n                };\n                document.getElementById('restart-defeat').onclick = () => {\n                    hideModal('defeat-modal');\n                    resetGame();\n                };\n            }\n\n            function initEditMode() {\n                document.getElementById('edit-toolbar').classList.remove('hidden');\n                document.getElementById('edit-help').classList.remove('hidden');\n\n                // Setup edit tool buttons\n                document.querySelectorAll('.tool-button').forEach(btn => {\n                    btn.onclick = () => {\n                        document.querySelectorAll('.tool-button').forEach(b => b.classList.remove('active'));\n                        btn.classList.add('active');\n                        editTool = btn.dataset.tool;\n                    };\n                });\n\n                // Setup canvas click for tile editing\n                canvas.addEventListener('click', handleEditClick);\n                canvas.addEventListener('mousedown', handleEditMouseDown);\n                canvas.addEventListener('mousemove', handleEditMouseMove);\n                canvas.addEventListener('mouseup', handleEditMouseUp);\n                canvas.addEventListener('touchstart', handleEditTouchStart);\n                canvas.addEventListener('touchmove', handleEditTouchMove);\n                canvas.addEventListener('touchend', handleEditTouchEnd);\n\n                // Initialize entities for visualization\n                initializeEntities();\n            }\n\n            function handleEditClick(e) {\n                const rect = canvas.getBoundingClientRect();\n                const x = e.clientX - rect.left;\n                const y = e.clientY - rect.top;\n                const col = Math.floor(x / TILE_SIZE);\n                const row = Math.floor(y / TILE_SIZE);\n\n                if (col >= 0 && col < GRID_SIZE && row >= 0 && row < GRID_SIZE) {\n                    // Don't allow editing border\n                    if (row === 0 || row === GRID_SIZE - 1 || col === 0 || col === GRID_SIZE - 1) {\n                        return;\n                    }\n\n                    const grid = getLevelGrid(currentLevelIndex);\n                    if (!grid) return;\n                    if (editTool === 'empty') {\n                        grid[row][col] = 0;\n                    } else if (editTool === 'soft') {\n                        grid[row][col] = 1;\n                    } else if (editTool === 'hard') {\n                        grid[row][col] = 2;\n                    } else if (editTool === 'enemy') {\n                        // Remove any existing enemy at this position\n                        grid[row][col] = 4;\n                    }\n\n                    // Reinitialize entities to show changes\n                    initializeEntities();\n                }\n            }\n\n            function handleEditMouseDown(e) {\n                const rect = canvas.getBoundingClientRect();\n                const x = e.clientX - rect.left;\n                const y = e.clientY - rect.top;\n\n                // Check if clicking on player\n                if (player && Math.abs(x - player.x) < TILE_SIZE/2 && Math.abs(y - player.y) < TILE_SIZE/2) {\n                    draggingPlayer = true;\n                }\n            }\n\n            function handleEditMouseMove(e) {\n                if (draggingPlayer) {\n                    const rect = canvas.getBoundingClientRect();\n                    const x = e.clientX - rect.left;\n                    const y = e.clientY - rect.top;\n                    const col = Math.floor(x / TILE_SIZE);\n                    const row = Math.floor(y / TILE_SIZE);\n\n                    if (col > 0 && col < GRID_SIZE - 1 && row > 0 && row < GRID_SIZE - 1) {\n                        // Check if tile is empty\n                        const grid = getLevelGrid(currentLevelIndex);\n                        if (grid && grid[row][col] === 0) {\n                            // Update player spawn position\n                            const oldRow = Math.floor(player.y / TILE_SIZE);\n                            const oldCol = Math.floor(player.x / TILE_SIZE);\n                            if (grid[oldRow][oldCol] === 3) {\n                                grid[oldRow][oldCol] = 0;\n                            }\n                            grid[row][col] = 3;\n                            player.x = col * TILE_SIZE + TILE_SIZE / 2;\n                            player.y = row * TILE_SIZE + TILE_SIZE / 2;\n                        }\n                    }\n                }\n            }\n\n            function handleEditMouseUp(e) {\n                draggingPlayer = false;\n            }\n\n            function handleEditTouchStart(e) {\n                e.preventDefault();\n                const touch = e.touches[0];\n                const rect = canvas.getBoundingClientRect();\n                const x = touch.clientX - rect.left;\n                const y = touch.clientY - rect.top;\n\n                // Check if touching player\n                if (player && Math.abs(x - player.x) < TILE_SIZE/2 && Math.abs(y - player.y) < TILE_SIZE/2) {\n                    draggingPlayer = true;\n                } else {\n                    // Treat as click for tile editing\n                    const col = Math.floor(x / TILE_SIZE);\n                    const row = Math.floor(y / TILE_SIZE);\n                    if (col >= 0 && col < GRID_SIZE && row >= 0 && row < GRID_SIZE) {\n                        if (row > 0 && row < GRID_SIZE - 1 && col > 0 && col < GRID_SIZE - 1) {\n                            const grid = getLevelGrid(currentLevelIndex);\n                            if (grid) {\n                                if (editTool === 'empty') grid[row][col] = 0;\n                                else if (editTool === 'soft') grid[row][col] = 1;\n                                else if (editTool === 'hard') grid[row][col] = 2;\n                                else if (editTool === 'enemy') grid[row][col] = 4;\n                                initializeEntities();\n                            }\n                        }\n                    }\n                }\n            }\n\n            function handleEditTouchMove(e) {\n                e.preventDefault();\n                if (draggingPlayer) {\n                    const touch = e.touches[0];\n                    const rect = canvas.getBoundingClientRect();\n                    const x = touch.clientX - rect.left;\n                    const y = touch.clientY - rect.top;\n                    const col = Math.floor(x / TILE_SIZE);\n                    const row = Math.floor(y / TILE_SIZE);\n\n                    const grid = getLevelGrid(currentLevelIndex);\n                    if (grid && col > 0 && col < GRID_SIZE - 1 && row > 0 && row < GRID_SIZE - 1) {\n                        if (grid[row][col] === 0) {\n                            const oldRow = Math.floor(player.y / TILE_SIZE);\n                            const oldCol = Math.floor(player.x / TILE_SIZE);\n                            if (grid[oldRow][oldCol] === 3) {\n                                grid[oldRow][oldCol] = 0;\n                            }\n                            grid[row][col] = 3;\n                            player.x = col * TILE_SIZE + TILE_SIZE / 2;\n                            player.y = row * TILE_SIZE + TILE_SIZE / 2;\n                        }\n                    }\n                }\n            }\n\n            function handleEditTouchEnd(e) {\n                e.preventDefault();\n                draggingPlayer = false;\n            }\n\n            function initializeEntities() {\n                // Reset arrays\n                player = null;\n                enemies = [];\n                bombs = [];\n                explosions = [];\n                blocks = [];\n                powerups = [];\n                gameOver = false;\n                victory = false;\n\n                // Use the selected level in edit mode, and the runtime progress level in play mode.\n                if (mode !== 'play') {\n                   currentLevelIndex = window.gameConfig.currentLevelIndex || 0;\n                }\n                window.gameConfig.currentLevelIndex = currentLevelIndex;\n                updateHUD();\n                \n                const grid = getLevelGrid(currentLevelIndex);\n\n                if (!grid) {\n                    lib.log(\"Error: No grid found in levels\");\n                    return;\n                }\n\n                // Parse grid\n                for (let row = 0; row < GRID_SIZE; row++) {\n                    for (let col = 0; col < GRID_SIZE; col++) {\n                        const cellType = grid[row][col];\n                        const x = col * TILE_SIZE + TILE_SIZE / 2;\n                        const y = row * TILE_SIZE + TILE_SIZE / 2;\n\n                        if (cellType === 3) { // Player spawn\n                            player = {\n                                x, y,\n                                col, row,\n                                vx: 0, vy: 0,\n                                speed: 150 * window.gameConfig.playerSpeed * getLevelSetting('playerSpeedMultiplier', 1, currentLevelIndex),\n                                bombCapacity: getLevelSetting('startingBombCapacity', window.gameConfig.startingBombCapacity, currentLevelIndex),\n                                bombRange: getLevelSetting('startingBombRange', window.gameConfig.startingBombRange, currentLevelIndex),\n                                activeBombs: 0,\n                                // Grid-centred movement state. The player travels from one tile centre to the next.\n                                moveDirection: { x: 0, y: 0 },\n                                queuedDirection: { x: 0, y: 0 },\n                                moveOrigin: { row, col, x, y },\n                                moveTarget: null,\n                                passThroughBombKey: null,\n                                powerups: {\n                                    hamburger: 0,\n                                    jellyfish: 0,\n                                    spatula: 0\n                                }\n                            };\n                        } else if (cellType === 4) { // Enemy spawn\n                            const enemyTypes = ['patrick_enemy', 'squidward_enemy', 'sandy_enemy'];\n                            const type = enemyTypes[enemies.length % enemyTypes.length];\n                            enemies.push({\n                                x, y,\n                                col, row,\n                                vx: 0, vy: 0,\n                                speed: 100 * window.gameConfig.enemySpeed * getLevelSetting('enemySpeedMultiplier', 1, currentLevelIndex),\n                                type,\n                                nextMoveTime: 0,\n                                moveDirection: { x: 0, y: 0 },\n                                bombCapacity: getLevelSetting('enemyBombCapacity', 1, currentLevelIndex),\n                                activeBombs: 0,\n                                bombRange: getLevelSetting('enemyBombRange', 1, currentLevelIndex),\n                                aiAggression: getLevelSetting('enemyAggression', 1, currentLevelIndex),\n                                lastBombTime: 0\n                            });\n                        } else if (cellType === 2) { // Hard block\n                            blocks.push({ row, col, type: 'hard' });\n                        } else if (cellType === 1) { // Soft block\n                            blocks.push({ row, col, type: 'soft' });\n                        }\n                    }\n                }\n\n                // Update HUD\n                if (player) {\n                    updateHUD();\n                }\n            }\n\n            function resetGame() {\n                stopBackgroundMusic();\n                initializeEntities();\n                playBackgroundMusic();\n            }\n\n            function setupTouchControls() {\n                const joystickContainer = document.getElementById('joystick-container');\n                const joystickKnob = document.getElementById('joystick-knob');\n                bombButton = document.getElementById('bomb-button');\n\n                let joystickTouchId = null;\n                const maxRadius = 80;\n\n                joystickContainer.addEventListener('touchstart', (e) => {\n                    e.preventDefault();\n                    if (joystickTouchId === null) {\n                        const touch = e.changedTouches[0];\n                        joystickTouchId = touch.identifier;\n                        joystickActive = true;\n                        updateJoystick(touch);\n                    }\n                });\n\n                joystickContainer.addEventListener('touchmove', (e) => {\n                    e.preventDefault();\n                    for (let touch of e.changedTouches) {\n                        if (touch.identifier === joystickTouchId) {\n                            updateJoystick(touch);\n                            break;\n                        }\n                    }\n                });\n\n                function releaseJoystickTouch(e) {\n                    e.preventDefault();\n                    for (let touch of e.changedTouches) {\n                        if (touch.identifier === joystickTouchId) {\n                            joystickTouchId = null;\n                            joystickActive = false;\n                            joystickDistance = 0;\n                            joystickDirection = { x: 0, y: 0 };\n                            joystickKnob.style.transform = 'translate(-50%, -50%)';\n                            break;\n                        }\n                    }\n                }\n\n                joystickContainer.addEventListener('touchend', releaseJoystickTouch);\n                joystickContainer.addEventListener('touchcancel', releaseJoystickTouch);\n\n                function updateJoystick(touch) {\n                    const rect = joystickContainer.getBoundingClientRect();\n                    const centerX = rect.left + rect.width / 2;\n                    const centerY = rect.top + rect.height / 2;\n                    const dx = touch.clientX - centerX;\n                    const dy = touch.clientY - centerY;\n                    const distance = Math.sqrt(dx * dx + dy * dy);\n                    \n                    joystickDistance = Math.min(distance / maxRadius, 1);\n                    joystickAngle = Math.atan2(dy, dx);\n\n                    // Convert the analogue stick to one stable cardinal direction.\n                    if (joystickDistance < 0.28) {\n                        joystickDirection = { x: 0, y: 0 };\n                    } else {\n                        const absX = Math.abs(dx);\n                        const absY = Math.abs(dy);\n                        const switchBias = 1.12;\n\n                        if (joystickDirection.x !== 0 && absY <= absX * switchBias) {\n                            joystickDirection = { x: dx < 0 ? -1 : 1, y: 0 };\n                        } else if (joystickDirection.y !== 0 && absX <= absY * switchBias) {\n                            joystickDirection = { x: 0, y: dy < 0 ? -1 : 1 };\n                        } else if (absX >= absY) {\n                            joystickDirection = { x: dx < 0 ? -1 : 1, y: 0 };\n                        } else {\n                            joystickDirection = { x: 0, y: dy < 0 ? -1 : 1 };\n                        }\n                    }\n\n                    // Position knob visually without affecting the four-direction input.\n                    const knobX = Math.cos(joystickAngle) * Math.min(distance, maxRadius);\n                    const knobY = Math.sin(joystickAngle) * Math.min(distance, maxRadius);\n                    joystickKnob.style.transform = `translate(calc(-50% + ${knobX}px), calc(-50% + ${knobY}px))`;\n                }\n\n                bombButton.addEventListener('touchstart', (e) => {\n                    e.preventDefault();\n                    placeBomb();\n                });\n            }\n\n            function setupKeyboardControls() {\n                window.addEventListener('keydown', (e) => {\n                    const key = e.key.toLowerCase();\n                    if (['w', 'a', 's', 'd', 'arrowup', 'arrowdown', 'arrowleft', 'arrowright', ' '].includes(key)) {\n                        e.preventDefault();\n\n                        if (key === ' ') {\n                            if (!keys[key]) placeBomb();\n                            keys[key] = true;\n                            return;\n                        }\n\n                        const directionName = keyToDirectionName(key);\n                        if (!keys[key] && directionName) {\n                            const oldIndex = directionInputOrder.indexOf(directionName);\n                            if (oldIndex !== -1) directionInputOrder.splice(oldIndex, 1);\n                            directionInputOrder.push(directionName);\n                        }\n                        keys[key] = true;\n                    }\n                });\n\n                window.addEventListener('keyup', (e) => {\n                    const key = e.key.toLowerCase();\n                    keys[key] = false;\n\n                    const directionName = keyToDirectionName(key);\n                    if (directionName && !isDirectionNameHeld(directionName)) {\n                        const index = directionInputOrder.indexOf(directionName);\n                        if (index !== -1) directionInputOrder.splice(index, 1);\n                    }\n                });\n\n                window.addEventListener('blur', () => {\n                    Object.keys(keys).forEach(k => keys[k] = false);\n                    directionInputOrder.length = 0;\n                    joystickDirection = { x: 0, y: 0 };\n                });\n            }\n\n            function placeBomb(entity = player) {\n                if (!entity || gameOver || victory || mode === 'edit') return;\n\n                if (entity.activeBombs >= (entity.bombCapacity || 1)) {\n                    if (entity === player) {\n                        bombButton?.classList.add('disabled');\n                        setTimeout(() => bombButton?.classList.remove('disabled'), 200);\n                    }\n                    return;\n                }\n\n                const bombCol = Math.floor(entity.x / TILE_SIZE);\n                const bombRow = Math.floor(entity.y / TILE_SIZE);\n\n                // Check if bomb already exists at this position\n                if (bombs.some(b => b.col === bombCol && b.row === bombRow)) {\n                    return;\n                }\n\n                bombs.push({\n                    col: bombCol,\n                    row: bombRow,\n                    x: bombCol * TILE_SIZE + TILE_SIZE / 2,\n                    y: bombRow * TILE_SIZE + TILE_SIZE / 2,\n                    timer: getLevelBombTimer(currentLevelIndex) * 1000,\n                    range: entity.bombRange || 1,\n                    owner: entity\n                });\n\n                // Let the player finish leaving the tile where the bomb was placed.\n                if (entity === player) {\n                    entity.passThroughBombKey = `${bombRow},${bombCol}`;\n                }\n\n                entity.activeBombs++;\n                playSound('sound_place_bomb', 0.5);\n            }\n\n            function updateHUD() {\n                const hud = document.getElementById('level-hud');\n                if (!hud) return;\n                const levelName = getLevelName(currentLevelIndex);\n                hud.innerHTML = `<span>${levelName}</span> \u00b7 ${currentLevelIndex + 1}/${getLevelCount()}`;\n            }\n\n            function showModal(modalId) {\n                document.getElementById(modalId).classList.remove('hidden');\n            }\n\n            function hideModal(modalId) {\n                document.getElementById(modalId).classList.add('hidden');\n            }\n\n            // Game loop\n            function gameLoop(timestamp) {\n                const deltaTime = lastTime ? Math.min((timestamp - lastTime) / 1000, 0.11) : 0;\n                lastTime = timestamp;\n\n                // Clear canvas\n                ctx.clearRect(0, 0, CANVAS_SIZE, CANVAS_SIZE);\n\n                // Draw background\n                if (assets.background_ocean_floor) {\n                    const pattern = ctx.createPattern(assets.background_ocean_floor.img, 'repeat');\n                    ctx.fillStyle = pattern;\n                    ctx.fillRect(0, 0, CANVAS_SIZE, CANVAS_SIZE);\n                }\n\n                if (mode === 'edit') {\n                    // Edit mode rendering\n                    renderEditMode();\n                } else {\n                    // Play mode\n                    if (gameStarted && !gameOver && !victory) {\n                        updatePlayMode(deltaTime);\n                    }\n                    renderPlayMode();\n                }\n\n                requestAnimationFrame(gameLoop);\n            }\n\n            function updatePlayMode(deltaTime) {\n                // Update player movement\n                updatePlayerMovement(deltaTime);\n\n                // Update enemies\n                updateEnemies(deltaTime);\n\n                // Update bombs\n                updateBombs(deltaTime);\n\n                // Update explosions\n                updateExplosions(deltaTime);\n\n                // Check collisions\n                checkCollisions();\n\n                // Check win/lose conditions\n                checkGameState();\n            }\n\n            function directionsEqual(a, b) {\n                return !!a && !!b && a.x === b.x && a.y === b.y;\n            }\n\n            function isZeroDirection(direction) {\n                return !direction || (direction.x === 0 && direction.y === 0);\n            }\n\n            function getPlayerInputDirection() {\n                const keyboardDirection = getKeyboardDirection();\n                if (!isZeroDirection(keyboardDirection)) return keyboardDirection;\n                if (joystickActive && joystickDistance >= 0.28) return joystickDirection;\n                return { x: 0, y: 0 };\n            }\n\n            function getTileCenter(row, col) {\n                return {\n                    x: col * TILE_SIZE + TILE_SIZE / 2,\n                    y: row * TILE_SIZE + TILE_SIZE / 2\n                };\n            }\n\n            function isPlayerTileBlocked(row, col) {\n                if (row < 0 || row >= GRID_SIZE || col < 0 || col >= GRID_SIZE) return true;\n                if (blocks.some(block => block.row === row && block.col === col)) return true;\n\n                const bombOnTile = bombs.some(bomb => bomb.row === row && bomb.col === col);\n                if (bombOnTile && player.passThroughBombKey !== `${row},${col}`) return true;\n\n                return false;\n            }\n\n            function canPlayerMoveFrom(row, col, direction) {\n                if (isZeroDirection(direction)) return false;\n                return !isPlayerTileBlocked(row + direction.y, col + direction.x);\n            }\n\n            function startPlayerSegment(direction) {\n                if (!player || isZeroDirection(direction)) return false;\n                if (!canPlayerMoveFrom(player.row, player.col, direction)) return false;\n\n                const origin = getTileCenter(player.row, player.col);\n                const targetRow = player.row + direction.y;\n                const targetCol = player.col + direction.x;\n                const target = getTileCenter(targetRow, targetCol);\n\n                player.x = origin.x;\n                player.y = origin.y;\n                player.moveOrigin = {\n                    row: player.row,\n                    col: player.col,\n                    x: origin.x,\n                    y: origin.y\n                };\n                player.moveTarget = {\n                    row: targetRow,\n                    col: targetCol,\n                    x: target.x,\n                    y: target.y\n                };\n                player.moveDirection = { x: direction.x, y: direction.y };\n                return true;\n            }\n\n            function reversePlayerSegment() {\n                if (!player.moveTarget || !player.moveOrigin) return;\n\n                const oldTarget = player.moveTarget;\n                const oldOrigin = player.moveOrigin;\n                player.moveTarget = oldOrigin;\n                player.moveOrigin = oldTarget;\n                player.moveDirection = {\n                    x: -player.moveDirection.x,\n                    y: -player.moveDirection.y\n                };\n            }\n\n            function chooseDirectionAtPlayerCenter(inputDirection) {\n                if (!isZeroDirection(inputDirection)) {\n                    player.queuedDirection = { x: inputDirection.x, y: inputDirection.y };\n                }\n\n                // First try the most recently requested direction, so turns can be buffered.\n                if (!isZeroDirection(player.queuedDirection) &&\n                    canPlayerMoveFrom(player.row, player.col, player.queuedDirection)) {\n                    return { ...player.queuedDirection };\n                }\n\n                // When two keys are held, continue straight if the requested turn is blocked.\n                if (!isZeroDirection(player.moveDirection)) {\n                    const directionName = player.moveDirection.y < 0 ? 'up' :\n                        player.moveDirection.y > 0 ? 'down' :\n                        player.moveDirection.x < 0 ? 'left' : 'right';\n                    const joystickKeepsDirection = joystickActive && directionsEqual(joystickDirection, player.moveDirection);\n                    if ((isDirectionNameHeld(directionName) || joystickKeepsDirection) &&\n                        canPlayerMoveFrom(player.row, player.col, player.moveDirection)) {\n                        return { ...player.moveDirection };\n                    }\n                }\n\n                return { x: 0, y: 0 };\n            }\n\n            function updatePlayerMovement(deltaTime) {\n                if (!player) return;\n\n                const inputDirection = getPlayerInputDirection();\n                if (!isZeroDirection(inputDirection)) {\n                    player.queuedDirection = { x: inputDirection.x, y: inputDirection.y };\n                } else {\n                    // Releasing the controls makes the player stop at the next tile centre.\n                    player.queuedDirection = { x: 0, y: 0 };\n                }\n\n                // A direct reversal is allowed immediately, but the player remains on the same centre line.\n                if (player.moveTarget && !isZeroDirection(inputDirection) &&\n                    inputDirection.x === -player.moveDirection.x &&\n                    inputDirection.y === -player.moveDirection.y) {\n                    reversePlayerSegment();\n                    player.queuedDirection = { ...inputDirection };\n                }\n\n                const speedBonus = 1 + (player.powerups.jellyfish * 0.1);\n                let remainingDistance = player.speed * speedBonus * deltaTime;\n                let safety = 0;\n\n                while (remainingDistance > 0.0001 && safety++ < 6) {\n                    if (!player.moveTarget) {\n                        const nextDirection = chooseDirectionAtPlayerCenter(inputDirection);\n                        if (!startPlayerSegment(nextDirection)) {\n                            player.vx = 0;\n                            player.vy = 0;\n                            break;\n                        }\n                    }\n\n                    const dx = player.moveTarget.x - player.x;\n                    const dy = player.moveTarget.y - player.y;\n                    const distanceToTarget = Math.hypot(dx, dy);\n\n                    if (distanceToTarget <= remainingDistance + 0.0001) {\n                        player.x = player.moveTarget.x;\n                        player.y = player.moveTarget.y;\n                        player.row = player.moveTarget.row;\n                        player.col = player.moveTarget.col;\n                        remainingDistance -= distanceToTarget;\n                        player.moveTarget = null;\n                        player.moveOrigin = {\n                            row: player.row,\n                            col: player.col,\n                            x: player.x,\n                            y: player.y\n                        };\n\n                        if (player.passThroughBombKey &&\n                            player.passThroughBombKey !== `${player.row},${player.col}`) {\n                            player.passThroughBombKey = null;\n                        }\n\n                        const nextDirection = chooseDirectionAtPlayerCenter(inputDirection);\n                        if (!startPlayerSegment(nextDirection)) {\n                            player.vx = 0;\n                            player.vy = 0;\n                            break;\n                        }\n                    } else {\n                        const ratio = remainingDistance / distanceToTarget;\n                        player.x += dx * ratio;\n                        player.y += dy * ratio;\n                        remainingDistance = 0;\n                    }\n                }\n\n                // Lock the perpendicular coordinate to the exact tile centre to prevent wall hugging and jitter.\n                if (player.moveTarget) {\n                    if (player.moveDirection.x !== 0) {\n                        player.y = player.moveOrigin.y;\n                    } else if (player.moveDirection.y !== 0) {\n                        player.x = player.moveOrigin.x;\n                    }\n                    player.vx = player.moveDirection.x * player.speed * speedBonus;\n                    player.vy = player.moveDirection.y * player.speed * speedBonus;\n                } else {\n                    const center = getTileCenter(player.row, player.col);\n                    player.x = center.x;\n                    player.y = center.y;\n                    player.vx = 0;\n                    player.vy = 0;\n                }\n            }\n\n            function isBlocked(x, y, entity) {\n                const hitboxRadius = 22; // Half-width of collision volume\n                const corners = [\n                    { x: x - hitboxRadius, y: y - hitboxRadius },\n                    { x: x + hitboxRadius, y: y - hitboxRadius },\n                    { x: x - hitboxRadius, y: y + hitboxRadius },\n                    { x: x + hitboxRadius, y: y + hitboxRadius }\n                ];\n\n                for (const corner of corners) {\n                    const col = Math.floor(corner.x / TILE_SIZE);\n                    const row = Math.floor(corner.y / TILE_SIZE);\n\n                    // Check bounds\n                    if (row < 0 || row >= GRID_SIZE || col < 0 || col >= GRID_SIZE) return true;\n\n                    // Check blocks\n                    if (blocks.some(b => b.col === col && b.row === row)) {\n                        return true;\n                    }\n\n                    // Check bombs\n                    for (const bomb of bombs) {\n                        if (bomb.col === col && bomb.row === row) {\n                            // Standard Bomberman rule: if you're already overlapping the bomb's tile, it doesn't block you.\n                            // This prevents getting stuck when placing a bomb or moving off it.\n                            if (entity) {\n                                const entRad = 22;\n                                const bLeft = bomb.col * TILE_SIZE;\n                                const bRight = bLeft + TILE_SIZE;\n                                const bTop = bomb.row * TILE_SIZE;\n                                const bBottom = bTop + TILE_SIZE;\n                                \n                                if (entity.x + entRad > bLeft && entity.x - entRad < bRight &&\n                                    entity.y + entRad > bTop && entity.y - entRad < bBottom) {\n                                    continue;\n                                }\n                            }\n                            return true;\n                        }\n                    }\n                }\n\n                return false;\n            }\n\n            function getDangerTiles(extraBomb = null) {\n                const danger = new Set();\n                const allBombs = [...bombs];\n                if (extraBomb) allBombs.push(extraBomb);\n                \n                allBombs.forEach(bomb => {\n                    danger.add(`${bomb.row},${bomb.col}`);\n                    const directions = [{dr:-1, dc:0}, {dr:1, dc:0}, {dr:0, dc:-1}, {dr:0, dc:1}];\n                    directions.forEach(dir => {\n                        for (let i = 1; i <= bomb.range; i++) {\n                            const r = bomb.row + dir.dr * i;\n                            const c = bomb.col + dir.dc * i;\n                            if (r < 0 || r >= GRID_SIZE || c < 0 || c >= GRID_SIZE) break;\n                            if (blocks.some(b => b.row === r && b.col === c && b.type === 'hard')) break;\n                            danger.add(`${r},${c}`);\n                            if (blocks.some(b => b.row === r && b.col === c && b.type === 'soft')) break;\n                        }\n                    });\n                });\n                return danger;\n            }\n\n            function findEscapeRoute(startRow, startCol, dangerTiles) {\n                const queue = [{r: startRow, c: startCol, d: 0}];\n                const visited = new Set();\n                visited.add(`${startRow},${startCol}`);\n\n                while (queue.length > 0) {\n                    const {r, c, d} = queue.shift();\n                    if (!dangerTiles.has(`${r},${c}`)) return {r, c};\n                    if (d > 10) continue;\n\n                    const dirs = [{dr:-1, dc:0}, {dr:1, dc:0}, {dr:0, dc:-1}, {dr:0, dc:1}];\n                    for (const dir of dirs) {\n                        const nr = r + dir.dr;\n                        const nc = c + dir.dc;\n                        const key = `${nr},${nc}`;\n                        if (nr >= 0 && nr < GRID_SIZE && nc >= 0 && nc < GRID_SIZE && !visited.has(key)) {\n                            const isBlock = blocks.some(b => b.row === nr && b.col === nc);\n                            const isBomb = bombs.some(b => b.row === nr && b.col === nc);\n                            if (!isBlock && !isBomb) {\n                                visited.add(key);\n                                queue.push({r: nr, c: nc, d: d + 1});\n                            }\n                        }\n                    }\n                }\n                return null;\n            }\n\n            function moveTowards(entity, targetRow, targetCol, deltaTime, avoidDanger = false, dangerTiles = new Set()) {\n                const queue = [{r: entity.row, c: entity.col, path: []}];\n                const visited = new Set();\n                visited.add(`${entity.row},${entity.col}`);\n                \n                let bestPath = null;\n                while (queue.length > 0) {\n                    const {r, c, path} = queue.shift();\n                    if (r === targetRow && c === targetCol) {\n                        bestPath = path;\n                        break;\n                    }\n                    if (path.length > 15) continue;\n\n                    const dirs = [\n                        {dr:-1, dc:0, x:0, y:-1}, {dr:1, dc:0, x:0, y:1}, \n                        {dr:0, dc:-1, x:-1, y:0}, {dr:0, dc:1, x:1, y:0}\n                    ];\n                    dirs.sort(() => Math.random() - 0.5);\n\n                    for (const dir of dirs) {\n                        const nr = r + dir.dr;\n                        const nc = c + dir.dc;\n                        const key = `${nr},${nc}`;\n                        if (nr >= 0 && nr < GRID_SIZE && nc >= 0 && nc < GRID_SIZE && !visited.has(key)) {\n                            const isBlock = blocks.some(b => b.row === nr && b.col === nc);\n                            const isBomb = bombs.some(b => b.row === nr && b.col === nc);\n                            const isDangerous = avoidDanger && dangerTiles.has(key);\n                            \n                            if (!isBlock && !isBomb && !isDangerous) {\n                                visited.add(key);\n                                queue.push({r: nr, c: nc, path: [...path, dir]});\n                            }\n                        }\n                    }\n                }\n\n                if (bestPath && bestPath.length > 0) {\n                    entity.moveDirection = { x: bestPath[0].x, y: bestPath[0].y };\n                } else {\n                    if (Math.random() < 0.1) {\n                        const dirs = [{x:-1, y:0}, {x:1, y:0}, {x:0, y:-1}, {x:0, y:1}, {x:0, y:0}];\n                        entity.moveDirection = dirs[Math.floor(Math.random() * dirs.length)];\n                    }\n                }\n            }\n\n            function updateEnemies(deltaTime) {\n                const currentTime = Date.now();\n                const dangerTiles = getDangerTiles();\n\n                enemies.forEach(enemy => {\n                    const inDanger = dangerTiles.has(`${enemy.row},${enemy.col}`);\n\n                    if (inDanger) {\n                        const escapeTarget = findEscapeRoute(enemy.row, enemy.col, dangerTiles);\n                        if (escapeTarget) {\n                            moveTowards(enemy, escapeTarget.r, escapeTarget.c, deltaTime, false);\n                        } else if (Math.random() < 0.1) {\n                            const dirs = [{x:-1, y:0}, {x:1, y:0}, {x:0, y:-1}, {x:0, y:1}];\n                            enemy.moveDirection = dirs[Math.floor(Math.random() * dirs.length)];\n                        }\n                    } else {\n                        let shouldPlaceBomb = false;\n                        const distToPlayer = player ? Math.sqrt(Math.pow(enemy.row - player.row, 2) + Math.pow(enemy.col - player.col, 2)) : 1000;\n                        const inLineWithPlayer = player && (enemy.row === player.row || enemy.col === player.col) && distToPlayer <= (enemy.bombRange + 2);\n                        \n                        if (inLineWithPlayer && Math.random() < 0.05 * (enemy.aiAggression || 1)) shouldPlaceBomb = true;\n                        \n                        if (!shouldPlaceBomb) {\n                            const nearSoftBlock = [{dr:0,dc:1},{dr:0,dc:-1},{dr:1,dc:0},{dr:-1,dc:0}].some(d => \n                                blocks.some(b => b.type === 'soft' && b.row === enemy.row + d.dr && b.col === enemy.col + d.dc)\n                            );\n                            if (nearSoftBlock && Math.random() < 0.02 * (enemy.aiAggression || 1)) shouldPlaceBomb = true;\n                        }\n\n                        if (shouldPlaceBomb && enemy.activeBombs < (enemy.bombCapacity || 1) && currentTime > (enemy.lastBombTime || 0) + 4000) {\n                            const futureDanger = getDangerTiles({ row: enemy.row, col: enemy.col, range: enemy.bombRange || 1 });\n                            if (findEscapeRoute(enemy.row, enemy.col, futureDanger)) {\n                                placeBomb(enemy);\n                                enemy.lastBombTime = currentTime;\n                            }\n                        }\n\n                        if (player && !inDanger) {\n                            moveTowards(enemy, player.row, player.col, deltaTime, true, dangerTiles);\n                        }\n                    }\n\n                    enemy.vx = enemy.moveDirection.x * enemy.speed;\n                    enemy.vy = enemy.moveDirection.y * enemy.speed;\n\n                    const newX = enemy.x + enemy.vx * deltaTime;\n                    const newY = enemy.y + enemy.vy * deltaTime;\n\n                    if (!isBlocked(newX, enemy.y, enemy)) enemy.x = newX;\n                    if (!isBlocked(enemy.x, newY, enemy)) enemy.y = newY;\n\n                    enemy.x = Math.max(TILE_SIZE / 2, Math.min(CANVAS_SIZE - TILE_SIZE / 2, enemy.x));\n                    enemy.y = Math.max(TILE_SIZE / 2, Math.min(CANVAS_SIZE - TILE_SIZE / 2, enemy.y));\n\n                    enemy.col = Math.floor(enemy.x / TILE_SIZE);\n                    enemy.row = Math.floor(enemy.y / TILE_SIZE);\n                });\n            }\n\n            function updateBombs(deltaTime) {\n                for (let i = bombs.length - 1; i >= 0; i--) {\n                    const bomb = bombs[i];\n                    bomb.timer -= deltaTime * 1000;\n\n                    if (bomb.timer <= 0) {\n                        explodeBomb(bomb);\n                        bombs.splice(i, 1);\n                    }\n                }\n            }\n\n            function explodeBomb(bomb) {\n                playSound('sound_explosion', 0.6);\n\n                if (bomb.owner) {\n                    bomb.owner.activeBombs = Math.max(0, bomb.owner.activeBombs - 1);\n                }\n\n                // Create explosion at bomb position\n                const explosionTiles = [{ row: bomb.row, col: bomb.col }];\n\n                // Expand in 4 directions\n                const directions = [\n                    { dr: -1, dc: 0 }, // up\n                    { dr: 1, dc: 0 },  // down\n                    { dr: 0, dc: -1 }, // left\n                    { dr: 0, dc: 1 }   // right\n                ];\n\n                directions.forEach(dir => {\n                    for (let i = 1; i <= bomb.range; i++) {\n                        const row = bomb.row + dir.dr * i;\n                        const col = bomb.col + dir.dc * i;\n\n                        if (row < 0 || row >= GRID_SIZE || col < 0 || col >= GRID_SIZE) break;\n\n                        // Check for hard blocks (stop explosion)\n                        const hardBlock = blocks.find(b => b.row === row && b.col === col && b.type === 'hard');\n                        if (hardBlock) break;\n\n                        explosionTiles.push({ row, col });\n\n                        // Check for soft blocks (stop explosion and destroy)\n                        const softBlockIndex = blocks.findIndex(b => b.row === row && b.col === col && b.type === 'soft');\n                        if (softBlockIndex !== -1) {\n                            blocks.splice(softBlockIndex, 1);\n                            break;\n                        }\n\n                        // Check for other bombs (chain reaction)\n                        const chainBombIndex = bombs.findIndex(b => b.row === row && b.col === col);\n                        if (chainBombIndex !== -1) {\n                            const chainBomb = bombs[chainBombIndex];\n                            bombs.splice(chainBombIndex, 1);\n                            explodeBomb(chainBomb);\n                        }\n                    }\n                });\n\n                // Add explosions\n                explosionTiles.forEach(tile => {\n                    explosions.push({\n                        row: tile.row,\n                        col: tile.col,\n                        x: tile.col * TILE_SIZE + TILE_SIZE / 2,\n                        y: tile.row * TILE_SIZE + TILE_SIZE / 2,\n                        timer: EXPLOSION_DURATION\n                    });\n                });\n            }\n\n            function spawnPowerup(row, col) {\n                // Logic removed as per user request\n            }\n\n            function updateExplosions(deltaTime) {\n                for (let i = explosions.length - 1; i >= 0; i--) {\n                    explosions[i].timer -= deltaTime * 1000;\n                    if (explosions[i].timer <= 0) {\n                        explosions.splice(i, 1);\n                    }\n                }\n            }\n\n            function checkCollisions() {\n                if (!player || gameOver) return;\n\n                const playerSize = 20; // Smaller hitbox for damage to be fair\n\n                // Check player vs explosions\n                explosions.forEach(exp => {\n                    const expLeft = exp.col * TILE_SIZE;\n                    const expTop = exp.row * TILE_SIZE;\n                    const expRight = expLeft + TILE_SIZE;\n                    const expBottom = expTop + TILE_SIZE;\n\n                    if (player.x + playerSize > expLeft && player.x - playerSize < expRight &&\n                        player.y + playerSize > expTop && player.y - playerSize < expBottom) {\n                        gameOver = true;\n                        playSound('sound_player_death');\n                        stopBackgroundMusic();\n                        setTimeout(() => showModal('defeat-modal'), 500);\n                    }\n                });\n\n                // Check enemies vs explosions\n                for (let i = enemies.length - 1; i >= 0; i--) {\n                    const enemy = enemies[i];\n                    const enemySize = 20;\n                    if (explosions.some(exp => {\n                        const expLeft = exp.col * TILE_SIZE;\n                        const expTop = exp.row * TILE_SIZE;\n                        const expRight = expLeft + TILE_SIZE;\n                        const expBottom = expTop + TILE_SIZE;\n                        return enemy.x + enemySize > expLeft && enemy.x - enemySize < expRight &&\n                               enemy.y + enemySize > expTop && enemy.y - enemySize < expBottom;\n                    })) {\n                        enemies.splice(i, 1);\n                        playSound('sound_enemy_defeat', 0.5);\n                    }\n                }\n\n                // Check player vs enemies (touching enemies causes death)\n                enemies.forEach(enemy => {\n                    const dx = player.x - enemy.x;\n                    const dy = player.y - enemy.y;\n                    const distance = Math.sqrt(dx * dx + dy * dy);\n                    const collisionDist = 45; // Combined hit radii\n\n                    if (distance < collisionDist) {\n                        gameOver = true;\n                        playSound('sound_player_death');\n                        stopBackgroundMusic();\n                        setTimeout(() => showModal('defeat-modal'), 500);\n                    }\n                });\n            }\n\n            function collectPowerup(powerup) {\n                // Logic removed as per user request\n            }\n\n            function checkGameState() {\n                if (gameOver) return;\n\n                // Check victory condition\n                if (enemies.length === 0 && !victory) {\n                    victory = true;\n                    playSound('sound_victory');\n                    stopBackgroundMusic();\n\n                    const nextBtn = document.getElementById('next-level-button');\n                    const vicTitle = document.getElementById('victory-title');\n                    const vicText = document.getElementById('victory-text');\n\n                    if (currentLevelIndex < getLevelCount() - 1) {\n                        vicTitle.textContent = \"Level Clear!\";\n                        vicText.textContent = `You completed ${getLevelName(currentLevelIndex)}!`;\n                        nextBtn.textContent = \"Next Level\";\n                    } else {\n                        vicTitle.textContent = \"Ultimate Victory!\";\n                        vicText.textContent = \"You are the bubble champion of Bikini Bottom!\";\n                        nextBtn.textContent = \"Play Again\";\n                    }\n\n                    setTimeout(() => showModal('victory-modal'), 500);\n                }\n            }\n\n            function renderPlayMode() {\n                // Draw blocks\n                blocks.forEach(block => {\n                    const x = block.col * TILE_SIZE;\n                    const y = block.row * TILE_SIZE;\n                    const assetKey = block.type === 'hard' ? 'hard_block_coral' : 'soft_block_crate';\n                    if (assets[assetKey]) {\n                        drawImageCover(ctx, assets[assetKey].img, x, y, TILE_SIZE, TILE_SIZE);\n                    }\n                });\n\n                // Draw power-ups\n                powerups.forEach(powerup => {\n                    const assetKey = `powerup_${powerup.type}`;\n                    if (assets[assetKey]) {\n                        const size = 35;\n                        drawImageContain(ctx, assets[assetKey].img, powerup.x - size/2, powerup.y - size/2, size, size);\n                    }\n                });\n\n                // Draw bombs\n                bombs.forEach(bomb => {\n                    if (assets.bubble_bomb) {\n                        const size = 40;\n                        const pulseFactor = 1 + (1 - bomb.timer / (getLevelBombTimer(currentLevelIndex) * 1000)) * 0.2;\n                        const finalSize = size * pulseFactor;\n                        drawImageContain(ctx, assets.bubble_bomb.img, bomb.x - finalSize/2, bomb.y - finalSize/2, finalSize, finalSize);\n                    }\n                });\n\n                // Draw explosions\n                explosions.forEach(exp => {\n                    if (assets.explosion_effect) {\n                        const size = 80;\n                        const alpha = exp.timer / EXPLOSION_DURATION;\n                        ctx.save();\n                        ctx.globalAlpha = alpha;\n                        drawImageContain(ctx, assets.explosion_effect.img, exp.x - size/2, exp.y - size/2, size, size);\n                        ctx.restore();\n                    }\n                });\n\n                // Draw enemies\n                enemies.forEach(enemy => {\n                    if (assets[enemy.type]) {\n                        const size = 45;\n                        drawImageContain(ctx, assets[enemy.type].img, enemy.x - size/2, enemy.y - size/2, size, size);\n                    }\n                });\n\n                // Draw player\n                if (player && assets.spongebob_character) {\n                    const size = 50;\n                    drawImageContain(ctx, assets.spongebob_character.img, player.x - size/2, player.y - size/2, size, size);\n                }\n            }\n\n            function renderEditMode() {\n                // Draw grid lines\n                ctx.strokeStyle = 'rgba(100, 200, 255, 0.3)';\n                ctx.lineWidth = 1;\n                for (let i = 0; i <= GRID_SIZE; i++) {\n                    ctx.beginPath();\n                    ctx.moveTo(i * TILE_SIZE, 0);\n                    ctx.lineTo(i * TILE_SIZE, CANVAS_SIZE);\n                    ctx.stroke();\n                    ctx.beginPath();\n                    ctx.moveTo(0, i * TILE_SIZE);\n                    ctx.lineTo(CANVAS_SIZE, i * TILE_SIZE);\n                    ctx.stroke();\n                }\n\n                // Draw blocks\n                blocks.forEach(block => {\n                    const x = block.col * TILE_SIZE;\n                    const y = block.row * TILE_SIZE;\n                    const assetKey = block.type === 'hard' ? 'hard_block_coral' : 'soft_block_crate';\n                    if (assets[assetKey]) {\n                        drawImageCover(ctx, assets[assetKey].img, x, y, TILE_SIZE, TILE_SIZE);\n                    }\n                });\n\n                // Draw enemies\n                enemies.forEach(enemy => {\n                    if (assets[enemy.type]) {\n                        const size = 45;\n                        drawImageContain(ctx, assets[enemy.type].img, enemy.x - size/2, enemy.y - size/2, size, size);\n                    }\n                });\n\n                // Draw player\n                if (player && assets.spongebob_character) {\n                    const size = 50;\n                    drawImageContain(ctx, assets.spongebob_character.img, player.x - size/2, player.y - size/2, size, size);\n                }\n            }\n\n            function drawImageCover(ctx, img, x, y, width, height) {\n                const imgAspect = img.width / img.height;\n                const boxAspect = width / height;\n                let drawWidth, drawHeight, offsetX, offsetY;\n\n                if (imgAspect > boxAspect) {\n                    drawHeight = height;\n                    drawWidth = height * imgAspect;\n                    offsetX = -(drawWidth - width) / 2;\n                    offsetY = 0;\n                } else {\n                    drawWidth = width;\n                    drawHeight = width / imgAspect;\n                    offsetX = 0;\n                    offsetY = -(drawHeight - height) / 2;\n                }\n\n                ctx.save();\n                ctx.beginPath();\n                ctx.rect(x, y, width, height);\n                ctx.clip();\n                ctx.drawImage(img, x + offsetX, y + offsetY, drawWidth, drawHeight);\n                ctx.restore();\n            }\n\n            function drawImageContain(ctx, img, x, y, width, height) {\n                const imgAspect = img.width / img.height;\n                const boxAspect = width / height;\n                let drawWidth, drawHeight, offsetX, offsetY;\n\n                if (imgAspect > boxAspect) {\n                    drawWidth = width;\n                    drawHeight = width / imgAspect;\n                    offsetX = 0;\n                    offsetY = (height - drawHeight) / 2;\n                } else {\n                    drawHeight = height;\n                    drawWidth = height * imgAspect;\n                    offsetX = (width - drawWidth) / 2;\n                    offsetY = 0;\n                }\n\n                ctx.drawImage(img, x + offsetX, y + offsetY, drawWidth, drawHeight);\n            }\n\n            // Start game loop\n            requestAnimationFrame(gameLoop);\n        }\n    </script>\n</body>\n</html>", "game_config": "{\n  \"grid\": [\n    [\n      2,\n      2,\n      2,\n      2,\n      2,\n      2,\n      2,\n      2,\n      2,\n      2,\n      2\n    ],\n    [\n      2,\n      3,\n      0,\n      1,\n      0,\n      1,\n      0,\n      1,\n      0,\n      0,\n      2\n    ],\n    [\n      2,\n      0,\n      2,\n      1,\n      2,\n      1,\n      2,\n      1,\n      2,\n      0,\n      2\n    ],\n    [\n      2,\n      1,\n      1,\n      0,\n      1,\n      0,\n      1,\n      0,\n      1,\n      1,\n      2\n    ],\n    [\n      2,\n      0,\n      2,\n      1,\n      2,\n      1,\n      2,\n      1,\n      2,\n      0,\n      2\n    ],\n    [\n      2,\n      1,\n      0,\n      1,\n      0,\n      4,\n      0,\n      1,\n      0,\n      1,\n      2\n    ],\n    [\n      2,\n      0,\n      2,\n      1,\n      2,\n      1,\n      2,\n      1,\n      2,\n      0,\n      2\n    ],\n    [\n      2,\n      1,\n      1,\n      0,\n      1,\n      0,\n      1,\n      0,\n      1,\n      1,\n      2\n    ],\n    [\n      2,\n      0,\n      2,\n      1,\n      2,\n      4,\n      2,\n      1,\n      2,\n      0,\n      2\n    ],\n    [\n      2,\n      0,\n      0,\n      1,\n      0,\n      1,\n      0,\n      1,\n      0,\n      4,\n      2\n    ],\n    [\n      2,\n      2,\n      2,\n      2,\n      2,\n      2,\n      2,\n      2,\n      2,\n      2,\n      2\n    ]\n  ],\n  \"bombTimer\": 3,\n  \"startingBombCapacity\": 1,\n  \"startingBombRange\": 1,\n  \"playerSpeed\": 1,\n  \"enemySpeed\": 1,\n  \"powerupDropRate\": 0.3,\n  \"powerupMaxLevels\": {\n    \"hamburger\": 5,\n    \"jellyfish\": 5,\n    \"spatula\": 3\n  },\n  \"enabledPowerups\": {\n    \"hamburger\": true,\n    \"jellyfish\": true,\n    \"spatula\": true\n  },\n  \"levels\": [\n    [\n      [\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2\n      ],\n      [\n        2,\n        3,\n        0,\n        1,\n        0,\n        1,\n        0,\n        1,\n        0,\n        0,\n        2\n      ],\n      [\n        2,\n        0,\n        2,\n        1,\n        2,\n        1,\n        2,\n        1,\n        2,\n        0,\n        2\n      ],\n      [\n        2,\n        1,\n        1,\n        0,\n        1,\n        0,\n        1,\n        0,\n        1,\n        1,\n        2\n      ],\n      [\n        2,\n        0,\n        2,\n        1,\n        2,\n        1,\n        2,\n        1,\n        2,\n        0,\n        2\n      ],\n      [\n        2,\n        1,\n        0,\n        1,\n        0,\n        4,\n        0,\n        1,\n        0,\n        1,\n        2\n      ],\n      [\n        2,\n        0,\n        2,\n        1,\n        2,\n        1,\n        2,\n        1,\n        2,\n        0,\n        2\n      ],\n      [\n        2,\n        1,\n        1,\n        0,\n        1,\n        0,\n        1,\n        0,\n        1,\n        1,\n        2\n      ],\n      [\n        2,\n        0,\n        2,\n        1,\n        2,\n        4,\n        2,\n        1,\n        2,\n        0,\n        2\n      ],\n      [\n        2,\n        0,\n        0,\n        1,\n        0,\n        1,\n        0,\n        1,\n        0,\n        4,\n        2\n      ],\n      [\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2\n      ]\n    ],\n    [\n      [\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2\n      ],\n      [\n        2,\n        3,\n        1,\n        1,\n        1,\n        0,\n        1,\n        1,\n        1,\n        0,\n        2\n      ],\n      [\n        2,\n        1,\n        2,\n        1,\n        2,\n        0,\n        2,\n        1,\n        2,\n        1,\n        2\n      ],\n      [\n        2,\n        0,\n        1,\n        4,\n        1,\n        1,\n        1,\n        4,\n        1,\n        0,\n        2\n      ],\n      [\n        2,\n        1,\n        2,\n        1,\n        2,\n        1,\n        2,\n        1,\n        2,\n        1,\n        2\n      ],\n      [\n        2,\n        0,\n        1,\n        1,\n        4,\n        1,\n        4,\n        1,\n        1,\n        0,\n        2\n      ],\n      [\n        2,\n        1,\n        2,\n        1,\n        2,\n        1,\n        2,\n        1,\n        2,\n        1,\n        2\n      ],\n      [\n        2,\n        0,\n        1,\n        4,\n        1,\n        1,\n        1,\n        4,\n        1,\n        0,\n        2\n      ],\n      [\n        2,\n        1,\n        2,\n        1,\n        2,\n        0,\n        2,\n        1,\n        2,\n        1,\n        2\n      ],\n      [\n        2,\n        0,\n        1,\n        1,\n        1,\n        0,\n        1,\n        1,\n        1,\n        4,\n        2\n      ],\n      [\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2\n      ]\n    ],\n    [\n      [\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2\n      ],\n      [\n        2,\n        3,\n        4,\n        1,\n        0,\n        4,\n        0,\n        1,\n        4,\n        0,\n        2\n      ],\n      [\n        2,\n        4,\n        2,\n        1,\n        2,\n        1,\n        2,\n        1,\n        2,\n        4,\n        2\n      ],\n      [\n        2,\n        1,\n        1,\n        0,\n        1,\n        0,\n        1,\n        0,\n        1,\n        1,\n        2\n      ],\n      [\n        2,\n        0,\n        2,\n        1,\n        2,\n        1,\n        2,\n        1,\n        2,\n        0,\n        2\n      ],\n      [\n        2,\n        4,\n        1,\n        0,\n        4,\n        1,\n        4,\n        0,\n        1,\n        4,\n        2\n      ],\n      [\n        2,\n        0,\n        2,\n        1,\n        2,\n        1,\n        2,\n        1,\n        2,\n        0,\n        2\n      ],\n      [\n        2,\n        1,\n        1,\n        0,\n        1,\n        0,\n        1,\n        0,\n        1,\n        1,\n        2\n      ],\n      [\n        2,\n        4,\n        2,\n        1,\n        2,\n        1,\n        2,\n        1,\n        2,\n        4,\n        2\n      ],\n      [\n        2,\n        0,\n        4,\n        1,\n        0,\n        4,\n        0,\n        1,\n        4,\n        4,\n        2\n      ],\n      [\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2,\n        2\n      ]\n    ]\n  ]\n}", "game_design_document": "## Core Game Loop and Mechanics\n\n**Main Gameplay Loop:**\n1. Player (SpongeBob) spawns in a grid-based arena with destructible blocks and enemies\n2. Player navigates the arena using movement controls\n3. Player places bubble bombs that explode after 3 seconds in a cross pattern\n4. Explosions destroy blocks, defeat enemies, and reveal power-ups\n5. Player collects power-ups to enhance abilities (bomb range, speed, bomb capacity)\n6. Player must avoid their own bomb explosions and enemy contact\n7. Level is complete when all enemies are defeated\n8. Player dies if hit by explosion or enemy, resulting in level restart\n\n**Core Mechanics:**\n- **Grid-based Movement:** Characters move on a tile-based grid (each tile ~80x80 pixels)\n- **Bubble Bomb Placement:** Player places bombs that remain stationary and explode after 3 seconds\n- **Cross Explosion Pattern:** Bombs explode in 4 directions (up, down, left, right) with configurable range\n- **Chain Reactions:** Explosions can trigger other bombs to explode immediately\n- **Destructible Environment:** Soft blocks can be destroyed by explosions, revealing power-ups\n- **Power-up System:** Three types of collectible upgrades that persist through the level\n- **Enemy AI:** Enemies patrol or chase the player with simple pathfinding\n\n**Win Condition:**\n- Defeat all enemies in the level\n\n**Lose Condition:**\n- Player is hit by a bomb explosion (their own or chain reaction)\n- Player collides with an enemy\n\n## Starting Scope\n\n**First Implementation (Minimal Viable Game):**\n1. **Single Level:** One arena with a simple layout\n   - 11x11 grid (fits well in 720px width with ~65px tiles)\n   - Mix of indestructible walls (hard blocks) and destructible blocks (soft blocks)\n   - 3-4 enemies with basic AI (random movement or simple chase)\n   \n2. **Core Mechanics Only:**\n   - Player movement (8-directional on grid)\n   - Bomb placement (starting with 1 bomb capacity)\n   - Bomb explosion after 3 seconds\n   - Cross-pattern explosion (starting with 1-tile range)\n   - Player death from explosion or enemy contact\n   - Enemy death from explosion\n   - Basic collision detection\n\n3. **Essential Power-ups:**\n   - Hamburger (\u589e\u52a0\u7206\u70b8\u8303\u56f4 +1, max 5 levels)\n   - Jellyfish (\u589e\u52a0\u79fb\u52a8\u901f\u5ea6 +10%, max 5 levels)\n   - Golden Spatula (\u589e\u52a0\u70b8\u5f39\u6570\u91cf +1, max 3 bombs)\n\n4. **Basic UI:**\n   - Current power-up levels display\n   - Victory/defeat screens\n   - Restart button\n\n5. **Visual Style:**\n   - Cartoon/colorful art style matching SpongeBob theme\n   - Clear visual distinction between player, enemies, blocks, and bombs\n   - Simple explosion animation\n\n**Future Enhancements (Post-Launch):**\n- Multiple levels with increasing difficulty\n- More enemy types with varied AI behaviors\n- Additional characters (Patrick, Squidward, Sandy, Mr. Krabs) as playable options\n- More power-up types\n- Level timer for score bonuses\n- Leaderboard/high score system\n- Special themed arenas (Krusty Krab, Jellyfish Fields, etc.)\n\n## Game Perspective\n\n**Top-down (Bird's Eye View)**\n- Classic Bomberman-style overhead perspective\n- Grid-based layout clearly visible\n- All game elements (player, enemies, blocks, bombs) viewed from directly above\n- No camera movement - fixed view of entire arena\n\n## Game World and Level Design\n\n**Arena Structure:**\n- **Grid Size:** 11 columns \u00d7 11 rows (total 121 tiles)\n- **Tile Size:** ~65x65 pixels (fits 715px width, leaving margins)\n- **Total Play Area:** ~715px \u00d7 715px (centered in 720\u00d71280 viewport)\n\n**Level 1 Layout:**\n\n```\nH = Hard Block (indestructible wall)\nS = Soft Block (destructible, may contain power-ups)\nE = Enemy spawn point\nP = Player spawn point\n. = Empty walkable space\n\nH H H H H H H H H H H\nH P . S . S . S . . H\nH . H S H S H S H . H\nH S S . S . S . S S H\nH . H S H S H S H . H\nH S . S . E . S . S H\nH . H S H S H S H . H\nH S S . S . S . S S H\nH . H S H E H S H . H\nH . . S . S . S . E H\nH H H H H H H H H H H\n```\n\n**Block Distribution:**\n- **Hard Blocks (H):** Form the outer border and create a maze-like pattern inside (immovable obstacles)\n- **Soft Blocks (S):** ~30-40 destructible blocks scattered throughout\n- **Empty Spaces (.):** Provide initial movement paths\n- **Player Start (P):** Top-left safe zone (no blocks immediately adjacent)\n- **Enemy Spawns (E):** 3 enemies placed in different areas of the map\n\n**Power-up Distribution:**\n- 30% of soft blocks contain power-ups when destroyed\n- Power-up distribution:\n  - 40% Hamburger (\u7206\u70b8\u8303\u56f4)\n  - 40% Jellyfish (\u79fb\u52a8\u901f\u5ea6)\n  - 20% Golden Spatula (\u70b8\u5f39\u6570\u91cf)\n\n**Arena Theme:**\n- Bikini Bottom underwater aesthetic\n- Sandy ocean floor texture as background\n- Coral and rock formations as hard blocks\n- Wooden crates or treasure chests as soft blocks\n- Bubble effects for bombs and explosions\n\n## Edit Mode Design\n\n**Editable Properties:**\n\n1. **Level Layout (Grid Editor):**\n   - Click/tap tiles to cycle through: Empty \u2192 Soft Block \u2192 Hard Block \u2192 Enemy Spawn\n   - Player spawn position (drag SpongeBob icon)\n   - Visual grid overlay for easy editing\n   - Constraints:\n     - Minimum 2 enemies, maximum 6 enemies\n     - Player spawn must be in an empty tile\n     - Border must remain hard blocks (enforced)\n     - At least 20% of inner tiles must be empty for playability\n\n2. **Game Parameters:**\n   - **Bomb Timer:** 2-5 seconds (default: 3s)\n   - **Starting Bomb Capacity:** 1-3 bombs (default: 1)\n   - **Starting Bomb Range:** 1-3 tiles (default: 1)\n   - **Player Speed:** 0.5x - 2x multiplier (default: 1x)\n   - **Enemy Speed:** 0.5x - 2x multiplier (default: 1x)\n   - **Power-up Drop Rate:** 10% - 50% (default: 30%)\n\n3. **Power-up Settings:**\n   - Enable/disable each power-up type\n   - Adjust max levels for each power-up (1-10)\n   - Adjust drop rate distribution percentages\n\n4. **Visual Customization:**\n   - Choose background theme (Bikini Bottom, Jellyfish Fields, Krusty Krab)\n   - Toggle explosion effects intensity\n\n**Edit Mode UI:**\n- Top toolbar with editing tools\n- Side panel for parameter sliders\n- \"Test Level\" button to quickly play-test changes\n- \"Reset to Default\" button\n- Clear visual feedback for selected tiles\n\n**Constraints:**\n- Total grid size fixed at 11\u00d711 (changing this would break game balance)\n- Bomb explosion must remain cross-pattern (core mechanic)\n- Power-up effects must remain within reasonable ranges to prevent broken gameplay\n\n## Control Design\n\n**Mobile (Touch):**\n- **Left Side - Virtual Joystick (Movement):**\n  - Appears where player touches on left half of screen (below y=640px)\n  - 8-directional movement (up, down, left, right, and diagonals)\n  - Analog input converted to grid-based movement\n  - Dead zone: 15% of max radius\n  - Max radius: 80px\n  - Base repositioning: If finger moves beyond max radius, slide base to keep knob at max radius under finger\n  \n- **Right Side - Bubble Button (Place Bomb):**\n  - Large circular button (120\u00d7120px) positioned in bottom-right corner\n  - Tap to place bomb at player's current grid position\n  - Visual feedback: Button scales down slightly on press, disabled state when at bomb capacity\n  - Position: 40px from right edge, 120px from bottom\n\n**Desktop (Keyboard + Mouse):**\n- **Movement:**\n  - WASD keys for 4-directional movement (W=up, A=left, S=down, D=right)\n  - Arrow keys also supported for 4-directional movement\n  - Diagonal movement by pressing two adjacent keys simultaneously\n  \n- **Place Bomb:**\n  - Spacebar to place bomb at current position\n  - Left mouse click also places bomb (for accessibility)\n\n**Input Handling:**\n- Prevent default browser behavior for used keys (WASD, arrows, space)\n- Clear all input states on window blur\n- Convert keycodes to lowercase before storing state\n- Track touch IDs separately to prevent interference between joystick and button\n\n**Movement Behavior:**\n- Grid-based movement with smooth interpolation between tiles\n- Player can only change direction when aligned with grid\n- Movement speed affected by Jellyfish power-ups (base speed + 10% per level)\n- Cannot move through hard blocks, soft blocks, or bombs\n- Can move through power-ups (auto-collect on contact)\n\n**Bomb Placement Behavior:**\n- Can only place bomb on current tile if no bomb already exists there\n- Bomb placement limited by current capacity (starts at 1, increases with Golden Spatula)\n- Bomb capacity replenishes when a bomb explodes\n- Visual/audio feedback when bomb is placed\n- Visual/audio feedback when trying to place bomb at capacity (error state)\n\n## Art Style and Visual Theme\n\n**Overall Art Style:**\n- **Cartoon/Stylized 2D Art** inspired by the SpongeBob SquarePants TV show\n- Bright, vibrant colors with high saturation\n- Thick black outlines around characters and objects (cel-shaded look)\n- Playful, exaggerated proportions\n- Underwater theme with bubble effects throughout\n\n**Color Palette:**\n- **Primary Colors:** Bright yellows (SpongeBob), pinks (Patrick), ocean blues and teals\n- **Background:** Sandy beige/tan ocean floor with subtle texture\n- **Blocks:** Coral pinks, rock grays, wooden browns\n- **UI Elements:** Bright, high-contrast colors for readability\n\n**Character Design:**\n- **SpongeBob (Player):** Iconic yellow square sponge with big eyes, red tie, brown pants\n  - Size: ~50\u00d750px (fits within 65px tile with padding)\n  - Animated idle state (slight bounce)\n  - Simple walk cycle when moving\n  \n- **Enemies:** Simplified versions of SpongeBob characters\n  - **Patrick:** Pink starfish, simple design\n  - **Squidward:** Teal/cyan squid with grumpy expression\n  - **Sandy:** White squirrel in diving suit\n  - All enemies ~45\u00d745px for clear distinction from player\n\n**Environment Assets:**\n- **Hard Blocks:** Coral formations or large rocks with pink/orange hues, immovable appearance\n- **Soft Blocks:** Wooden crates or small treasure chests, clearly destructible\n- **Background:** Sandy ocean floor texture with subtle seaweed, shells, and pebbles\n- **Grid Lines:** Subtle light blue lines to help players see tile boundaries (can be toggled)\n\n**Game Objects:**\n- **Bubble Bomb:** Large translucent bubble with sparkles, ~40\u00d740px\n  - Pulsing animation as it counts down\n  - Color shifts from blue \u2192 yellow \u2192 red as explosion approaches\n  \n- **Explosion:** Bright yellow/orange burst effect\n  - Cross-shaped pattern extending from bomb center\n  - Particle effects (bubbles, sparkles) radiating outward\n  - Duration: ~0.5 seconds\n\n**Power-ups:**\n- **Hamburger (Krabby Patty):** Iconic burger with sesame bun, ~35\u00d735px\n- **Jellyfish:** Pink jellyfish with trailing tentacles, ~35\u00d735px, gentle floating animation\n- **Golden Spatula:** Shiny gold spatula, ~35\u00d735px, rotating slowly\n\n**UI Elements:**\n- **Power-up Display:** Top-left corner showing current levels\n  - Icon + number for each power-up type\n  - Glowing effect when newly collected\n  \n- **Victory/Defeat Screens:** Full-screen overlays with character reactions\n  - Victory: SpongeBob celebrating with confetti\n  - Defeat: SpongeBob looking sad/defeated\n  \n- **Buttons:** Rounded rectangles with thick borders, cartoon-style\n  - Bright colors (yellow, blue, green)\n  - Hover/press states with scale and color changes\n\n**Animation Principles:**\n- Smooth, bouncy animations (ease-in-out curves)\n- Exaggerated movements for comedic effect\n- Constant subtle motion (idle animations, floating effects)\n- Quick, snappy feedback for player actions\n\n**Technical Rendering:**\n- **Generated Assets:** All characters, power-ups, and complex objects\n- **CSS/Canvas Rendering:** Grid lines, simple UI elements, solid color backgrounds\n- **Consistent Scale:** All assets designed relative to 65\u00d765px tile size\n- **Transparent Backgrounds:** All character and object assets use transparency for clean compositing", "persistent_storage_config": null, "game_metadata": {"game_id": "01KV7WB5QN0V3G4Y4SFRZ31PE3", "title": "bikini bottom blast", "description": " \u626e\u6f14\u6d77\u7ef5\u5b9d\u5b9d\uff0c\u7528\u6ce1\u6ce1\u70b8\u5f39\u6d88\u706d\u6bd4\u5947\u5821\u7684\u654c\u4eba\uff01\u6536\u96c6\u9053\u5177\u63d0\u5347\u80fd\u529b\uff0c\u6210\u4e3a\u6ce1\u6ce1\u5927\u5e08\uff01", "thumbnail_url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/thumbnail_7da45eba-738f-4886-af83-3f25299fd2b5.webp", "created_at": 1781602338, "updated_at": 1781768623061.6133, "art_style": null}, "task_plan": null}