{"asset_map": {"player_character": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/player_character_3a48d29b-3b11-47a5-99bb-f97d3d2d72c0.webp", "type": "image", "prompt": "Generate a full-body standing illustration of a friendly, rounded mascot character for a game. Style: modern flat design, subtle gradients, rounded and approachable. Color: warm orange and yellow (#F39C12). Features: simple circular body, large expressive eyes, small smile. View: top-down with a slight 3/4 angle. High resolution, professional game asset, transparent background.", "description": "A friendly orange rounded mascot character for the player, viewed from a top-down 3/4 perspective.", "subtype": "character", "aspect_ratio": [759, 1078], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "locked_door": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/locked_door_30472e18-35f7-446d-9c6f-5e676cf57946.webp", "type": "image", "prompt": "Generate a top-down 3/4 perspective view of a locked door tile for a maze game. Style: modern flat design, clean edges, subtle gradients. Color: bright blue (#3498DB). Elements: a clear door frame with a prominent simple padlock icon in the center. Professional UI game asset, transparent background.", "description": "A bright blue locked door tile with a padlock icon, viewed from a top-down perspective.", "subtype": "object", "aspect_ratio": [463, 867], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "unlocked_door": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/unlocked_door_023135e2-b873-44b1-b668-f532a1aaaf75.webp", "type": "image", "prompt": "Generate a top-down 3/4 perspective view of an open door tile. Style: modern flat design, matches 'locked_door'. Color: light gray (#ECF0F1) with subtle blue accents. Elements: door frame shown as swung open, no lock icon, clear depth indicating a passable opening. Professional UI game asset, transparent background.", "description": "An open door tile in light gray, matching the style of the locked door but without the lock.", "subtype": "object", "aspect_ratio": [666, 1237], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "entrance_tile": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/entrance_tile_0312d2b0-a954-4b29-bdfe-267525712c62.webp", "type": "image", "prompt": "Generate a top-down flat view of an entrance floor tile. Style: vibrant flat design, rounded corners. Color: vibrant green (#2ECC71). Elements: a subtle upward arrow icon in the center, soft green glow/halo effect radiating from the tile. Professional game asset, transparent background.", "description": "A vibrant green starting tile with an upward arrow and a soft glow effect.", "subtype": "ground", "aspect_ratio": [723, 696], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "exit_tile": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/exit_tile_78635e2a-9258-4c2e-9a51-9d6e8fc3a6a8.webp", "type": "image", "prompt": "Generate a top-down flat view of an exit goal tile. Style: vibrant flat design, rounded corners. Color: vibrant red (#E74C3C). Elements: a subtle star or flag icon in the center, soft red glow/halo effect radiating from the tile. Professional game asset, transparent background.", "description": "A vibrant red goal tile with a star icon and victory glow effect.", "subtype": "ground", "aspect_ratio": [725, 730], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "correct_answer_sfx": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/correct_answer_sfx_d956e0e0-86c9-4b20-b582-61ae22812fa2.mp3", "type": "audio", "prompt": "A bright, high-pitched musical chime for a correct answer. Major key, ascending two-note motif, sparkling textures like a glockenspiel or a clean synth bell. Positive, rewarding, and high-quality for a polished educational game UI. Duration of 2 seconds with a clean decay.", "description": "A cheerful major-key chime for correctly solving a math problem.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 1.47, "loop": false, "animation": null}, "incorrect_answer_sfx": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/incorrect_answer_sfx_4b2caf0e-3c2d-4a88-b32c-5d285b93529f.mp3", "type": "audio", "prompt": "A gentle, low-frequency synth 'boop' sound. Short and informative, neutral tone, soft attack. Designed to indicate a mistake in a math game without being harsh or discouraging. Minimalist and clean with a 2-second tail.", "description": "A soft, non-punishing neutral 'boop' for an incorrect answer.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 1.68, "loop": false, "animation": null}, "door_unlock_sfx": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/door_unlock_sfx_4751ff3e-e417-4ec8-8fdc-14c9e7751603.mp3", "type": "audio", "prompt": "A mechanical lock 'click' sound immediately followed by a brief, shimmering magical chime. High-quality foley of a latch opening with a soft, airy synth sparkle. Satisfying feedback for progression in a puzzle game.", "description": "Mechanical click followed by a magical shimmer when a maze door opens.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 1.58, "loop": false, "animation": null}, "level_complete_sfx": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/level_complete_sfx_6033a863-134e-48e9-bb03-5411267214ed.mp3", "type": "audio", "prompt": "A celebratory short victory fanfare for a puzzle game. Upbeat 4-note ascending melody in a major key using bright bells, energetic synth plucks, and a light shimmer. Triumphant, high-energy, and rewarding.", "description": "A triumphant victory fanfare jingle for completing a maze.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 1.8, "loop": false, "animation": null}, "button_click_sfx": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/button_click_sfx_71ae8c2f-6d97-4bf1-9d62-260ed2c587fc.mp3", "type": "audio", "prompt": "Short, snappy digital UI click sound. High-frequency, clean, and responsive. Suitable for frequent menu navigation and button presses in a modern flat-design game. No echo, very tight decay, padded to 2 seconds.", "description": "A crisp, modern UI click for menu and answer buttons.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 0.53, "loop": false, "animation": null}, "apple_character": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/apple_character_6af9c5d0-f9e7-4b57-8d1e-dff8b63c55d6.webp", "type": "image", "prompt": "Generate a full-body, top-down 3/4 perspective illustration of a friendly red apple mascot character. The character should have a rounded, cute, and polished mascot style, featuring smooth red skin, a small green leaf on top, and a friendly, expressive face. Match the artistic style, lighting, and simple rounded proportions of the player_character. High resolution, professional game asset, clean lines, transparent background.", "description": "A friendly red apple mascot character for the player, viewed from a top-down 3/4 perspective.", "subtype": "character", "aspect_ratio": [791, 1215], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "banana_character": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/banana_character_c18ec4f9-0d47-459c-b493-bb377c56cac0.webp", "type": "image", "prompt": "Generate a full-body, top-down 3/4 perspective illustration of a friendly yellow banana mascot character. The character should have a rounded, cute, and polished mascot style that matches the aesthetic of the player_character. Features include a bright yellow peel, a friendly smiling face with expressive eyes, and a clean, professional game-ready look on a transparent background.", "description": "A friendly banana mascot character for the player in a top-down 3/4 perspective.", "subtype": "character", "aspect_ratio": [599, 1190], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "grapefruit_character": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/grapefruit_character_e34fc994-5bcf-4aa3-9b01-f1685b3faee0.webp", "type": "image", "prompt": "Generate a full-body, standing illustration of a friendly grapefruit mascot character. The character should have a cute, expressive face and a rounded body with a textured citrus peel. Top-down 3/4 perspective. High-quality cartoon style, clean lines, and vibrant colors consistent with the visual style of the apple and banana characters in image 1 and image 2. Transparent background, professional game-ready asset.", "description": "A friendly grapefruit mascot character for the player in a top-down 3/4 perspective.", "subtype": "character", "aspect_ratio": [794, 1240], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "watermelon_character": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/watermelon_character_a42d0db8-8ff9-4bd0-9b06-89353cf48d55.webp", "type": "image", "prompt": "Generate a high-quality cartoon-style watermelon slice mascot character. The character is friendly with a cute face, viewed from a top-down 3/4 perspective. It should be a full-body standing illustration on a transparent background. Maintain a consistent visual style, color vibrance, and character design language as seen in the apple_character and banana_character. Professional game asset, clean lines, polished look.", "description": "A friendly watermelon mascot character for the player, viewed from a top-down 3/4 perspective.", "subtype": "character", "aspect_ratio": [768, 1147], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "carrot_character": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/carrot_character_e8310fba-1268-4976-97ed-d613af20dd31.webp", "type": "image", "prompt": "Generate a full-body, friendly carrot mascot character for the player. The character should have a cute face, orange body, and green leafy top. Viewed from a top-down 3/4 perspective to match the perspective of `apple_character` and `banana_character`. Clean, polished, high-quality cartoon game asset style with bold outlines and vibrant colors. Transparent background, front-facing illustration.", "description": "A friendly carrot mascot character for the player in a top-down 3/4 perspective.", "subtype": "character", "aspect_ratio": [583, 1244], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "meatball_character": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/meatball_character_2d771931-e5b3-4c7e-b1bd-82a27a6ef3f1.webp", "type": "image", "prompt": "Generate a full-body, standing illustration of a friendly, cartoonish meatball character with large expressive eyes and a tiny smile. The meatball has a rich brown color with slight meaty texture and a small green sprig of basil on top. View from a top-down 3/4 perspective. High resolution, clean polished game-ready asset, transparent background, matching the art style of player_character.", "description": "A friendly meatball mascot character for the player, viewed from a top-down 3/4 perspective.", "subtype": "character", "aspect_ratio": [808, 1186], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "cookie_character": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/cookie_character_b5bfa4af-9ba9-46da-b03a-25692cf7987c.webp", "type": "image", "prompt": "Generate a full-body, front-facing illustration of a friendly, round chocolate chip cookie character with a cute, expressive face. The character is viewed from a top-down 3/4 perspective to match the style of 'meatball_character'. It should be golden-brown with clearly visible dark chocolate chips and have a polished, clean cartoon aesthetic. High resolution, professional game asset, transparent background.", "description": "A friendly chocolate chip cookie player mascot in a top-down 3/4 perspective.", "subtype": "character", "aspect_ratio": [829, 1221], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "fire_character": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/fire_character_d162a489-e041-465a-a53f-3bc65e72553b.webp", "type": "image", "prompt": "Generate a full-body, top-down view of a friendly, stylized flame character. The character should have large expressive eyes and a happy, cheerful expression. Use a vibrant palette of bright orange, yellow, and red with a soft glowing effect around the edges. Professional game asset quality, consistent with the top-down 3/4 perspective and cartoonish style of the meatball_character and cookie_character. Transparent background, clean edges.", "description": "A friendly flaming mascot character with a glowing effect and happy expression.", "subtype": "character", "aspect_ratio": [678, 1201], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "water_character": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/water_character_347b842a-e122-4775-8019-2a9e3716b6f0.webp", "type": "image", "prompt": "Generate a friendly, rounded water droplet character with big kind eyes and a small smile, viewed from a top-down perspective. Translucent blue color with highlights and reflections, polished game-ready asset, professional quality, 3/4 view consistent with other mascot characters like image 1, transparent background.", "description": "A friendly water droplet mascot character for the player, viewed from a top-down perspective.", "subtype": "character", "aspect_ratio": [781, 1104], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}}, "game_code": "<!DOCTYPE html>\n<html>\n<head>\n    <title>Math Maze Millennium</title>\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&family=Roboto:wght@400;500;700&display=swap\" rel=\"stylesheet\">\n    <style>\n        /* CSS Reset & Base Styles */\n        *, *::before, *::after {\n            box-sizing: border-box;\n            margin: 0;\n            padding: 0;\n        }\n\n        body, html {\n            width: 100%;\n            height: 100%;\n            overflow: hidden;\n            touch-action: none;\n            -ms-touch-action: none;\n            font-family: 'Roboto', sans-serif;\n        }\n\n        /* CSS Custom Properties */\n        :root {\n            /* Animation Timings */\n            --transition-fast: 150ms;\n            --transition-normal: 300ms;\n            --transition-slow: 500ms;\n\n            /* Easing Functions */\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\n            /* Spacing Scale (8px grid) */\n            --space-xs: 8px;\n            --space-sm: 16px;\n            --space-md: 24px;\n            --space-lg: 32px;\n            --space-xl: 48px;\n\n            /* Game Colors */\n            --color-wall: #2C3E50;\n            --color-corridor: #ECF0F1;\n            --color-entrance: #2ECC71;\n            --color-exit: #E74C3C;\n            --color-door-locked: #3498DB;\n            --color-player: #F39C12;\n            --color-correct: #27AE60;\n            --color-incorrect: #C0392B;\n            --color-primary: #9B59B6;\n            --color-accent: #1ABC9C;\n        }\n\n        /* Game Container */\n        #game-world {\n            position: relative;\n            width: 100%;\n            height: 100%;\n            background: linear-gradient(to bottom, #E8F4F8 0%, #FFFFFF 100%);\n        }\n\n        #game-canvas {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n        }\n\n        /* UI Overlay */\n        .ui-overlay {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            pointer-events: none;\n        }\n\n        .ui-overlay > * {\n            pointer-events: auto;\n        }\n\n        /* HUD Elements */\n        .hud {\n            position: absolute;\n            top: var(--space-sm);\n            left: var(--space-sm);\n            right: var(--space-sm);\n            display: flex;\n            justify-content: space-between;\n            align-items: flex-start;\n            gap: var(--space-sm);\n            z-index: 10;\n        }\n\n        .hud-info {\n            display: flex;\n            flex-direction: column;\n            gap: var(--space-xs);\n            background: rgba(255, 255, 255, 0.95);\n            padding: var(--space-sm);\n            border-radius: 12px;\n            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n        }\n\n        .hud-text {\n            font-size: 18px;\n            color: var(--color-wall);\n            font-weight: 500;\n        }\n\n        .hud-level {\n            font-size: 24px;\n            font-weight: 700;\n            color: var(--color-primary);\n            font-family: 'Fredoka', sans-serif;\n        }\n\n        .hud-streak {\n            font-size: 20px;\n            color: var(--color-correct);\n            font-weight: 600;\n        }\n\n        /* Buttons */\n        .btn {\n            font-family: 'Fredoka', sans-serif;\n            font-size: 22px;\n            font-weight: 600;\n            padding: var(--space-sm) var(--space-lg);\n            border: none;\n            border-radius: 12px;\n            cursor: pointer;\n            transition: transform var(--transition-fast) var(--ease-out),\n                        box-shadow var(--transition-fast) var(--ease-out);\n            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n            text-align: center;\n            min-width: 96px;\n            min-height: 96px;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n        }\n\n        .btn:hover {\n            transform: translateY(-2px);\n            box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);\n        }\n\n        .btn:active {\n            transform: translateY(0);\n            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);\n        }\n\n        .btn-primary {\n            background: var(--color-primary);\n            color: white;\n        }\n\n        .btn-secondary {\n            background: var(--color-accent);\n            color: white;\n        }\n\n        .btn-success {\n            background: var(--color-correct);\n            color: white;\n        }\n\n        .btn-danger {\n            background: var(--color-incorrect);\n            color: white;\n        }\n\n        .btn-icon {\n            min-width: 64px;\n            min-height: 64px;\n            padding: var(--space-sm);\n            font-size: 28px;\n        }\n\n        /* Modal Backdrop */\n        .modal-backdrop {\n            position: fixed;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            background: rgba(0, 0, 0, 0.5);\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            z-index: 100;\n            animation: fadeIn var(--transition-normal) var(--ease-out);\n        }\n\n        /* Modal Container */\n        .modal {\n            background: white;\n            border-radius: 24px;\n            padding: var(--space-lg);\n            max-width: 640px;\n            width: calc(100% - var(--space-lg) * 2);\n            max-height: calc(100% - var(--space-lg) * 2);\n            overflow-y: auto;\n            box-shadow: 0 24px 48px rgba(0, 0, 0, 0.3);\n            animation: slideUp var(--transition-normal) var(--ease-bounce);\n        }\n\n        /* Math Problem Modal */\n        .math-modal {\n            text-align: center;\n        }\n\n        .math-problem {\n            font-family: 'Fredoka', sans-serif;\n            font-size: 48px;\n            font-weight: 600;\n            color: var(--color-wall);\n            margin-bottom: var(--space-lg);\n            text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);\n        }\n\n        .answer-buttons {\n            display: flex;\n            flex-direction: column;\n            gap: var(--space-sm);\n            margin-top: var(--space-lg);\n        }\n\n        .answer-btn {\n            width: 100%;\n            min-height: 96px;\n            font-size: 28px;\n            background: var(--color-primary);\n            color: white;\n            transition: all var(--transition-fast) var(--ease-out);\n        }\n\n        .answer-btn.correct {\n            background: var(--color-correct);\n            animation: pulse var(--transition-slow) var(--ease-out);\n        }\n\n        .answer-btn.incorrect {\n            background: var(--color-incorrect);\n            animation: shake var(--transition-normal) var(--ease-out);\n        }\n\n        /* Level Select Modal */\n        .level-select-modal {\n            text-align: center;\n        }\n\n        .modal-title {\n            font-family: 'Fredoka', sans-serif;\n            font-size: 48px;\n            font-weight: 600;\n            color: var(--color-primary);\n            margin-bottom: var(--space-lg);\n        }\n\n        .level-grid {\n            display: grid;\n            grid-template-columns: repeat(5, 1fr);\n            gap: var(--space-sm);\n            margin-bottom: var(--space-lg);\n        }\n\n        .level-btn {\n            aspect-ratio: 1;\n            min-width: 0;\n            min-height: 0;\n            padding: var(--space-sm);\n            font-size: 24px;\n            position: relative;\n            background: var(--color-corridor);\n            color: var(--color-wall);\n        }\n\n        .level-btn.completed {\n            background: var(--color-correct);\n            color: white;\n        }\n\n        .level-btn.locked {\n            background: #ccc;\n            color: #999;\n            cursor: not-allowed;\n            opacity: 0.5;\n        }\n\n        .level-stars {\n            position: absolute;\n            bottom: 4px;\n            left: 50%;\n            transform: translateX(-50%);\n            font-size: 12px;\n            color: #FFD700;\n        }\n\n        /* Victory Modal */\n        .victory-modal {\n            text-align: center;\n        }\n\n        .victory-title {\n            font-family: 'Fredoka', sans-serif;\n            font-size: 56px;\n            font-weight: 600;\n            color: var(--color-correct);\n            margin-bottom: var(--space-md);\n        }\n\n        .victory-stars {\n            font-size: 64px;\n            margin: var(--space-lg) 0;\n            color: #FFD700;\n        }\n\n        .victory-stats {\n            font-size: 24px;\n            color: var(--color-wall);\n            margin-bottom: var(--space-lg);\n        }\n\n        .victory-buttons {\n            display: flex;\n            flex-direction: column;\n            gap: var(--space-sm);\n        }\n\n        /* Settings Panel */\n        .settings-panel {\n            position: fixed;\n            top: 0;\n            right: -100%;\n            width: 100%;\n            max-width: 480px;\n            height: 100%;\n            background: white;\n            box-shadow: -4px 0 24px rgba(0, 0, 0, 0.3);\n            z-index: 200;\n            transition: right var(--transition-normal) var(--ease-out);\n            overflow-y: auto;\n            padding: var(--space-lg);\n        }\n\n        .settings-panel.open {\n            right: 0;\n        }\n\n        .settings-header {\n            display: flex;\n            justify-content: space-between;\n            align-items: center;\n            margin-bottom: var(--space-lg);\n        }\n\n        .settings-title {\n            font-family: 'Fredoka', sans-serif;\n            font-size: 32px;\n            font-weight: 600;\n            color: var(--color-primary);\n        }\n\n        .setting-group {\n            margin-bottom: var(--space-lg);\n        }\n\n        .setting-label {\n            font-size: 18px;\n            font-weight: 600;\n            color: var(--color-wall);\n            margin-bottom: var(--space-sm);\n            display: block;\n        }\n\n        .setting-description {\n            font-size: 14px;\n            color: #666;\n            margin-bottom: var(--space-sm);\n        }\n\n        /* Utility Classes */\n        .hidden {\n            display: none !important;\n        }\n\n        /* Animations */\n        @keyframes fadeIn {\n            from { opacity: 0; }\n            to { opacity: 1; }\n        }\n\n        @keyframes slideUp {\n            from {\n                transform: translateY(32px);\n                opacity: 0;\n            }\n            to {\n                transform: translateY(0);\n                opacity: 1;\n            }\n        }\n\n        @keyframes pulse {\n            0%, 100% { transform: scale(1); }\n            50% { transform: scale(1.05); }\n        }\n\n        @keyframes shake {\n            0%, 100% { transform: translateX(0); }\n            25% { transform: translateX(-8px); }\n            75% { transform: translateX(8px); }\n        }\n\n        @media (prefers-reduced-motion: reduce) {\n            *, *::before, *::after {\n                animation-duration: 0.01ms !important;\n                animation-iteration-count: 1 !important;\n                transition-duration: 0.01ms !important;\n            }\n        }\n    </style>\n</head>\n<body>\n    <div id=\"game-world\">\n        <canvas id=\"game-canvas\"></canvas>\n        <div class=\"ui-overlay\">\n            <!-- HUD (hidden in edit mode) -->\n            <div class=\"hud hidden\" id=\"hud\">\n                <div style=\"display: flex; gap: var(--space-sm); align-items: flex-start;\">\n                    <div class=\"hud-info\">\n                        <div class=\"hud-level\" id=\"hud-level\">Level 1</div>\n                        <div class=\"hud-streak\" id=\"hud-streak\">Streak: 0</div>\n                    </div>\n                    <button class=\"btn btn-icon btn-secondary\" id=\"skin-btn\" title=\"Change Character\">\n                        <img id=\"skin-img\" src=\"\" style=\"width: 48px; height: 48px; object-fit: contain; pointer-events: none;\">\n                    </button>\n                </div>\n                <button class=\"btn btn-icon btn-secondary\" id=\"pause-btn\">\u23f8</button>\n            </div>\n        </div>\n    </div>\n\n    <script>\n        /**\n         * Math Maze Millennium\n         * \n         * A top-down maze navigation game where players solve math problems to unlock doors.\n         * Features 50 levels with progressive difficulty, procedurally generated mazes,\n         * and a star rating system based on performance.\n         * \n         * Core mechanics:\n         * - Navigate through mazes using touch/click or keyboard controls\n         * - Solve math problems (addition/subtraction) to unlock doors\n         * - Earn stars based on mistakes (3 stars = perfect, 2 stars = 1-2 mistakes, 1 star = 3+ mistakes)\n         * - Progress through 50 levels with increasing difficulty\n         */\n\n        // Global game state\n        let gameMode = 'play';\n        let canvas, ctx;\n        let assetCache = {};\n        let audioContext = null;\n        let audioBuffers = {};\n\n        // Runtime game state (not persisted in gameConfig)\n        let currentMaze = null;\n        let playerPos = { x: 0, y: 0 };\n        let targetPos = null;\n        let path = [];\n        let unlockedDoors = new Set();\n        let currentStreak = 0;\n        let levelMistakes = 0;\n        let isMoving = false;\n        let currentDoorIndex = null;\n        let gameStartTime = 0;\n\n        // Constants\n        const TILE_SIZE = 48;\n        const PLAYER_SPEED = 4; // tiles per second\n        const CELL_WALL = 1;\n        const CELL_PATH = 0;\n        const CELL_ENTRANCE = 2;\n        const CELL_EXIT = 3;\n\n        async function run(mode) {\n            gameMode = mode;\n            canvas = document.getElementById('game-canvas');\n            ctx = canvas.getContext('2d');\n\n            // Initialize canvas size\n            resizeCanvas();\n            window.addEventListener('resize', resizeCanvas);\n\n            // Initialize audio context\n            audioContext = new (window.AudioContext || window.webkitAudioContext)();\n\n            // Preload all assets\n            await preloadAssets();\n\n            // Initialize game config if needed\n            if (!window.gameConfig.currentLevel) {\n                window.gameConfig.currentLevel = 1;\n            }\n            if (!window.gameConfig.completedLevels) {\n                window.gameConfig.completedLevels = [];\n            }\n            if (!window.gameConfig.selectedCharacter) {\n                window.gameConfig.selectedCharacter = 'player_character';\n            }\n            if (!window.gameConfig.settings) {\n                window.gameConfig.settings = {\n                    operations: { addition: true, subtraction: false, multiplication: false },\n                    numberRange: { min: 1, max: 10 },\n                    mazeSize: 'auto',\n                    doorCount: 'auto',\n                    mazeComplexity: 5,\n                    showTimer: false,\n                    mistakePenalty: 'restart'\n                };\n            }\n\n            // Show game parameters\n            lib.showGameParameters({\n                name: 'Math Maze Settings',\n                params: {\n                    'Character': {\n                        key: 'selectedCharacter',\n                        type: 'dropdown',\n                        options: [\n                            { label: 'Orange', value: 'player_character' },\n                            { label: 'Apple', value: 'apple_character' },\n                            { label: 'Banana', value: 'banana_character' },\n                            { label: 'Grapefruit', value: 'grapefruit_character' },\n                            { label: 'Watermelon', value: 'watermelon_character' },\n                            { label: 'Carrot', value: 'carrot_character' },\n                            { label: 'Meatball', value: 'meatball_character' },\n                            { label: 'Cookie', value: 'cookie_character' },\n                            { label: 'Fire', value: 'fire_character' },\n                            { label: 'Water', value: 'water_character' }\n                        ]\n                    },\n                    'Math Difficulty': {\n                        key: 'settings.numberRange.max',\n                        type: 'slider',\n                        min: 10,\n                        max: 100,\n                        step: 10\n                    },\n                    'Maze Size': {\n                        key: 'settings.mazeSize',\n                        type: 'dropdown',\n                        options: [\n                            { label: 'Auto (Level-based)', value: 'auto' },\n                            { label: 'Small', value: 'small' },\n                            { label: 'Medium', value: 'medium' },\n                            { label: 'Large', value: 'large' }\n                        ]\n                    },\n                    'Show Timer': {\n                        key: 'settings.showTimer',\n                        type: 'checkbox'\n                    }\n                }\n            });\n\n            if (mode === 'edit') {\n                initEditMode();\n            } else {\n                initPlayMode();\n            }\n\n            // Start game loop\n            let lastTime = performance.now();\n            function gameLoop(timestamp) {\n                let deltaTime = (timestamp - lastTime) / 1000;\n                deltaTime = Math.min(deltaTime, 0.11); // Cap at 110ms\n                lastTime = timestamp;\n\n                update(deltaTime);\n                render();\n\n                requestAnimationFrame(gameLoop);\n            }\n            requestAnimationFrame(gameLoop);\n        }\n\n        function resizeCanvas() {\n            const gameWorld = document.getElementById('game-world');\n            canvas.width = gameWorld.clientWidth;\n            canvas.height = gameWorld.clientHeight;\n        }\n\n        async function preloadAssets() {\n            const assetIds = [\n                'player_character',\n                'apple_character',\n                'banana_character',\n                'grapefruit_character',\n                'watermelon_character',\n                'carrot_character',\n                'meatball_character',\n                'cookie_character',\n                'fire_character',\n                'water_character',\n                'locked_door',\n                'unlocked_door',\n                'entrance_tile',\n                'exit_tile',\n                'correct_answer_sfx',\n                'incorrect_answer_sfx',\n                'door_unlock_sfx',\n                'level_complete_sfx',\n                'button_click_sfx'\n            ];\n\n            const loadPromises = assetIds.map(async (id) => {\n                const assetInfo = lib.getAsset(id);\n                if (!assetInfo) {\n                    lib.log(`Asset not found: ${id}`);\n                    return;\n                }\n\n                if (assetInfo.type === 'image') {\n                    return new Promise((resolve, reject) => {\n                        const img = new Image();\n                        img.onload = () => {\n                            assetCache[id] = { image: img, info: assetInfo };\n                            resolve();\n                        };\n                        img.onerror = () => {\n                            lib.log(`Failed to load image: ${id}`);\n                            reject();\n                        };\n                        img.src = assetInfo.url;\n                    });\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 (error) {\n                        lib.log(`Failed to load audio: ${id}`);\n                    }\n                }\n            });\n\n            await Promise.all(loadPromises);\n        }\n\n        function playSound(assetId) {\n            if (!audioContext || !audioBuffers[assetId]) return;\n\n            const source = audioContext.createBufferSource();\n            source.buffer = audioBuffers[assetId];\n            source.connect(audioContext.destination);\n            source.start(0);\n        }\n\n        function initEditMode() {\n            // In edit mode, show level select by default\n            showLevelSelectModal();\n        }\n\n        function initPlayMode() {\n            // Show HUD\n            document.getElementById('hud').classList.remove('hidden');\n\n            // Set up pause button\n            document.getElementById('pause-btn').onclick = () => {\n                playSound('button_click_sfx');\n                showPauseModal();\n            };\n\n            // Set up skin button\n            const skins = ['player_character', 'apple_character', 'banana_character', 'grapefruit_character', 'watermelon_character', 'carrot_character', 'meatball_character', 'cookie_character', 'fire_character', 'water_character'];\n            const skinBtn = document.getElementById('skin-btn');\n            const updateSkinBtnIcon = () => {\n                const currentSkin = window.gameConfig.selectedCharacter || 'player_character';\n                const asset = lib.getAsset(currentSkin);\n                if (asset) {\n                    document.getElementById('skin-img').src = asset.url;\n                }\n            };\n\n            skinBtn.onclick = () => {\n                playSound('button_click_sfx');\n                const currentIndex = skins.indexOf(window.gameConfig.selectedCharacter || 'player_character');\n                const nextIndex = (currentIndex + 1) % skins.length;\n                window.gameConfig.selectedCharacter = skins[nextIndex];\n                updateSkinBtnIcon();\n            };\n            updateSkinBtnIcon();\n\n            // Generate maze for current level\n            generateLevel(window.gameConfig.currentLevel);\n\n            // Set up controls\n            setupControls();\n\n            // Update HUD\n            updateHUD();\n\n            // Start timer\n            gameStartTime = Date.now();\n        }\n        function setupControls() {\n            // Mouse/touch controls\n            canvas.addEventListener('click', handleCanvasClick);\n            canvas.addEventListener('touchend', (e) => {\n                e.preventDefault();\n                const touch = e.changedTouches[0];\n                const rect = canvas.getBoundingClientRect();\n                const x = touch.clientX - rect.left;\n                const y = touch.clientY - rect.top;\n                handleCanvasClick({ offsetX: x, offsetY: y });\n            });\n\n            // Keyboard controls\n            const keys = {};\n            window.addEventListener('keydown', (e) => {\n                keys[e.key] = true;\n                \n                // Prevent default for arrow keys and space\n                if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', ' '].includes(e.key)) {\n                    e.preventDefault();\n                }\n\n                // ESC to pause\n                if (e.key === 'Escape') {\n                    showPauseModal();\n                }\n            });\n\n            window.addEventListener('keyup', (e) => {\n                keys[e.key] = false;\n            });\n\n            // Store keys for update loop\n            window.gameKeys = keys;\n        }\n\n        function handleCanvasClick(e) {\n            if (gameMode === 'edit') return;\n\n            const rect = canvas.getBoundingClientRect();\n            const scaleX = canvas.width / rect.width;\n            const scaleY = canvas.height / rect.height;\n            const clickX = e.offsetX * scaleX;\n            const clickY = e.offsetY * scaleY;\n\n            // Convert to tile coordinates\n            const mazeOffsetX = (canvas.width - currentMaze.width * TILE_SIZE) / 2;\n            const mazeOffsetY = (canvas.height - currentMaze.height * TILE_SIZE) / 2;\n            const tileX = Math.floor((clickX - mazeOffsetX) / TILE_SIZE);\n            const tileY = Math.floor((clickY - mazeOffsetY) / TILE_SIZE);\n\n            // Check if click is within maze bounds\n            if (tileX < 0 || tileX >= currentMaze.width || tileY < 0 || tileY >= currentMaze.height) {\n                return;\n            }\n\n            // Check if tile is walkable\n            if (!isWalkable(tileX, tileY)) {\n                return;\n            }\n\n            // Set target and find path\n            targetPos = { x: tileX, y: tileY };\n            path = findPath(playerPos, targetPos);\n        }\n\n        function update(deltaTime) {\n            if (gameMode === 'edit') return;\n\n            // Keyboard movement\n            if (window.gameKeys && !isMoving) {\n                let dx = 0, dy = 0;\n                if (window.gameKeys['ArrowLeft'] || window.gameKeys['a'] || window.gameKeys['A']) dx = -1;\n                if (window.gameKeys['ArrowRight'] || window.gameKeys['d'] || window.gameKeys['D']) dx = 1;\n                if (window.gameKeys['ArrowUp'] || window.gameKeys['w'] || window.gameKeys['W']) dy = -1;\n                if (window.gameKeys['ArrowDown'] || window.gameKeys['s'] || window.gameKeys['S']) dy = 1;\n\n                if (dx !== 0 || dy !== 0) {\n                    const newX = playerPos.x + dx;\n                    const newY = playerPos.y + dy;\n                    if (isWalkable(newX, newY)) {\n                        targetPos = { x: newX, y: newY };\n                        path = [targetPos];\n                    }\n                }\n            }\n\n            // Move player along path\n            if (path && path.length > 0 && !isMoving) {\n                const nextTile = path[0];\n                \n                // Check if there's a locked door at next position\n                const doorIndex = isDoorAt(nextTile.x, nextTile.y);\n                if (doorIndex !== -1 && !unlockedDoors.has(doorIndex)) {\n                    // Stop and show math problem\n                    path = [];\n                    targetPos = null;\n                    currentDoorIndex = doorIndex;\n                    showMathProblem(doorIndex);\n                    return;\n                }\n\n                // Move to next tile\n                playerPos = nextTile;\n                path.shift();\n\n                // Check if reached exit\n                if (currentMaze.grid[playerPos.y][playerPos.x] === CELL_EXIT) {\n                    onLevelComplete();\n                }\n            }\n        }\n\n        function render() {\n            ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n            if (!currentMaze) return;\n\n            const mazeOffsetX = (canvas.width - currentMaze.width * TILE_SIZE) / 2;\n            const mazeOffsetY = (canvas.height - currentMaze.height * TILE_SIZE) / 2;\n\n            // Draw maze grid\n            for (let y = 0; y < currentMaze.height; y++) {\n                for (let x = 0; x < currentMaze.width; x++) {\n                    const cell = currentMaze.grid[y][x];\n                    const px = mazeOffsetX + x * TILE_SIZE;\n                    const py = mazeOffsetY + y * TILE_SIZE;\n\n                    // Draw cell\n                    if (cell === CELL_WALL) {\n                        ctx.fillStyle = '#2C3E50';\n                        ctx.fillRect(px, py, TILE_SIZE, TILE_SIZE);\n                    } else {\n                        ctx.fillStyle = '#ECF0F1';\n                        ctx.fillRect(px, py, TILE_SIZE, TILE_SIZE);\n\n                        // Draw entrance/exit tiles\n                        if (cell === CELL_ENTRANCE) {\n                            drawAsset('entrance_tile', px, py, TILE_SIZE, TILE_SIZE);\n                        } else if (cell === CELL_EXIT) {\n                            drawAsset('exit_tile', px, py, TILE_SIZE, TILE_SIZE);\n                        }\n                    }\n\n                    // Draw grid lines\n                    ctx.strokeStyle = 'rgba(0, 0, 0, 0.1)';\n                    ctx.lineWidth = 1;\n                    ctx.strokeRect(px, py, TILE_SIZE, TILE_SIZE);\n                }\n            }\n\n            // Draw doors\n            currentMaze.doors.forEach((door, index) => {\n                const px = mazeOffsetX + door.x * TILE_SIZE;\n                const py = mazeOffsetY + door.y * TILE_SIZE;\n                \n                if (unlockedDoors.has(index)) {\n                    drawAsset('unlocked_door', px, py, TILE_SIZE, TILE_SIZE);\n                } else {\n                    drawAsset('locked_door', px, py, TILE_SIZE, TILE_SIZE);\n                }\n            });\n\n            // Draw player\n            const playerPx = mazeOffsetX + playerPos.x * TILE_SIZE;\n            const playerPy = mazeOffsetY + playerPos.y * TILE_SIZE;\n            const characterId = window.gameConfig.selectedCharacter || 'player_character';\n            drawAsset(characterId, playerPx, playerPy, TILE_SIZE, TILE_SIZE);\n\n            // Draw path (for debugging in edit mode)\n            if (gameMode === 'edit' && path && path.length > 0) {\n                ctx.strokeStyle = 'rgba(255, 0, 0, 0.5)';\n                ctx.lineWidth = 3;\n                ctx.beginPath();\n                ctx.moveTo(playerPx + TILE_SIZE / 2, playerPy + TILE_SIZE / 2);\n                path.forEach(tile => {\n                    const px = mazeOffsetX + tile.x * TILE_SIZE + TILE_SIZE / 2;\n                    const py = mazeOffsetY + tile.y * TILE_SIZE + TILE_SIZE / 2;\n                    ctx.lineTo(px, py);\n                });\n                ctx.stroke();\n            }\n        }\n\n        function drawAsset(assetId, x, y, width, height) {\n            const asset = assetCache[assetId];\n            if (!asset) return;\n\n            const img = asset.image;\n            const aspectRatio = asset.info.aspect_ratio || [1, 1];\n            const imgAspect = aspectRatio[0] / aspectRatio[1];\n            const targetAspect = width / height;\n\n            let drawWidth, drawHeight, drawX, drawY;\n\n            // Use contain scaling for all assets (fit within bounds)\n            if (imgAspect > targetAspect) {\n                drawWidth = width;\n                drawHeight = width / imgAspect;\n                drawX = x;\n                drawY = y + (height - drawHeight) / 2;\n            } else {\n                drawHeight = height;\n                drawWidth = height * imgAspect;\n                drawX = x + (width - drawWidth) / 2;\n                drawY = y;\n            }\n\n            ctx.drawImage(img, drawX, drawY, drawWidth, drawHeight);\n        }\n\n        function isWalkable(x, y) {\n            if (!currentMaze) return false;\n            if (x < 0 || x >= currentMaze.width || y < 0 || y >= currentMaze.height) {\n                return false;\n            }\n            return currentMaze.grid[y][x] !== CELL_WALL;\n        }\n\n        function isDoorAt(x, y) {\n            return currentMaze.doors.findIndex(door => door.x === x && door.y === y);\n        }\n\n        // A* Pathfinding\n        function findPath(start, end) {\n            const openSet = [start];\n            const cameFrom = new Map();\n            const gScore = new Map();\n            const fScore = new Map();\n\n            gScore.set(key(start), 0);\n            fScore.set(key(start), heuristic(start, end));\n\n            while (openSet.length > 0) {\n                // Find node with lowest fScore\n                let current = openSet[0];\n                let currentIndex = 0;\n                for (let i = 1; i < openSet.length; i++) {\n                    if (fScore.get(key(openSet[i])) < fScore.get(key(current))) {\n                        current = openSet[i];\n                        currentIndex = i;\n                    }\n                }\n\n                if (current.x === end.x && current.y === end.y) {\n                    // Reconstruct path\n                    const path = [current];\n                    while (cameFrom.has(key(current))) {\n                        current = cameFrom.get(key(current));\n                        path.unshift(current);\n                    }\n                    return path.slice(1); // Remove start position\n                }\n\n                openSet.splice(currentIndex, 1);\n\n                // Check neighbors\n                const neighbors = [\n                    { x: current.x + 1, y: current.y },\n                    { x: current.x - 1, y: current.y },\n                    { x: current.x, y: current.y + 1 },\n                    { x: current.x, y: current.y - 1 }\n                ];\n\n                for (const neighbor of neighbors) {\n                    if (!isWalkable(neighbor.x, neighbor.y)) continue;\n\n                    const tentativeGScore = gScore.get(key(current)) + 1;\n\n                    if (!gScore.has(key(neighbor)) || tentativeGScore < gScore.get(key(neighbor))) {\n                        cameFrom.set(key(neighbor), current);\n                        gScore.set(key(neighbor), tentativeGScore);\n                        fScore.set(key(neighbor), tentativeGScore + heuristic(neighbor, end));\n\n                        if (!openSet.some(n => n.x === neighbor.x && n.y === neighbor.y)) {\n                            openSet.push(neighbor);\n                        }\n                    }\n                }\n            }\n\n            return []; // No path found\n        }\n\n        function heuristic(a, b) {\n            return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);\n        }\n\n        function key(pos) {\n            return `${pos.x},${pos.y}`;\n        }\n\n        // Maze Generation\n        function generateLevel(level) {\n            const settings = window.gameConfig.settings;\n\n            // Determine maze size based on level or settings\n            let width, height;\n            if (settings.mazeSize === 'auto') {\n                // Scaling up to level 1000\n                const sizeInc = Math.floor(Math.min(level, 500) / 25);\n                width = 11 + sizeInc;\n                height = 13 + Math.floor(sizeInc * 1.5);\n                \n                // Cap dimensions to keep it playable on mobile\n                width = Math.min(width, 21);\n                height = Math.min(height, 29);\n            } else {\n                const sizes = { small: [11, 13], medium: [13, 17], large: [17, 21] };\n                [width, height] = sizes[settings.mazeSize] || [13, 17];\n            }\n\n            // Ensure width and height are odd to guarantee path connectivity in the 2-step carving algorithm\n            if (width % 2 === 0) width++;\n            if (height % 2 === 0) height++;\n\n            // Determine door count\n            let doorCount;\n            if (settings.doorCount === 'auto') {\n                doorCount = 3 + Math.floor(Math.min(level, 200) / 40);\n                doorCount = Math.min(doorCount, 8); // Max 8 doors\n            } else {\n                doorCount = settings.doorCount;\n            }\n\n            // Generate maze\n            const grid = generateMazeGrid(width, height);\n\n            // Set entrance and exit\n            grid[0][0] = CELL_ENTRANCE;\n            grid[height - 1][width - 1] = CELL_EXIT;\n\n            // Initialize currentMaze before placing doors so findPath works during generation\n            currentMaze = { grid, width, height, doors: [] };\n\n            // Place doors along the path\n            const doors = placeDoors(grid, width, height, doorCount, level);\n            currentMaze.doors = doors;\n\n            playerPos = { x: 0, y: 0 };\n            targetPos = null;\n            path = [];\n            unlockedDoors = new Set();\n            levelMistakes = 0;\n            currentStreak = 0;\n        }\n\n        function generateMazeGrid(width, height) {\n            // Initialize grid with all walls\n            const grid = Array(height).fill(null).map(() => Array(width).fill(CELL_WALL));\n\n            // Recursive backtracking\n            const stack = [];\n            const startX = 0;\n            const startY = 0;\n            grid[startY][startX] = CELL_PATH;\n            stack.push({ x: startX, y: startY });\n\n            while (stack.length > 0) {\n                const current = stack[stack.length - 1];\n                const neighbors = [];\n\n                // Check all four directions\n                const directions = [\n                    { dx: 0, dy: -2 }, // Up\n                    { dx: 2, dy: 0 },  // Right\n                    { dx: 0, dy: 2 },  // Down\n                    { dx: -2, dy: 0 }  // Left\n                ];\n\n                for (const dir of directions) {\n                    const nx = current.x + dir.dx;\n                    const ny = current.y + dir.dy;\n\n                    if (nx >= 0 && nx < width && ny >= 0 && ny < height && grid[ny][nx] === CELL_WALL) {\n                        neighbors.push({ x: nx, y: ny, dx: dir.dx, dy: dir.dy });\n                    }\n                }\n\n                if (neighbors.length > 0) {\n                    // Choose random neighbor\n                    const next = neighbors[Math.floor(Math.random() * neighbors.length)];\n                    \n                    // Remove wall between current and next\n                    const wallX = current.x + next.dx / 2;\n                    const wallY = current.y + next.dy / 2;\n                    grid[wallY][wallX] = CELL_PATH;\n                    grid[next.y][next.x] = CELL_PATH;\n\n                    stack.push({ x: next.x, y: next.y });\n                } else {\n                    stack.pop();\n                }\n            }\n\n            return grid;\n        }\n\n        function placeDoors(grid, width, height, count, level) {\n            const doors = [];\n            \n            // Find path from entrance to exit\n            const path = findPath({ x: 0, y: 0 }, { x: width - 1, y: height - 1 });\n            \n            if (!path || path.length === 0) {\n                lib.log(\"Warning: No path found for door placement.\");\n                return doors;\n            }\n\n            if (path.length < count * 2) {\n                count = Math.max(0, Math.floor(path.length / 2));\n            }\n\n            // Place doors evenly along the path\n            const step = Math.floor(path.length / (count + 1));\n            for (let i = 1; i <= count; i++) {\n                const index = Math.floor(i * step);\n                if (index >= 0 && index < path.length) {\n                    const pos = path[index];\n                    if (pos) {\n                        doors.push({\n                            x: pos.x,\n                            y: pos.y,\n                            problem: generateMathProblem(level)\n                        });\n                    }\n                }\n            }\n\n            return doors;\n        }\n\n        function generateMathProblem(level) {\n            const settings = window.gameConfig.settings;\n            const operations = settings.operations;\n\n            // Determine number range based on level\n            let min = settings.numberRange.min;\n            let max = settings.numberRange.max;\n\n            if (settings.numberRange.max === 10 || settings.mazeSize === 'auto') {\n                // Auto mode based on level - scales up to level 1000\n                min = 1;\n                max = 10 + (level * 2);\n                if (level > 50) max = 110 + (level - 50) * 5;\n                if (level > 100) max = 360 + (level - 100) * 10;\n                max = Math.min(max, 2000); // Cap at 2000\n            }\n\n            // Select operation\n            const availableOps = [];\n            if (operations.addition) availableOps.push('add');\n            if (operations.subtraction && (level > 5 || operations.subtraction)) availableOps.push('sub');\n            if (operations.multiplication && (level > 15 || operations.multiplication)) availableOps.push('mul');\n\n            if (availableOps.length === 0) availableOps.push('add');\n\n            const op = availableOps[Math.floor(Math.random() * availableOps.length)];\n\n            let a, b, answer, question;\n\n            if (op === 'add') {\n                a = Math.floor(Math.random() * (max - min + 1)) + min;\n                b = Math.floor(Math.random() * (max - min + 1)) + min;\n                answer = a + b;\n                question = `${a} + ${b}`;\n            } else if (op === 'sub') {\n                a = Math.floor(Math.random() * (max - min + 1)) + min;\n                b = Math.floor(Math.random() * Math.min(a, max - min + 1)) + min;\n                answer = a - b;\n                question = `${a} - ${b}`;\n            } else if (op === 'mul') {\n                const multipliers = [2, 3, 5, 10];\n                a = Math.floor(Math.random() * 10) + 1;\n                b = multipliers[Math.floor(Math.random() * multipliers.length)];\n                answer = a * b;\n                question = `${a} \u00d7 ${b}`;\n            }\n\n            // Generate wrong answers\n            const wrongAnswers = new Set();\n            while (wrongAnswers.size < 3) {\n                const offset = Math.floor(Math.random() * 20) - 10;\n                const wrong = answer + offset;\n                if (wrong !== answer && wrong > 0) {\n                    wrongAnswers.add(wrong);\n                }\n            }\n\n            // Shuffle answers\n            const answers = [answer, ...Array.from(wrongAnswers)];\n            for (let i = answers.length - 1; i > 0; i--) {\n                const j = Math.floor(Math.random() * (i + 1));\n                [answers[i], answers[j]] = [answers[j], answers[i]];\n            }\n\n            return { question, answer, answers };\n        }\n\n        function showMathProblem(doorIndex) {\n            const door = currentMaze.doors[doorIndex];\n            const problem = door.problem;\n\n            const backdrop = document.createElement('div');\n            backdrop.className = 'modal-backdrop';\n\n            const modal = document.createElement('div');\n            modal.className = 'modal math-modal';\n\n            const problemText = document.createElement('div');\n            problemText.className = 'math-problem';\n            problemText.textContent = problem.question + ' = ?';\n\n            const answerButtons = document.createElement('div');\n            answerButtons.className = 'answer-buttons';\n\n            problem.answers.forEach((ans, index) => {\n                const btn = document.createElement('button');\n                btn.className = 'btn answer-btn';\n                btn.textContent = ans;\n                btn.onclick = () => handleAnswer(ans, problem.answer, btn, backdrop, doorIndex);\n                answerButtons.appendChild(btn);\n            });\n\n            modal.appendChild(problemText);\n            modal.appendChild(answerButtons);\n            backdrop.appendChild(modal);\n            document.body.appendChild(backdrop);\n        }\n\n        function handleAnswer(selected, correct, btn, backdrop, doorIndex) {\n            if (selected === correct) {\n                // Correct answer\n                btn.classList.add('correct');\n                playSound('correct_answer_sfx');\n                currentStreak++;\n                \n                setTimeout(() => {\n                    unlockedDoors.add(doorIndex);\n                    playSound('door_unlock_sfx');\n                    backdrop.remove();\n                    currentDoorIndex = null;\n                    updateHUD();\n                }, 500);\n            } else {\n                // Incorrect answer\n                btn.classList.add('incorrect');\n                playSound('incorrect_answer_sfx');\n                levelMistakes++;\n                currentStreak = 0;\n                \n                // User requested: \"make so if your worng you need to restart from start\"\n                setTimeout(() => {\n                    backdrop.remove();\n                    generateLevel(window.gameConfig.currentLevel);\n                    updateHUD();\n                    lib.log(\"Incorrect answer! Level restarted.\");\n                }, 800);\n            }\n        }\n\n        function updateHUD() {\n            document.getElementById('hud-level').textContent = `Level ${window.gameConfig.currentLevel}`;\n            document.getElementById('hud-streak').textContent = `Streak: ${currentStreak}`;\n        }\n\n        function onLevelComplete() {\n            const level = window.gameConfig.currentLevel;\n            const time = Math.floor((Date.now() - gameStartTime) / 1000);\n            \n            // Calculate stars\n            let stars;\n            if (levelMistakes === 0) stars = 3;\n            else if (levelMistakes <= 2) stars = 2;\n            else stars = 1;\n\n            // Save completion\n            const existingIndex = window.gameConfig.completedLevels.findIndex(c => c.level === level);\n            const completion = { level, stars, time, mistakes: levelMistakes };\n            \n            if (existingIndex >= 0) {\n                // Update if better\n                const existing = window.gameConfig.completedLevels[existingIndex];\n                if (stars > existing.stars || (stars === existing.stars && time < existing.time)) {\n                    window.gameConfig.completedLevels[existingIndex] = completion;\n                }\n            } else {\n                window.gameConfig.completedLevels.push(completion);\n            }\n\n            playSound('level_complete_sfx');\n            showVictoryModal(stars, time);\n        }\n\n        function showVictoryModal(stars, time) {\n            const backdrop = document.createElement('div');\n            backdrop.className = 'modal-backdrop';\n\n            const modal = document.createElement('div');\n            modal.className = 'modal victory-modal';\n\n            const title = document.createElement('div');\n            title.className = 'victory-title';\n            title.textContent = 'Level Complete!';\n\n            const starsDisplay = document.createElement('div');\n            starsDisplay.className = 'victory-stars';\n            starsDisplay.textContent = '\u2b50'.repeat(stars);\n\n            const stats = document.createElement('div');\n            stats.className = 'victory-stats';\n            stats.innerHTML = `\n                Time: ${time}s<br>\n                Mistakes: ${levelMistakes}\n            `;\n\n            const buttons = document.createElement('div');\n            buttons.className = 'victory-buttons';\n\n            const nextBtn = document.createElement('button');\n            nextBtn.className = 'btn btn-success';\n            nextBtn.textContent = 'Next Level';\n            nextBtn.onclick = () => {\n                playSound('button_click_sfx');\n                if (window.gameConfig.currentLevel < 1000) {\n                    window.gameConfig.currentLevel++;\n                    backdrop.remove();\n                    generateLevel(window.gameConfig.currentLevel);\n                    updateHUD();\n                } else {\n                    backdrop.remove();\n                    showLevelSelectModal();\n                }\n            };\n\n            const menuBtn = document.createElement('button');\n            menuBtn.className = 'btn btn-secondary';\n            menuBtn.textContent = 'Level Select';\n            menuBtn.onclick = () => {\n                playSound('button_click_sfx');\n                backdrop.remove();\n                showLevelSelectModal();\n            };\n\n            buttons.appendChild(nextBtn);\n            buttons.appendChild(menuBtn);\n\n            modal.appendChild(title);\n            modal.appendChild(starsDisplay);\n            modal.appendChild(stats);\n            modal.appendChild(buttons);\n            backdrop.appendChild(modal);\n            document.body.appendChild(backdrop);\n        }\n\n        function showPauseModal() {\n            const backdrop = document.createElement('div');\n            backdrop.className = 'modal-backdrop';\n\n            const modal = document.createElement('div');\n            modal.className = 'modal level-select-modal';\n\n            const title = document.createElement('div');\n            title.className = 'modal-title';\n            title.textContent = 'Paused';\n\n            const buttons = document.createElement('div');\n            buttons.className = 'victory-buttons';\n            buttons.style.marginTop = '32px';\n\n            const resumeBtn = document.createElement('button');\n            resumeBtn.className = 'btn btn-success';\n            resumeBtn.textContent = 'Resume';\n            resumeBtn.onclick = () => {\n                playSound('button_click_sfx');\n                backdrop.remove();\n            };\n\n            const restartBtn = document.createElement('button');\n            restartBtn.className = 'btn btn-secondary';\n            restartBtn.textContent = 'Restart Level';\n            restartBtn.onclick = () => {\n                playSound('button_click_sfx');\n                backdrop.remove();\n                generateLevel(window.gameConfig.currentLevel);\n                updateHUD();\n            };\n\n            const menuBtn = document.createElement('button');\n            menuBtn.className = 'btn btn-primary';\n            menuBtn.textContent = 'Level Select';\n            menuBtn.onclick = () => {\n                playSound('button_click_sfx');\n                backdrop.remove();\n                showLevelSelectModal();\n            };\n\n            buttons.appendChild(resumeBtn);\n            buttons.appendChild(restartBtn);\n            buttons.appendChild(menuBtn);\n\n            modal.appendChild(title);\n            modal.appendChild(buttons);\n            backdrop.appendChild(modal);\n            document.body.appendChild(backdrop);\n        }\n\n        function showLevelSelectModal() {\n            const backdrop = document.createElement('div');\n            backdrop.className = 'modal-backdrop';\n\n            const modal = document.createElement('div');\n            modal.className = 'modal level-select-modal';\n\n            const title = document.createElement('div');\n            title.className = 'modal-title';\n            title.textContent = 'Math Maze Millennium';\n\n            const subtitle = document.createElement('div');\n            subtitle.style.fontSize = '24px';\n            subtitle.style.color = '#666';\n            subtitle.style.marginBottom = '24px';\n            subtitle.textContent = 'Select a Level';\n\n            const grid = document.createElement('div');\n            grid.className = 'level-grid';\n\n            for (let i = 1; i <= 100; i++) {\n                const btn = document.createElement('button');\n                btn.className = 'btn level-btn';\n                btn.textContent = i;\n\n                const completion = window.gameConfig.completedLevels.find(c => c.level === i);\n                if (completion) {\n                    btn.classList.add('completed');\n                    const stars = document.createElement('div');\n                    stars.className = 'level-stars';\n                    stars.textContent = '\u2b50'.repeat(completion.stars);\n                    btn.appendChild(stars);\n                } else if (i > window.gameConfig.currentLevel && i > 1) {\n                    btn.classList.add('locked');\n                    btn.disabled = true;\n                }\n\n                if (!btn.disabled) {\n                    btn.onclick = () => {\n                        playSound('button_click_sfx');\n                        window.gameConfig.currentLevel = i;\n                        backdrop.remove();\n                        generateLevel(i);\n                        updateHUD();\n                    };\n                }\n\n                grid.appendChild(btn);\n            }\n\n            modal.appendChild(title);\n            modal.appendChild(subtitle);\n            modal.appendChild(grid);\n            backdrop.appendChild(modal);\n            document.body.appendChild(backdrop);\n        }\n    </script>\n</body>\n</html>", "game_config": "{\n  \"currentLevel\": 1,\n  \"completedLevels\": [],\n  \"settings\": {\n    \"operations\": {\n      \"addition\": true,\n      \"subtraction\": false,\n      \"multiplication\": false\n    },\n    \"numberRange\": {\n      \"min\": 1,\n      \"max\": 10\n    },\n    \"mazeSize\": \"auto\",\n    \"doorCount\": \"auto\",\n    \"mazeComplexity\": 5,\n    \"showTimer\": false,\n    \"mistakePenalty\": \"restart\"\n  },\n  \"selectedCharacter\": \"player_character\"\n}", "game_design_document": "## Core Game Loop and Mechanics\n\n**Math Maze Millennium** combines maze navigation with mathematical problem-solving in an engaging, progressively challenging experience.\n\n### Moment-to-Moment Gameplay\n1. Player spawns at the maze entrance (green tile)\n2. Player navigates through corridors using touch/keyboard controls\n3. When approaching a locked door, a math problem appears\n4. Player solves the problem by selecting the correct answer from multiple choices\n5. Correct answer unlocks the door; incorrect answer may penalize (time penalty or require retry)\n6. Player continues through the maze until reaching the exit (red tile)\n7. Upon completion, next level unlocks with increased difficulty\n\n### Progression System\n- **1000+ Levels**: Procedurally generated mazes with increasing complexity\n- **Difficulty Scaling**: \n  - Levels 1-100: Single-digit addition/subtraction\n  - Levels 101-300: Double-digit addition/subtraction, single-digit multiplication\n  - Levels 301-500: Multi-digit operations, division introduced\n  - Levels 501-750: Mixed operations, fractions, decimals\n  - Levels 751-1000+: Advanced operations, percentages, order of operations, algebraic thinking\n\n### Engagement Drivers\n- **Star Rating System**: 3 stars for perfect completion, 2 stars for 1-2 mistakes, 1 star for completion with more mistakes\n- **Time Tracking**: Optional timer for speedrun challenges\n- **Streak Counter**: Track consecutive correct answers\n- **Level Unlocking**: Linear progression with ability to replay completed levels\n\n### Win/Lose Conditions\n- **Win**: Reach the exit tile after unlocking all necessary doors\n- **No Lose State**: Players can retry problems unlimited times, but mistakes affect star rating\n- **Replayability**: Replay any completed level to improve star rating or time\n\n## Starting Scope\n\nFor the initial implementation, we'll focus on creating a polished, playable core experience:\n\n### Phase 1 (Initial Implementation)\n- **Levels 1-50**: Fully implemented with appropriate difficulty scaling\n- **Math Operations**: Addition and subtraction (single and double-digit)\n- **Maze Generation**: Procedural maze generation with 3-5 doors per level\n- **Core UI**: \n  - Level select screen showing completed levels and stars earned\n  - In-game HUD with level number, current streak, and pause button\n  - Math problem modal with 4 multiple-choice answers\n  - Victory screen with star rating and next level button\n- **Basic Edit Mode**: Allow players to adjust:\n  - Math difficulty range (customize operation types)\n  - Maze size (small/medium/large)\n  - Number of doors per level\n- **Visual Polish**: Clean, colorful maze tiles with clear visual feedback\n- **Sound Effects**: Door unlock, correct/incorrect answer, level complete\n\n### Future Enhancements (Post-Launch)\n- Levels 51-1000+ with advanced math operations\n- Multiplication, division, fractions, decimals, percentages\n- Power-ups (hint system, skip door, reveal answer)\n- Daily challenges with special rewards\n- Leaderboards for speedruns\n- Custom maze creator in edit mode\n- Achievement system\n- Background music with multiple tracks\n\n## Game Perspective\n\n**Top-down** perspective with a fixed camera showing the entire maze on screen. This allows players to:\n- See the full maze layout and plan their route\n- Identify door locations and strategize which path to take\n- Maintain spatial awareness throughout gameplay\n\nThe maze will be scaled to fit the 720\u00d71280 viewport, with the player character and doors clearly visible.\n\n## Game World and Level Design\n\n### Maze Structure\nEach level consists of:\n- **Grid-based Layout**: Mazes built on a grid system (e.g., 10\u00d715 tiles for early levels, expanding to 15\u00d720+ for later levels)\n- **Entrance (Green Tile)**: Starting position, always at the top or left edge\n- **Exit (Red Tile)**: Goal position, always at the bottom or right edge\n- **Walls (Dark Gray)**: Impassable barriers forming the maze structure\n- **Corridors (Light Gray)**: Walkable paths\n- **Doors (Blue)**: Locked barriers requiring math problems to unlock (turn to light gray when unlocked)\n- **Player Character**: Bright, easily visible character sprite\n\n### Procedural Generation Algorithm\nFor the initial 50 levels:\n1. Generate a perfect maze using recursive backtracking or Prim's algorithm\n2. Ensure a valid path exists from entrance to exit\n3. Place 3-5 doors strategically along the critical path\n4. Each door is assigned a math problem based on current level difficulty\n5. Add some dead-ends and alternate paths for exploration\n\n### Level Difficulty Progression (Levels 1-50)\n- **Levels 1-10**: Small mazes (10\u00d712), 3 doors, addition only (1-10)\n- **Levels 11-20**: Medium mazes (12\u00d715), 4 doors, addition/subtraction (1-20)\n- **Levels 21-30**: Medium mazes (12\u00d715), 4 doors, addition/subtraction (1-50)\n- **Levels 31-40**: Large mazes (15\u00d718), 5 doors, addition/subtraction (1-100)\n- **Levels 41-50**: Large mazes (15\u00d720), 5 doors, mixed operations including simple multiplication (\u00d72, \u00d73, \u00d75, \u00d710)\n\n### Visual Design\n- **Color-coded Elements**:\n  - Entrance: Bright green (#00FF00)\n  - Exit: Bright red (#FF0000)\n  - Walls: Dark gray (#333333)\n  - Corridors: Light gray (#CCCCCC)\n  - Locked doors: Blue (#0066FF)\n  - Unlocked doors: Same as corridors (#CCCCCC)\n  - Player: Bright yellow/orange character\n- **Grid Lines**: Subtle grid lines to help with spatial awareness\n- **Door Indicators**: Small lock icon on locked doors\n\n## Edit Mode Design\n\nEdit mode allows players to customize their gameplay experience without breaking core mechanics.\n\n### Editable Properties\n\n**Math Difficulty Settings**\n- **Operation Types** (checkboxes):\n  - Addition (default: ON)\n  - Subtraction (default: ON for levels 11+)\n  - Multiplication (default: ON for levels 41+)\n  - Division (default: OFF, future feature)\n- **Number Range** (slider):\n  - Minimum: 1-10\n  - Maximum: 10-100\n  - Default: Based on current level\n\n**Maze Settings**\n- **Maze Size** (dropdown):\n  - Small (10\u00d712 tiles)\n  - Medium (12\u00d715 tiles)\n  - Large (15\u00d720 tiles)\n  - Default: Based on current level\n- **Door Count** (slider):\n  - Range: 2-8 doors\n  - Default: Based on current level (3-5)\n- **Maze Complexity** (slider):\n  - Simple: Fewer dead-ends, more direct paths\n  - Complex: More branching, more dead-ends\n  - Default: Medium\n\n**Visual Settings**\n- **Color Theme** (dropdown):\n  - Classic (default colors)\n  - Dark Mode (inverted colors)\n  - Colorblind Friendly (high contrast)\n  - Neon (bright, vibrant colors)\n\n**Gameplay Settings**\n- **Timer** (toggle):\n  - ON: Show timer and track completion time\n  - OFF: No time pressure (default)\n- **Mistake Penalty** (dropdown):\n  - None: Unlimited retries, no penalty\n  - Star Penalty: Mistakes reduce star rating (default)\n  - Time Penalty: +10 seconds per mistake (requires timer ON)\n\n### Constraints\n- Cannot disable all operation types (at least one must be selected)\n- Number range maximum must be greater than minimum\n- Door count cannot exceed maze capacity (max 8 doors)\n- Changes apply to current level only; each level retains its default settings\n- Star ratings and leaderboards are disabled for customized levels\n\n### Edit Mode UI\n- **Edit Button**: Accessible from pause menu and level select\n- **Settings Panel**: Slide-in panel from right side with all editable properties\n- **Reset Button**: Restore default settings for current level\n- **Apply Button**: Regenerate level with new settings\n- **Visual Preview**: Show sample math problem based on current settings\n\n## Control Design\n\n**Math Maze Millennium** uses a **top-down casual** control scheme optimized for both mobile and desktop.\n\n### Mobile Controls (Touch)\n- **Movement**: Tap anywhere on the maze to move the player character to that location\n  - Player automatically pathfinds to tapped location (avoiding walls)\n  - Tapping on a locked door triggers the math problem modal\n  - Tapping on unreachable areas shows a brief \"Can't reach\" indicator\n- **Math Problem Interaction**:\n  - Tap answer buttons to select\n  - Large, thumb-friendly buttons (minimum 96\u00d796 pixels)\n  - Positioned in bottom half of screen for easy reach\n- **UI Navigation**:\n  - Tap buttons for pause, level select, next level\n  - Swipe gestures not used to avoid conflicts with scrolling\n\n### Desktop Controls (Keyboard + Mouse)\n- **Movement**: \n  - **WASD** or **Arrow Keys**: Move player character in four directions (up, down, left, right)\n  - **Mouse Click**: Click on maze tiles to move player to that location (same pathfinding as mobile)\n- **Math Problem Interaction**:\n  - **Mouse Click**: Click answer buttons to select\n  - **Number Keys (1-4)**: Quick select answers (1 = top answer, 4 = bottom answer)\n  - **Enter**: Confirm selected answer (if pre-selected with number keys)\n- **UI Navigation**:\n  - **ESC**: Pause game\n  - **Mouse Click**: All menu buttons\n\n### Input Handling Details\n- **Prevent Default**: Prevent browser scrolling for arrow keys and spacebar\n- **Clear on Blur**: Clear all input states when window loses focus\n- **Touch ID Tracking**: Not needed (no joysticks, simple tap-to-move)\n- **Pathfinding**: Use A* or similar algorithm for automatic movement to tapped/clicked locations\n- **Movement Speed**: Smooth, consistent movement speed (approximately 4 tiles per second)\n- **Door Interaction**: Automatically trigger math problem when player reaches a locked door\n\n### Math Problem Modal\nWhen a player reaches a locked door:\n1. Game pauses (player cannot move)\n2. Modal appears with:\n   - Math problem displayed prominently at top\n   - 4 multiple-choice answer buttons (one correct, three incorrect)\n   - Buttons arranged vertically for easy thumb access on mobile\n   - \"Close\" button to cancel (returns player to previous position)\n3. Player selects an answer\n4. Immediate feedback:\n   - **Correct**: Green flash, door unlocks, modal closes, player continues\n   - **Incorrect**: Red flash, shake animation, try again (or penalty based on settings)\n5. Modal remains until correct answer is given or player closes it\n\n### Control Simplicity\n- No simultaneous actions required\n- Single-tap/click for all interactions\n- Clear visual feedback for all actions\n- No complex gestures or multi-touch required\n\n## Art Style and Visual Theme\n\n**Math Maze Millennium** features a clean, modern, colorful aesthetic that's both visually appealing and highly functional for gameplay.\n\n### Overall Art Style\n- **Style**: Flat design with subtle gradients and shadows for depth\n- **Mood**: Bright, encouraging, educational but fun\n- **Inspiration**: Modern mobile puzzle games (Monument Valley, Two Dots) meets classic maze games\n\n### Color Palette\n- **Primary Colors**:\n  - Maze walls: Dark gray (#2C3E50)\n  - Corridors: Light gray (#ECF0F1)\n  - Entrance: Vibrant green (#2ECC71)\n  - Exit: Vibrant red (#E74C3C)\n  - Locked doors: Bright blue (#3498DB)\n  - Player character: Warm orange/yellow (#F39C12)\n- **Accent Colors**:\n  - Correct answer: Green (#27AE60)\n  - Incorrect answer: Red (#C0392B)\n  - UI elements: Purple (#9B59B6) and teal (#1ABC9C)\n- **Background**: Soft gradient from light blue to white (#E8F4F8 to #FFFFFF)\n\n### Visual Elements\n\n**Maze Rendering**\n- Use **Canvas rendering** for the maze grid\n- Walls: Solid rectangles with subtle inner shadow for depth\n- Corridors: Lighter rectangles with very subtle texture\n- Grid lines: Thin, semi-transparent lines (#000000, 10% opacity) for spatial clarity\n- Entrance/Exit: Solid colored tiles with subtle glow effect\n\n**Player Character**\n- **Generated Asset**: Simple, friendly character sprite\n- Size: Approximately 80% of tile size for clear visibility\n- Style: Rounded, cartoon-like design\n- Animation: Idle bounce, walking animation (4 frames)\n- Viewing angle: Top-down, slight 3/4 perspective\n\n**Doors**\n- **Generated Asset**: Door sprite with lock icon\n- Size: Fits within single tile\n- Style: Matches flat design aesthetic\n- States: Locked (blue with lock), Unlocked (fades to corridor color)\n- Animation: Unlock animation (door slides open, lock disappears)\n\n**UI Elements**\n- **CSS rendering** for all UI components (buttons, modals, text)\n- Rounded corners (border-radius: 12px)\n- Subtle drop shadows for depth\n- Large, readable fonts (minimum 18px for body text, 24px+ for headings)\n- High contrast for accessibility\n\n**Math Problem Modal**\n- **CSS rendering** for modal container and buttons\n- Semi-transparent backdrop (#000000, 50% opacity)\n- White modal card with rounded corners and shadow\n- Math problem text: Large, bold, dark gray (#2C3E50)\n- Answer buttons: Large (minimum 96\u00d796px), colorful, with hover/active states\n\n**Visual Feedback**\n- **Particle effects** (Canvas): Small sparkles when door unlocks, confetti on level complete\n- **Color flashes**: Green for correct, red for incorrect\n- **Shake animations**: For incorrect answers\n- **Smooth transitions**: All state changes animated (door opening, modal appearing)\n\n### Asset Sizes and Consistency\n- **Tile Size**: 48\u00d748 pixels (base unit)\n- **Player Character**: ~38\u00d738 pixels (80% of tile)\n- **Door Sprite**: 48\u00d748 pixels (full tile)\n- **UI Icons**: 32\u00d732 pixels (pause, settings, etc.)\n- **Consistent Viewing Angle**: All assets use top-down perspective with slight 3/4 angle for depth\n- **Consistent Lighting**: Soft, diffused lighting from top-left\n\n### Typography\n- **Primary Font**: \"Fredoka One\" or similar rounded, friendly sans-serif (loaded from Google Fonts CDN)\n- **Secondary Font**: \"Roboto\" for body text and numbers\n- **Font Sizes**:\n  - Level title: 32px\n  - Math problem: 36px\n  - Answer buttons: 24px\n  - UI text: 18px\n  - Small labels: 14px", "persistent_storage_config": null, "game_metadata": {"game_id": "01KT15ZNBSM41N610KFES3MAQD", "title": "Math Maze Millennium", "description": "Navigate mazes by solving math problems to unlock doors\u20141000+ levels of increasing difficulty await!", "thumbnail_url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/thumbnail_a830b84d-2274-4507-a842-7dedf6e2f851.webp", "created_at": 1780303841.0, "updated_at": 1780403880492.7942, "art_style": null}}