{"asset_map": {"weapon_tier1_pistol": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/weapon_tier1_pistol_8d000cf4-802c-4343-a49a-a7d707c120e5.webp", "type": "image", "prompt": "Generate a game-ready compact semi-automatic pistol, 45-degree isometric view, right-facing. Simple utilitarian design with dark gray and black metallic finish. Realistic industrial sci-fi style with clean edges and subtle weathering. High resolution, detailed metallic texture, transparent background, front-view illustration.", "description": "Tier 1: Basic semi-automatic pistol in industrial sci-fi style", "subtype": "character", "aspect_ratio": [705, 682], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "weapon_tier2_revolver": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/weapon_tier2_revolver_ab0370d9-355b-4042-a0d3-0d7354eada04.webp", "type": "image", "prompt": "Generate a classic six-shooter revolver, 45-degree isometric view, right-facing. Features a brown wooden grip and polished silver chrome cylinder and barrel. More detailed than a basic pistol with visible cylinder chambers. Match the industrial sci-fi style and perspective of weapon_tier1_pistol. Realistic metallic texture, wood grain details, transparent background.", "description": "Tier 2: Classic revolver with wood grip and chrome finish", "subtype": "character", "aspect_ratio": [1015, 547], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "weapon_tier3_shotgun": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/weapon_tier3_shotgun_75b0c911-4fbc-473c-8521-2b18f7d46db3.webp", "type": "image", "prompt": "Generate a bulky pump-action shotgun, 45-degree isometric view, right-facing. Black metallic body with tactical rails and glowing cyan-blue energy accents along the barrel. Substantial and heavy appearance. Match the industrial sci-fi style of weapon_tier1_pistol. Realistic texture with glowing blue neon light effects, transparent background.", "description": "Tier 3: Pump-action shotgun with blue energy accents", "subtype": "character", "aspect_ratio": [1019, 648], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "weapon_tier4_rifle": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/weapon_tier4_rifle_a8072984-972e-4c6f-b99a-122d87fa5293.webp", "type": "image", "prompt": "Generate a sleek modern assault rifle, 45-degree isometric view, right-facing. Long barrel, tactical stock, and green glowing tech components on the scope and magazine. High-tech military aesthetic. Match the perspective and industrial sci-fi style of weapon_tier1_pistol. Detailed metallic texture, green illumination, transparent background.", "description": "Tier 4: Modern assault rifle with green tech details", "subtype": "character", "aspect_ratio": [1019, 707], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "weapon_tier5_sniper": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/weapon_tier5_sniper_680c2314-1ee1-4a37-93a3-2a5ba9dbb7eb.webp", "type": "image", "prompt": "Generate a long, sleek high-tech sniper rifle, 45-degree isometric view, right-facing. Visible bipod and a prominent scope glowing purple. Dark metallic body with purple energy lines running along the barrel. Very detailed and advanced design. Match the style and perspective of weapon_tier1_pistol. Transparent background.", "description": "Tier 5: High-tech sniper rifle with purple scope glow", "subtype": "character", "aspect_ratio": [1024, 656], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "weapon_tier6_machinegun": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/weapon_tier6_machinegun_8d9d5247-04f6-4508-be3f-66eac0f8740d.webp", "type": "image", "prompt": "Generate a bulky heavy machine gun, 45-degree isometric view, right-facing. Intimidating industrial design with a large barrel and a red glowing ammunition belt feeding into the side. Thick, heavy metallic construction. Match the style and perspective of weapon_tier1_pistol. Realistic sci-fi textures, red neon glow, transparent background.", "description": "Tier 6: Heavy machine gun with red ammunition belt", "subtype": "character", "aspect_ratio": [1023, 986], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "weapon_tier7_launcher": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/weapon_tier7_launcher_349d6edd-d37d-46f1-adff-f931d31159e0.webp", "type": "image", "prompt": "Generate a massive futuristic rocket launcher, 45-degree isometric view, right-facing. Wide barrel with visible orange and gold glowing missiles loaded inside. Advanced targeting sensors and heavy industrial plating. Ultimate weapon appearance. Match the style and perspective of weapon_tier1_pistol. Intense orange/gold energy effects, transparent background.", "description": "Tier 7: Futuristic rocket launcher with gold/orange missiles", "subtype": "character", "aspect_ratio": [1006, 726], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "merge_particle": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/merge_particle_b35e93cd-1ba8-4a11-963e-02db2af33a23.webp", "type": "image", "prompt": "Generate a small, bright glowing starburst spark. Radially symmetric shape with a white-hot center and bright yellow and orange outer edges. High energy particle effect, clean bloom glow, transparent background, 2D game asset style.", "description": "Bright starburst spark for merge effects", "subtype": "effect", "aspect_ratio": [699, 731], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "grid_background": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/grid_background_33d7a00c-b980-44c9-b985-fd1e57ce9536.webp", "type": "image", "prompt": "Generate a 9:16 tall image of a dark industrial military background. Dark gray and black metallic panels with subtle rivets, weathered textures, and faint tech patterns. Minimalist and non-distracting design. Clean, polished sci-fi aesthetic with subtle depth. Dark color palette (#1a1a1a to #2d2d2d).", "description": "Dark industrial military background for the game screen", "subtype": "background", "aspect_ratio": [1024, 1536], "compression_suggestion": "original", "duration_secs": null, "loop": null, "animation": null}, "merge_sound": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/merge_sound_18bc6536-a41c-480b-ae88-683d75dfa6d0.mp3", "type": "audio", "prompt": "A satisfying high-quality weapon merge sound effect. It begins with a heavy metallic 'clang' followed immediately by a futuristic energy 'whoosh' and a brief, bright rising power-up synth tone. Industrial and punchy, conveying a sense of upgrade and reward. High-fidelity, clean layers.", "description": "Satisfying industrial-sci-fi sound for merging two weapons into a higher tier.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 1.37, "loop": false, "animation": null}, "spawn_sound": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/spawn_sound_87ad5a7e-4ef6-4ba7-8dbc-b06527d003e5.mp3", "type": "audio", "prompt": "A brief sci-fi materialization sound effect. A quick digital 'whoosh' with high-frequency electronic chirps, suggesting a weapon teleporting or 3D printing into existence. Short, snappy, and clean for frequent use.", "description": "Quick digital energy sound for when a new weapon part appears on the grid.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 1.5, "loop": false, "animation": null}, "invalid_move_sound": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/invalid_move_sound_a0a1293e-eea0-4bf3-85c0-847a3771f5db.mp3", "type": "audio", "prompt": "A short, low-pitched mechanical 'thunk' combined with a very brief electronic 'buzz'. Designed as negative UI feedback for an error. Subtle, non-intrusive, industrial character, clearly indicating 'move not allowed'.", "description": "Subtle negative feedback sound for incorrect merge attempts.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 2.0, "loop": false, "animation": null}, "game_over_sound": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/game_over_sound_5999e7d6-93e1-4263-88a0-6032f08f513c.mp3", "type": "audio", "prompt": "A dramatic game over sound effect featuring a descending electronic sequence. Includes heavy metallic resonance and a fading industrial drone. Conveys finality and the end of a mission. Dark sci-fi atmosphere.", "description": "Dramatic final sequence sound for when the grid is full.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 2.07, "loop": false, "animation": null}, "background_music": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/background_music_51b8f3d5-5afc-4dcc-8ead-ffc90b7a3575.mp3", "type": "audio", "prompt": "Energetic industrial sci-fi electronic background music. 120 BPM, driving synthetic percussion, rhythmic metallic textures, and pulsing basslines. Incorporates subtle neon-synth melodies. Should be engaging and maintain high-energy focus without distracting. Seamless loop, no vocals, high-tech atmosphere.", "description": "Energetic industrial sci-fi loop for gameplay background.", "subtype": "music", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 30.0, "loop": true, "animation": null}, "button_click_sound": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/button_click_sound_1ec7b357-57d3-4b1c-9d7b-da170b4d6e08.mp3", "type": "audio", "prompt": "A clean, ultra-short UI button click. Features a crisp mechanical 'snap' with a slight metallic ring. Minimalist and sharp, designed for immediate tactile feedback in a military-style interface.", "description": "Crisp metallic UI click sound for menu and button interactions.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 1.12, "loop": false, "animation": null}, "weapon_tier8_plasma": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/weapon_tier8_plasma_83290390-3e1f-4fbe-b8eb-38ea14401c29.webp", "type": "image", "prompt": "Generate a heavy Plasma Cannon, a bulky futuristic weapon with glowing blue energy conduits and a large muzzle. Industrial sci-fi style, front-side view, clean polished game asset, matching the design aesthetic of the existing weapon set. Professional quality, high resolution, transparent background.", "description": "Tier 8: A bulky, heavy plasma cannon with glowing blue energy conduits in a futuristic industrial sci-fi style.", "subtype": "object", "aspect_ratio": [1230, 759], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "weapon_tier9_railgun": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/weapon_tier9_railgun_4ff77173-e5af-44a2-9722-6fe8b684e717.webp", "type": "image", "prompt": "Generate a professional game asset of a Tier 9 Sleek Railgun. The design features a long, thin barrel with visible electromagnetic rails and small electric blue arcs jumping between them. Use a modern military-tech aesthetic that matches the industrial sci-fi style of image 1. Front-side view, high resolution, clean edges, transparent background.", "description": "Tier 9: High-tech sleek railgun with electromagnetic rails and blue electric arcs", "subtype": "object", "aspect_ratio": [1210, 768], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "weapon_tier10_nuke": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/weapon_tier10_nuke_a09222d0-85d2-4b34-9916-af2c6d5d02ca.webp", "type": "image", "prompt": "Generate a Tier 10 Tactical Mini-Nuke Launcher for a merge game. A fat, rounded, bulky weapon launcher featuring hazard yellow stripes and visible radiation warning symbols. The design should follow an industrial sci-fi military style, consistent with the existing arsenal. Professional game asset, high-resolution, front-side view, transparent background, detailed metallic textures, powerful and dangerous appearance.", "description": "Tier 10: Tactical Mini-Nuke Launcher with hazard yellow stripes and industrial sci-fi aesthetic.", "subtype": "object", "aspect_ratio": [1216, 743], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "weapon_tier11_omega": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/weapon_tier11_omega_05eea0e8-c6aa-40ab-bba0-91cf8680dbbc.webp", "type": "image", "prompt": "Generate a high-resolution, front-side view of a Tier 11 'Omega Weapon' firearm. The design should be the ultimate evolution of the industrial sci-fi style seen in previous tiers, blending ornate golden plating with dark matter technology. Feature glowing purple energy cores and floating crystal shards. The weapon should look god-like and incredibly powerful, with professional game asset quality, sharp details, and a transparent background.", "description": "The ultimate Tier 11 Omega Weapon, combining crystal and dark matter technology with a god-like aesthetic.", "subtype": "object", "aspect_ratio": [1239, 754], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "theme_cybercity": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/theme_cybercity_e46bea95-bf37-4cb4-aaad-a00db8ffba81.webp", "type": "image", "prompt": "Generate a 9:16 tall image of a realistic cyberpunk city at night. The scene features rainy asphalt streets reflecting vibrant neon signs in pink, blue, and orange. Towering high-tech skyscrapers with glowing windows fill the background. Highly detailed, photographic sci-fi style, cinematic lighting, 8k resolution, professional quality.", "description": "A vertical cyberpunk city background with neon lights and rainy streets for the game environment", "subtype": "background", "aspect_ratio": [1024, 1536], "compression_suggestion": "original", "duration_secs": null, "loop": null, "animation": null}, "theme_military_base": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/theme_military_base_8ec51bcd-6e66-4fa1-b360-3b5c156ef9f2.webp", "type": "image", "prompt": "Generate a 9:16 tall image of a realistic military base interior. The scene features a gritty aircraft hangar with fighter jets, stacked crates of ammunition, and harsh industrial overhead lighting. Professional, detailed, and cinematic quality with a cold, military-tech aesthetic to match the industrial sci-fi theme.", "description": "A vertical realistic military base hangar background with jets and industrial lighting.", "subtype": "background", "aspect_ratio": [832, 1248], "compression_suggestion": "original", "duration_secs": null, "loop": null, "animation": null}, "trash_icon": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/trash_icon_73ab3385-e526-493c-a3b4-e3e6a30ce924.webp", "type": "image", "prompt": "Generate a professional game UI icon of a futuristic glowing red trash can. The design should feature an industrial sci-fi aesthetic with metallic surfaces, neon red energy accents, and a 'delete' symbol. Clean, polished look, front-facing, high resolution, transparent background, matching the industrial military style of the existing game assets.", "description": "A futuristic glowing red trash can icon for deleting weapons in an industrial sci-fi style.", "subtype": "ui", "aspect_ratio": [563, 820], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "weapon_tier12_antimatter": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/weapon_tier12_antimatter_85525f0b-67b6-4750-a871-b359a962ee78.webp", "type": "image", "prompt": "Generate a side view of a Tier 12 Antimatter Pulse Rifle in an industrial sci-fi style. The weapon has a sleek white and dark purple color scheme with visible energy distortion effects or ripples in the air around the barrel. High-quality game asset, polished finish, matching the industrial aesthetic of the previous weapon tiers, transparent background, professional 2D game art.", "description": "Tier 12: Antimatter Pulse Rifle for the weapon merge progression, featuring white and dark purple distortion effects.", "subtype": "object", "aspect_ratio": [1237, 762], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "weapon_tier13_singularity": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/weapon_tier13_singularity_20558048-de6d-4d4b-853b-c34516548424.webp", "type": "image", "prompt": "Generate a professional game asset of a Tier 13 Singularity Projector heavy weapon. The design features a massive, heavy frame made of dark obsidian metal with an industrial sci-fi aesthetic. At its core, a rotating containment field houses a tiny, glowing black hole with intense violet energy arcs swirling around it. Side view, right-facing, clean polished look, high resolution, detailed mechanical parts, transparent background.", "description": "Tier 13 Singularity Projector heavy weapon with a black hole core for the merge game progression.", "subtype": "object", "aspect_ratio": [1231, 763], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "weapon_tier14_cosmic": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/weapon_tier14_cosmic_f9d1110e-5409-4821-ba99-6f1ec655ab95.webp", "type": "image", "prompt": "Generate a side-view illustration of a Tier 14 Cosmic Obliterator weapon. A massive, ornate heavy firearm made from captured starlight and nebulae, featuring intricate gold filigree and cosmic blue energy accents. The design should maintain an industrial sci-fi silhouette consistent with a high-tech weapon. High resolution, detailed textures, glowing energy effects, transparent background, professional game asset style.", "description": "Tier 14 Cosmic Obliterator weapon made of starlight and nebulae with gold and cosmic blue accents.", "subtype": "object", "aspect_ratio": [1226, 752], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "weapon_tier15_infinity": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/weapon_tier15_infinity_1a4f9c3f-bb50-4d8a-bb8f-551e7604fd15.webp", "type": "image", "prompt": "Generate a side-view illustration of the Tier 15 'Infinite Paradox' weapon. The design features a floating central core of pure brilliant white light, surrounded by concentric rotating golden rings and floating geometric shapes. It should have a transcendental, god-like aesthetic while maintaining an industrial sci-fi foundation. Use high-quality polished metal textures and glowing energy effects. Ensure the style matches the high-tier weapons like weapon_tier11_omega and weapon_tier14_cosmic. Side view, transparent background, highly detailed, professional game asset.", "description": "Tier 15 Infinite Paradox: The ultimate transcendental weapon featuring a glowing white core and rotating golden rings.", "subtype": "object", "aspect_ratio": [1246, 760], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "theme_desert_outpost": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/theme_desert_outpost_fefa5786-e776-4312-baa3-704333c3d320.webp", "type": "image", "prompt": "Generate a 9:16 tall image of a realistic desert military outpost in an industrial sci-fi style. The scene features rolling sand dunes, sun-bleached metallic structures, and visible heat haze. The atmosphere is arid and harsh with high-contrast sunlight and industrial details like antennas and fortified walls. Match the professional quality and industrial sci-fi aesthetic of the existing game backgrounds.", "description": "A vertical realistic desert military outpost background for the game environment", "subtype": "background", "aspect_ratio": [832, 1248], "compression_suggestion": "original", "duration_secs": null, "loop": null, "animation": null}, "theme_arctic_station": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/theme_arctic_station_fa693985-81ad-4644-9af3-9cb9c8519fdd.webp", "type": "image", "prompt": "Generate a 9:16 tall image of a realistic arctic research station. The scene features a heavy blizzard, thick snow, and jagged ice formations. Industrial sci-fi architecture with glowing blue laboratory windows and high-tech equipment visible through the storm. Cold, atmospheric lighting with high detail and a cinematic feel, suitable for a vertical game background.", "description": "A vertical arctic research station background with a blizzard and glowing blue labs in an industrial sci-fi style.", "subtype": "background", "aspect_ratio": [1024, 1536], "compression_suggestion": "original", "duration_secs": null, "loop": null, "animation": null}, "theme_deep_sea": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/theme_deep_sea_7af79c15-046e-4f1d-a6fc-c42fc850db0a.webp", "type": "image", "prompt": "Generate a 9:16 tall image of a realistic deep-sea laboratory. The scene features pressurized metal walls with an industrial sci-fi aesthetic, looking out into dark water filled with bioluminescent sea life. High-tech underwater research station with glowing consoles, pipes, and thick reinforced glass. Atmospheric lighting with deep blues and vibrant bioluminescent accents. Professional quality, highly detailed, vertical orientation for mobile gaming.", "description": "A vertical deep-sea laboratory background with bioluminescent elements and industrial sci-fi architecture.", "subtype": "background", "aspect_ratio": [1024, 1536], "compression_suggestion": "original", "duration_secs": null, "loop": null, "animation": null}, "theme_volcano_forge": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/theme_volcano_forge_c5e93f7f-1d78-482f-a06b-90399ada9603.webp", "type": "image", "prompt": "Generate a 9:16 tall image of a realistic volcanic forge background for a mobile game. The scene features glowing orange flowing lava, heat ripples distorting the air, and heavy industrial machinery integrated into the volcanic rock. The style is industrial sci-fi, matching the aesthetic of a military facility. High resolution, detailed textures, cinematic lighting, and a vertical perspective.", "description": "A vertical volcanic forge background with flowing lava and heavy industrial machinery in a sci-fi style.", "subtype": "background", "aspect_ratio": [1024, 1536], "compression_suggestion": "original", "duration_secs": null, "loop": null, "animation": null}, "theme_dyson_sphere": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/theme_dyson_sphere_83684021-5336-4683-a7ce-b77955a6c153.webp", "type": "image", "prompt": "Generate a 9:16 tall image of a realistic Dyson Sphere interior. The view shows the massive, glowing surface of a star seen through a gargantuan hexagonal lattice structure. Use a grand sci-fi scale with industrial sci-fi details, intricate mega-structures, and realistic cinematic lighting. Match the industrial sci-fi aesthetic of the existing game themes like theme_military_base and theme_deep_sea.", "description": "A vertical realistic Dyson Sphere interior background for the game environment.", "subtype": "background", "aspect_ratio": [832, 1248], "compression_suggestion": "original", "duration_secs": null, "loop": null, "animation": null}, "theme_nexus": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/theme_nexus_65102653-205a-405b-8a75-fc0c6aa54f1d.webp", "type": "image", "prompt": "Generate a 9:16 tall vertical background of a realistic multiversal nexus. The scene should feature shimmering interdimensional portals, complex fractals, and swirling cosmic energy. Use a grand, high-tech industrial sci-fi aesthetic that feels like the ultimate destination. Cinematic lighting, vibrant colors with deep space blacks, highly detailed, professional quality.", "description": "The ultimate multiversal nexus background with shimmering portals and cosmic energy for the final game environment.", "subtype": "background", "aspect_ratio": [1024, 1536], "compression_suggestion": "original", "duration_secs": null, "loop": null, "animation": null}}, "game_code": "<!DOCTYPE html>\n<html>\n<head>\n    <title>The Ultimate Gun Merge</title>\n    <link href=\"https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Inter:wght@400;600;700&display=swap\" rel=\"stylesheet\">\n    <style>\n        :root {\n            --bg-dark: #1a1a1a;\n            --bg-medium: #252525;\n            --bg-light: #2d2d2d;\n            --accent-cyan: #00d9ff;\n            --accent-orange: #ff6b35;\n            --accent-yellow: #ffd700;\n            --text-light: #ffffff;\n            --text-dim: #888888;\n            \n            --transition-fast: 150ms;\n            --transition-normal: 300ms;\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            --space-xs: 8px;\n            --space-sm: 16px;\n            --space-md: 24px;\n            --space-lg: 32px;\n            --space-xl: 48px;\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: 'Inter', sans-serif;\n            background-color: var(--bg-dark);\n        }\n\n        #game-world {\n            position: relative;\n            width: 100%;\n            height: 100%;\n            overflow: hidden;\n        }\n\n        #background-canvas {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            z-index: 0;\n        }\n\n        #game-canvas {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            z-index: 1;\n        }\n\n        .ui-overlay {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            pointer-events: none;\n            z-index: 10;\n        }\n\n        .ui-overlay > * {\n            pointer-events: auto;\n        }\n\n        /* Top Section - Score Display */\n        .score-container {\n            position: absolute;\n            top: var(--space-md);\n            left: 50%;\n            transform: translateX(-50%);\n            text-align: center;\n            color: var(--text-light);\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            width: 100%;\n        }\n\n        .stats-row {\n            display: flex;\n            justify-content: center;\n            gap: var(--space-xl);\n            margin-bottom: var(--space-xs);\n        }\n\n        .stat-item {\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n        }\n\n        .stat-label {\n            font-family: 'Orbitron', sans-serif;\n            font-size: 14px;\n            font-weight: 400;\n            color: var(--accent-cyan);\n            text-transform: uppercase;\n            letter-spacing: 2px;\n            margin-bottom: 2px;\n        }\n\n        .stat-value {\n            font-family: 'Orbitron', sans-serif;\n            font-size: 32px;\n            font-weight: 900;\n            color: var(--text-light);\n            text-shadow: 0 0 15px rgba(0, 217, 255, 0.4);\n        }\n\n        .money-value {\n            color: var(--accent-yellow);\n            text-shadow: 0 0 15px rgba(255, 215, 0, 0.4);\n        }\n\n        .income-display {\n            font-family: 'Orbitron', sans-serif;\n            font-size: 14px;\n            color: var(--accent-cyan);\n            margin-top: 4px;\n        }\n\n        .high-score {\n            font-family: 'Orbitron', sans-serif;\n            font-size: 14px;\n            font-weight: 400;\n            color: var(--text-dim);\n        }\n\n        .high-score span {\n            color: var(--accent-yellow);\n        }\n\n        /* Bottom Section - Controls */\n        .controls-container {\n            position: absolute;\n            bottom: var(--space-lg);\n            left: 50%;\n            transform: translateX(-50%);\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            gap: var(--space-sm);\n        }\n\n        .spawn-button {\n            width: 240px;\n            height: 80px;\n            background: linear-gradient(180deg, var(--accent-cyan), #0099cc);\n            border: 3px solid rgba(255, 255, 255, 0.3);\n            border-radius: 12px;\n            color: var(--bg-dark);\n            font-family: 'Orbitron', sans-serif;\n            font-size: 28px;\n            font-weight: 700;\n            text-transform: uppercase;\n            letter-spacing: 2px;\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 20px rgba(0, 217, 255, 0.4),\n                        inset 0 1px 0 rgba(255, 255, 255, 0.3);\n        }\n\n        .spawn-button:hover {\n            transform: scale(1.05);\n            box-shadow: 0 6px 30px rgba(0, 217, 255, 0.6),\n                        inset 0 1px 0 rgba(255, 255, 255, 0.3);\n        }\n\n        .spawn-button:active {\n            transform: scale(0.95);\n        }\n\n        .spawn-button-cost {\n            font-size: 16px;\n            display: block;\n            margin-top: 4px;\n            color: var(--accent-yellow);\n            opacity: 0.9;\n        }\n\n        .spawn-button:disabled .spawn-button-cost {\n            color: var(--text-dim);\n        }\n\n        .spawn-button.shake {\n            animation: shake 0.5s ease-in-out;\n        }\n\n        @keyframes shake {\n            0%, 100% { transform: translateX(0); }\n            20% { transform: translateX(-10px); }\n            40% { transform: translateX(10px); }\n            60% { transform: translateX(-10px); }\n            80% { transform: translateX(10px); }\n        }\n\n        .restart-button {\n            width: 140px;\n            height: 48px;\n            background: transparent;\n            border: 2px solid var(--accent-orange);\n            border-radius: 8px;\n            color: var(--accent-orange);\n            font-family: 'Orbitron', sans-serif;\n            font-size: 16px;\n            font-weight: 600;\n            text-transform: uppercase;\n            letter-spacing: 1px;\n            cursor: pointer;\n            transition: all var(--transition-fast) var(--ease-out);\n        }\n\n        .restart-button:hover {\n            background: var(--accent-orange);\n            color: var(--bg-dark);\n        }\n\n        .restart-button:active {\n            transform: scale(0.95);\n        }\n\n        /* Game Over Overlay */\n        .game-over-overlay {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            background: rgba(0, 0, 0, 0.85);\n            display: flex;\n            flex-direction: column;\n            justify-content: center;\n            align-items: center;\n            opacity: 0;\n            visibility: hidden;\n            transition: opacity var(--transition-normal) var(--ease-out),\n                        visibility var(--transition-normal);\n            z-index: 100;\n        }\n\n        .game-over-overlay.visible {\n            opacity: 1;\n            visibility: visible;\n        }\n\n        .game-over-title {\n            font-family: 'Orbitron', sans-serif;\n            font-size: 48px;\n            font-weight: 900;\n            color: var(--accent-orange);\n            text-transform: uppercase;\n            letter-spacing: 4px;\n            margin-bottom: var(--space-md);\n            text-shadow: 0 0 30px rgba(255, 107, 53, 0.7);\n        }\n\n        .game-over-score {\n            font-family: 'Orbitron', sans-serif;\n            font-size: 32px;\n            font-weight: 700;\n            color: var(--text-light);\n            margin-bottom: var(--space-xs);\n        }\n\n        .game-over-high {\n            font-family: 'Inter', sans-serif;\n            font-size: 20px;\n            color: var(--accent-yellow);\n            margin-bottom: var(--space-xl);\n        }\n\n        .play-again-button {\n            width: 240px;\n            height: 72px;\n            background: linear-gradient(180deg, var(--accent-cyan), #0099cc);\n            border: none;\n            border-radius: 12px;\n            color: var(--bg-dark);\n            font-family: 'Orbitron', sans-serif;\n            font-size: 24px;\n            font-weight: 700;\n            text-transform: uppercase;\n            letter-spacing: 2px;\n            cursor: pointer;\n            transition: transform var(--transition-fast) var(--ease-bounce);\n            box-shadow: 0 4px 20px rgba(0, 217, 255, 0.4);\n        }\n\n        .play-again-button:hover {\n            transform: scale(1.05);\n        }\n\n        .play-again-button:active {\n            transform: scale(0.95);\n        }\n\n        /* Merge Message */\n        .merge-message {\n            position: absolute;\n            top: 180px;\n            left: 50%;\n            transform: translateX(-50%);\n            font-family: 'Orbitron', sans-serif;\n            font-size: 24px;\n            font-weight: 700;\n            color: var(--accent-yellow);\n            text-shadow: 0 0 15px rgba(255, 215, 0, 0.8);\n            opacity: 0;\n            pointer-events: none;\n            transition: opacity 0.2s ease-out;\n        }\n\n        .merge-message.visible {\n            opacity: 1;\n            animation: floatUp 1s ease-out forwards;\n        }\n\n        @keyframes floatUp {\n            0% { transform: translateX(-50%) translateY(0); opacity: 1; }\n            100% { transform: translateX(-50%) translateY(-30px); opacity: 0; }\n        }\n\n        /* Delete/Trash Button */\n        #trash-zone {\n            position: absolute;\n            left: 20px;\n            bottom: 20px;\n            width: 120px;\n            height: 120px;\n            border: 3px dashed rgba(255, 68, 68, 0.5);\n            border-radius: 16px;\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            justify-content: center;\n            background: rgba(255, 0, 0, 0.1);\n            pointer-events: none;\n            transition: all 0.2s;\n            z-index: 11;\n        }\n\n        #trash-zone.active {\n            background: rgba(255, 0, 0, 0.4);\n            transform: scale(1.1);\n            border-color: #ff4444;\n            box-shadow: 0 0 20px rgba(255, 0, 0, 0.3);\n        }\n\n        #trash-label {\n            font-family: 'Orbitron', sans-serif;\n            font-size: 12px;\n            color: #ff4444;\n            margin-top: 5px;\n            text-transform: uppercase;\n            font-weight: 700;\n        }\n    </style>\n</head>\n<body>\n    <div id=\"game-world\">\n        <canvas id=\"background-canvas\"></canvas>\n        <canvas id=\"game-canvas\"></canvas>\n        <div class=\"ui-overlay\" id=\"ui-overlay\">\n            <div class=\"score-container\">\n                <div class=\"stats-row\">\n                    <div class=\"stat-item\">\n                        <div class=\"stat-label\">Score</div>\n                        <div class=\"stat-value\" id=\"score-display\">0</div>\n                    </div>\n                    <div class=\"stat-item\">\n                        <div class=\"stat-label\">Credits</div>\n                        <div class=\"stat-value money-value\" id=\"money-display\">\u09f30</div>\n                        <div class=\"income-display\" id=\"income-display\">+\u09f30/s</div>\n                    </div>\n                </div>\n                <div class=\"high-score\">Best: <span id=\"high-score-display\">0</span></div>\n            </div>\n            \n            <div id=\"trash-zone\">\n                <img id=\"trash-img\" src=\"\" style=\"width: 60px; height: 60px; opacity: 0.7;\">\n                <div id=\"trash-label\">Delete</div>\n            </div>\n\n            <div class=\"controls-container\">\n                <button class=\"spawn-button\" id=\"spawn-button\">\n                    <span class=\"spawn-button-label\">SPAWN</span>\n                    <span class=\"spawn-button-cost\" id=\"spawn-cost-display\">\u09f310</span>\n                </button>\n                <div style=\"display: flex; gap: 10px;\">\n                    <button class=\"restart-button\" id=\"index-button\" style=\"border-color: var(--accent-cyan); color: var(--accent-cyan);\">Index</button>\n                    <button class=\"restart-button\" id=\"shop-button\" style=\"border-color: var(--accent-yellow); color: var(--accent-yellow);\">Shop</button>\n                </div>\n            </div>\n\n            <!-- Index Overlay -->\n            <div id=\"index-overlay\" class=\"game-over-overlay\">\n                <h2 class=\"game-over-title\" style=\"font-size: 32px;\">Weapon Index</h2>\n                <div id=\"index-list\" style=\"display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; max-height: 600px; overflow-y: auto; padding: 20px; width: 90%;\"></div>\n                <button class=\"play-again-button\" id=\"close-index\" style=\"margin-top: 20px;\">Close</button>\n            </div>\n\n            <!-- Shop Overlay -->\n            <div id=\"shop-overlay\" class=\"game-over-overlay\">\n                <h2 class=\"game-over-title\" style=\"font-size: 32px;\">Theme Shop</h2>\n                <div id=\"shop-list\" style=\"display: flex; flex-direction: column; gap: 15px; width: 90%; padding: 20px;\"></div>\n                <button class=\"play-again-button\" id=\"close-shop\" style=\"margin-top: 20px;\">Close</button>\n            </div>\n            \n            <div class=\"merge-message\" id=\"merge-message\">+100</div>\n            \n            <div class=\"game-over-overlay\" id=\"game-over-overlay\">\n                <div class=\"game-over-title\">Game Over</div>\n                <div class=\"game-over-score\" id=\"final-score\">Score: 0</div>\n                <div class=\"game-over-high\" id=\"final-high\">New High Score!</div>\n                <button class=\"play-again-button\" id=\"play-again-button\">Play Again</button>\n            </div>\n            \n            <div class=\"edit-indicator\" id=\"edit-indicator\" style=\"display: none;\">Edit Mode</div>\n        </div>\n    </div>\n\n    <script>\n        /**\n         * GUN FUSION - Merge Puzzle Game\n         * \n         * Combine identical weapon parts on a grid to create increasingly powerful firearms.\n         * Drag and drop matching weapons to merge them into the next tier.\n         * Manage your grid space strategically to achieve the highest score!\n         * \n         * Weapon Tiers: Pistol \u2192 Revolver \u2192 Shotgun \u2192 Rifle \u2192 Sniper \u2192 Machine Gun \u2192 Rocket Launcher\n         */\n\n        async function run(mode) {\n            const gameWorld = document.getElementById('game-world');\n            const bgCanvas = document.getElementById('background-canvas');\n            const bgCtx = bgCanvas.getContext('2d');\n            const canvas = document.getElementById('game-canvas');\n            const ctx = canvas.getContext('2d');\n            \n            // UI Elements\n            const scoreDisplay = document.getElementById('score-display');\n            const moneyDisplay = document.getElementById('money-display');\n            const incomeDisplay = document.getElementById('income-display');\n            const highScoreDisplay = document.getElementById('high-score-display');\n            const spawnButton = document.getElementById('spawn-button');\n            const spawnCostDisplay = document.getElementById('spawn-cost-display');\n            const indexButton = document.getElementById('index-button');\n            const shopButton = document.getElementById('shop-button');\n            const trashZone = document.getElementById('trash-zone');\n            const trashImg = document.getElementById('trash-img');\n            \n            const indexOverlay = document.getElementById('index-overlay');\n            const indexList = document.getElementById('index-list');\n            const closeIndex = document.getElementById('close-index');\n            \n            const shopOverlay = document.getElementById('shop-overlay');\n            const shopList = document.getElementById('shop-list');\n            const closeShop = document.getElementById('close-shop');\n\n            const mergeMessage = document.getElementById('merge-message');\n            const gameOverOverlay = document.getElementById('game-over-overlay');\n            const finalScore = document.getElementById('final-score');\n            const finalHigh = document.getElementById('final-high');\n            const playAgainButton = document.getElementById('play-again-button');\n            const editIndicator = document.getElementById('edit-indicator');\n            \n            // Canvas setup\n            function resizeCanvas() {\n                bgCanvas.width = canvas.width = gameWorld.clientWidth;\n                bgCanvas.height = canvas.height = gameWorld.clientHeight;\n            }\n            resizeCanvas();\n            window.addEventListener('resize', resizeCanvas);\n            \n            // Config reference\n            const config = window.gameConfig;\n            \n            // ==================== ASSET LOADING ====================\n            const assetCache = {};\n            const audioContext = new (window.AudioContext || window.webkitAudioContext)();\n            const audioBuffers = {};\n            \n            const imageAssets = [\n                'weapon_tier1_pistol',\n                'weapon_tier2_revolver',\n                'weapon_tier3_shotgun',\n                'weapon_tier4_rifle',\n                'weapon_tier5_sniper',\n                'weapon_tier6_machinegun',\n                'weapon_tier7_launcher',\n                'weapon_tier8_plasma',\n                'weapon_tier9_railgun',\n                'weapon_tier10_nuke',\n                'weapon_tier11_omega',\n                'weapon_tier12_antimatter',\n                'weapon_tier13_singularity',\n                'weapon_tier14_cosmic',\n                'weapon_tier15_infinity',\n                'merge_particle',\n                'grid_background',\n                'theme_cybercity',\n                'theme_military_base',\n                'theme_desert_outpost',\n                'theme_arctic_station',\n                'theme_deep_sea',\n                'theme_volcano_forge',\n                'theme_dyson_sphere',\n                'theme_nexus',\n                'trash_icon'\n            ];\n            \n            const audioAssets = [\n                'merge_sound',\n                'spawn_sound',\n                'invalid_move_sound',\n                'game_over_sound',\n                'background_music',\n                'button_click_sound'\n            ];\n            \n            const weaponAssetIds = [\n                'weapon_tier1_pistol',\n                'weapon_tier2_revolver',\n                'weapon_tier3_shotgun',\n                'weapon_tier4_rifle',\n                'weapon_tier5_sniper',\n                'weapon_tier6_machinegun',\n                'weapon_tier7_launcher',\n                'weapon_tier8_plasma',\n                'weapon_tier9_railgun',\n                'weapon_tier10_nuke',\n                'weapon_tier11_omega',\n                'weapon_tier12_antimatter',\n                'weapon_tier13_singularity',\n                'weapon_tier14_cosmic',\n                'weapon_tier15_infinity'\n            ];\n            // Extend asset IDs to handle up to 200 tiers by cycling or generic names\n            for (let i = weaponAssetIds.length; i < 200; i++) {\n                weaponAssetIds.push(`weapon_tier${i+1}`);\n            }\n            \n            async function preloadAssets() {\n                const imagePromises = imageAssets.map(id => {\n                    return new Promise((resolve) => {\n                        const assetInfo = lib.getAsset(id);\n                        if (assetInfo && assetInfo.url) {\n                            const img = new Image();\n                            img.onload = () => {\n                                assetCache[id] = { img, info: assetInfo };\n                                resolve();\n                            };\n                            img.onerror = () => {\n                                lib.log(`Failed to load image: ${id}`);\n                                resolve();\n                            };\n                            img.src = assetInfo.url;\n                        } else {\n                            resolve();\n                        }\n                    });\n                });\n                \n                const audioPromises = audioAssets.map(id => {\n                    return new Promise((resolve) => {\n                        const assetInfo = lib.getAsset(id);\n                        if (assetInfo && assetInfo.url) {\n                            fetch(assetInfo.url)\n                                .then(response => response.arrayBuffer())\n                                .then(buffer => audioContext.decodeAudioData(buffer))\n                                .then(audioBuffer => {\n                                    audioBuffers[id] = audioBuffer;\n                                    resolve();\n                                })\n                                .catch(() => {\n                                    lib.log(`Failed to load audio: ${id}`);\n                                    resolve();\n                                });\n                        } else {\n                            resolve();\n                        }\n                    });\n                });\n                \n                await Promise.all([...imagePromises, ...audioPromises]);\n            }\n            \n            // ==================== AUDIO SYSTEM ====================\n            let musicSource = null;\n            let musicGainNode = null;\n            \n            function playSound(id, volume = 1) {\n                if (!audioBuffers[id]) return;\n                try {\n                    const source = audioContext.createBufferSource();\n                    const gainNode = audioContext.createGain();\n                    source.buffer = audioBuffers[id];\n                    gainNode.gain.value = volume * (config.settings.sfxVolume / 100);\n                    source.connect(gainNode);\n                    gainNode.connect(audioContext.destination);\n                    source.start(0);\n                } catch (e) {\n                    lib.log(`Error playing sound: ${e}`);\n                }\n            }\n            \n            function playMusic() {\n                if (!audioBuffers['background_music'] || musicSource) return;\n                try {\n                    musicSource = audioContext.createBufferSource();\n                    musicGainNode = audioContext.createGain();\n                    musicSource.buffer = audioBuffers['background_music'];\n                    musicSource.loop = true;\n                    musicGainNode.gain.value = config.settings.musicVolume / 100;\n                    musicSource.connect(musicGainNode);\n                    musicGainNode.connect(audioContext.destination);\n                    musicSource.start(0);\n                } catch (e) {\n                    lib.log(`Error playing music: ${e}`);\n                }\n            }\n            \n            function stopMusic() {\n                if (musicSource) {\n                    try {\n                        musicSource.stop();\n                    } catch (e) {}\n                    musicSource = null;\n                }\n            }\n            \n            function updateMusicVolume() {\n                if (musicGainNode) {\n                    musicGainNode.gain.value = config.settings.musicVolume / 100;\n                }\n            }\n            \n            // ==================== GAME STATE ====================\n            let grid = [];\n            let score = 0;\n            let money = config.settings.initialMoney || 0;\n            let highScore = 0;\n            let discoveredTiers = [0]; // Tracks discovered weapons for index\n            let incomeTimer = 0;\n            let isGameOver = false;\n            let isDragging = false;\n            let draggedCell = null;\n            let dragStartPos = { x: 0, y: 0 };\n            let dragCurrentPos = { x: 0, y: 0 };\n            let hoveredCell = null;\n            let isSpawning = false;\n            \n            // Animation state\n            let particles = [];\n            let mergeAnimations = [];\n            let spawnAnimations = [];\n            let invalidAnimations = [];\n            \n            // Grid dimensions calculated from config\n            let gridStartX, gridStartY, cellSize, cellSpacing;\n            \n            function calculateGridDimensions() {\n                const cols = config.grid.cols;\n                const rows = config.grid.rows;\n                cellSize = config.grid.cellSize;\n                cellSpacing = config.grid.spacing;\n                \n                const gridWidth = cols * cellSize + (cols - 1) * cellSpacing;\n                const gridHeight = rows * cellSize + (rows - 1) * cellSpacing;\n                \n                gridStartX = (canvas.width - gridWidth) / 2;\n                gridStartY = 220;\n            }\n            \n            function initializeGrid() {\n                grid = [];\n                for (let row = 0; row < config.grid.rows; row++) {\n                    grid[row] = [];\n                    for (let col = 0; col < config.grid.cols; col++) {\n                        grid[row][col] = null;\n                    }\n                }\n            }\n            \n            function getCellPosition(row, col) {\n                return {\n                    x: gridStartX + col * (cellSize + cellSpacing),\n                    y: gridStartY + row * (cellSize + cellSpacing)\n                };\n            }\n            \n            function getCellFromPosition(x, y) {\n                for (let row = 0; row < config.grid.rows; row++) {\n                    for (let col = 0; col < config.grid.cols; col++) {\n                        const pos = getCellPosition(row, col);\n                        if (x >= pos.x && x < pos.x + cellSize &&\n                            y >= pos.y && y < pos.y + cellSize) {\n                            return { row, col };\n                        }\n                    }\n                }\n                return null;\n            }\n            \n            function getEmptyCells() {\n                const empty = [];\n                for (let row = 0; row < config.grid.rows; row++) {\n                    // Safety check to prevent crash if grid is being resized or not yet initialized\n                    if (!grid[row]) continue;\n                    for (let col = 0; col < config.grid.cols; col++) {\n                        if (grid[row][col] === null) {\n                            empty.push({ row, col });\n                        }\n                    }\n                }\n                return empty;\n            }\n            \n            function spawnWeapon(targetCell = null) {\n                const emptyCells = getEmptyCells();\n                if (emptyCells.length === 0) return false;\n                \n                let cell = targetCell;\n                if (!cell || grid[cell.row][cell.col] !== null) {\n                    cell = emptyCells[Math.floor(Math.random() * emptyCells.length)];\n                }\n                \n                // Weighted random tier selection (lower tiers more common)\n                const probs = config.spawnProbabilities;\n                const total = probs.reduce((a, b) => a + b, 0);\n                let rand = Math.random() * total;\n                let tier = 0;\n                for (let i = 0; i < probs.length; i++) {\n                    rand -= probs[i];\n                    if (rand <= 0) {\n                        tier = i;\n                        break;\n                    }\n                }\n                \n                grid[cell.row][cell.col] = tier;\n                \n                // Spawn animation\n                const pos = getCellPosition(cell.row, cell.col);\n                spawnAnimations.push({\n                    x: pos.x + cellSize / 2,\n                    y: pos.y + cellSize / 2,\n                    progress: 0,\n                    row: cell.row,\n                    col: cell.col\n                });\n                \n                playSound('spawn_sound');\n                return true;\n            }\n            \n            function mergeWeapons(fromRow, fromCol, toRow, toCol) {\n                const fromTier = grid[fromRow][fromCol];\n                const toTier = grid[toRow][toCol];\n                \n                if (fromTier !== toTier || fromTier === null) return false;\n                if (fromTier >= config.weaponTiers.length - 1) return false;\n                \n                const newTier = fromTier + 1;\n                grid[toRow][toCol] = newTier;\n                grid[fromRow][fromCol] = null;\n                \n                // Track discovered tiers\n                if (!discoveredTiers.includes(newTier)) {\n                    discoveredTiers.push(newTier);\n                    lib.saveUserGameState({ \n                        highScore, \n                        money, \n                        discoveredTiers, \n                        themes: config.settings.themes \n                    });\n                }\n                \n                // Score calculation\n                const points = config.weaponTiers[newTier].points;\n                const moneyReward = config.weaponTiers[newTier].moneyReward || Math.floor(points / 2);\n                score += points;\n                money += moneyReward;\n                \n                updateScoreDisplay();\n                showMergeMessage(`+${points}`);\n                \n                // Merge animation\n                const toPos = getCellPosition(toRow, toCol);\n                mergeAnimations.push({\n                    x: toPos.x + cellSize / 2,\n                    y: toPos.y + cellSize / 2,\n                    progress: 0,\n                    tier: newTier\n                });\n                \n                // Particles\n                createMergeParticles(toPos.x + cellSize / 2, toPos.y + cellSize / 2);\n                \n                playSound('merge_sound');\n                return true;\n            }\n            \n            function createMergeParticles(x, y) {\n                const count = 12;\n                for (let i = 0; i < count; i++) {\n                    const angle = (Math.PI * 2 * i) / count;\n                    const speed = 150 + Math.random() * 100;\n                    particles.push({\n                        x,\n                        y,\n                        vx: Math.cos(angle) * speed,\n                        vy: Math.sin(angle) * speed,\n                        life: 1,\n                        size: 16 + Math.random() * 16\n                    });\n                }\n            }\n            \n            function checkGameOver() {\n                const emptyCells = getEmptyCells();\n                if (emptyCells.length > 0) return false;\n                \n                // Check for possible merges\n                for (let row = 0; row < config.grid.rows; row++) {\n                    for (let col = 0; col < config.grid.cols; col++) {\n                        const tier = grid[row][col];\n                        if (tier === null || tier >= config.weaponTiers.length - 1) continue;\n                        \n                        // Check adjacent cells\n                        const neighbors = [\n                            { r: row - 1, c: col },\n                            { r: row + 1, c: col },\n                            { r: row, c: col - 1 },\n                            { r: row, c: col + 1 }\n                        ];\n                        \n                        for (const n of neighbors) {\n                            if (n.r >= 0 && n.r < config.grid.rows &&\n                                n.c >= 0 && n.c < config.grid.cols) {\n                                if (grid[n.r][n.c] === tier) return false;\n                            }\n                        }\n                    }\n                }\n                \n                return true;\n            }\n            \n            function triggerGameOver() {\n                isGameOver = true;\n                \n                // Update high score\n                if (score > highScore) {\n                    highScore = score;\n                    finalHigh.style.display = 'block';\n                    finalHigh.textContent = 'New High Score!';\n                } else {\n                    finalHigh.style.display = 'none';\n                }\n                \n                // Save to player state\n                lib.saveUserGameState({ \n                    highScore, \n                    money, \n                    discoveredTiers, \n                    themes: config.settings.themes \n                });\n                \n                finalScore.textContent = `Score: ${score}`;\n                gameOverOverlay.classList.add('visible');\n                \n                playSound('game_over_sound');\n            }\n            \n            function restartGame() {\n                initializeGrid();\n                score = 0;\n                money = config.settings.initialMoney || 0;\n                config.settings.spawnCost = 10; // Reset spawn cost\n                isGameOver = false;\n                particles = [];\n                mergeAnimations = [];\n                spawnAnimations = [];\n                invalidAnimations = [];\n                \n                updateScoreDisplay();\n                gameOverOverlay.classList.remove('visible');\n                \n                // Spawn initial weapons\n                for (let i = 0; i < config.settings.startingSpawnCount; i++) {\n                    spawnWeapon();\n                }\n                \n                playSound('button_click_sound');\n            }\n            \n            function updateScoreDisplay() {\n                scoreDisplay.textContent = score.toLocaleString();\n                \n                moneyDisplay.textContent = `\u09f3${Math.floor(money).toLocaleString()}`;\n                \n                highScoreDisplay.textContent = highScore.toLocaleString();\n                \n                // Calculate current income\n                let totalIncome = 0;\n                for (let row = 0; row < config.grid.rows; row++) {\n                    if (!grid[row]) continue;\n                    for (let col = 0; col < config.grid.cols; col++) {\n                        const tier = grid[row][col];\n                        if (tier !== null) {\n                            totalIncome += config.weaponTiers[tier].income || 0;\n                        }\n                    }\n                }\n                incomeDisplay.textContent = `+\u09f3${totalIncome}/s`;\n                \n                const cost = config.settings.spawnCost || 10;\n                spawnCostDisplay.textContent = `\u09f3${cost}`;\n                \n                if (money < cost) {\n                    spawnButton.classList.add('low-funds');\n                } else {\n                    spawnButton.classList.remove('low-funds');\n                }\n            }\n            \n            function showMergeMessage(text) {\n                mergeMessage.textContent = text;\n                mergeMessage.classList.remove('visible');\n                void mergeMessage.offsetWidth; // Trigger reflow\n                mergeMessage.classList.add('visible');\n                setTimeout(() => {\n                    mergeMessage.classList.remove('visible');\n                }, 1000);\n            }\n            \n            // ==================== RENDERING ====================\n            function drawBackground() {\n                const currentThemeId = config.settings.currentTheme || 'grid_background';\n                const bgAsset = assetCache[currentThemeId];\n                if (bgAsset && bgAsset.img.complete) {\n                    // Draw with cover scaling\n                    const img = bgAsset.img;\n                    const imgRatio = img.width / img.height;\n                    const canvasRatio = bgCanvas.width / bgCanvas.height;\n                    \n                    let drawWidth, drawHeight, offsetX, offsetY;\n                    \n                    if (imgRatio > canvasRatio) {\n                        drawHeight = bgCanvas.height;\n                        drawWidth = drawHeight * imgRatio;\n                        offsetX = (bgCanvas.width - drawWidth) / 2;\n                        offsetY = 0;\n                    } else {\n                        drawWidth = bgCanvas.width;\n                        drawHeight = drawWidth / imgRatio;\n                        offsetX = 0;\n                        offsetY = (bgCanvas.height - drawHeight) / 2;\n                    }\n                    \n                    bgCtx.drawImage(img, offsetX, offsetY, drawWidth, drawHeight);\n                } else {\n                    bgCtx.fillStyle = '#1a1a1a';\n                    bgCtx.fillRect(0, 0, bgCanvas.width, bgCanvas.height);\n                }\n            }\n            \n            function drawGrid() {\n                for (let row = 0; row < config.grid.rows; row++) {\n                    for (let col = 0; col < config.grid.cols; col++) {\n                        const pos = getCellPosition(row, col);\n                        \n                        // Cell background\n                        ctx.fillStyle = '#252525';\n                        ctx.fillRect(pos.x, pos.y, cellSize, cellSize);\n                        \n                        // Cell border\n                        ctx.strokeStyle = 'rgba(0, 217, 255, 0.3)';\n                        ctx.lineWidth = 2;\n                        ctx.strokeRect(pos.x, pos.y, cellSize, cellSize);\n                        \n                        // Highlight for valid merge target\n                        if (hoveredCell && hoveredCell.row === row && hoveredCell.col === col &&\n                            isDragging && draggedCell) {\n                            const dragTier = grid[draggedCell.row][draggedCell.col];\n                            const targetTier = grid[row][col];\n                            if (targetTier === dragTier && dragTier !== null && \n                                dragTier < config.weaponTiers.length - 1 &&\n                                (draggedCell.row !== row || draggedCell.col !== col)) {\n                                ctx.fillStyle = 'rgba(0, 217, 255, 0.3)';\n                                ctx.fillRect(pos.x, pos.y, cellSize, cellSize);\n                                ctx.strokeStyle = '#00d9ff';\n                                ctx.lineWidth = 4;\n                                ctx.strokeRect(pos.x, pos.y, cellSize, cellSize);\n                            }\n                        }\n                    }\n                }\n            }\n            \n            function drawWeapons() {\n                for (let row = 0; row < config.grid.rows; row++) {\n                    for (let col = 0; col < config.grid.cols; col++) {\n                        const tier = grid[row][col];\n                        if (tier === null) continue;\n                        \n                        // Skip if this cell is being dragged\n                        if (isDragging && draggedCell && \n                            draggedCell.row === row && draggedCell.col === col) continue;\n                        \n                        // Skip if spawning animation active\n                        const spawning = spawnAnimations.find(a => a.row === row && a.col === col);\n                        if (spawning && spawning.progress < 1) continue;\n                        \n                        const pos = getCellPosition(row, col);\n                        drawWeaponAt(tier, pos.x + cellSize / 2, pos.y + cellSize / 2, 1);\n                    }\n                }\n            }\n            \n            function drawWeaponAt(tier, centerX, centerY, scale) {\n                const assetId = weaponAssetIds[tier];\n                const asset = assetCache[assetId];\n                \n                if (asset && asset.img.complete) {\n                    const img = asset.img;\n                    const aspectRatio = asset.info.aspect_ratio || [1, 1];\n                    const imgRatio = aspectRatio[0] / aspectRatio[1];\n                    \n                    // Contain scaling within cell\n                    const maxSize = (cellSize - 20) * scale;\n                    let drawWidth, drawHeight;\n                    \n                    if (imgRatio > 1) {\n                        drawWidth = maxSize;\n                        drawHeight = maxSize / imgRatio;\n                    } else {\n                        drawHeight = maxSize;\n                        drawWidth = maxSize * imgRatio;\n                    }\n                    \n                    ctx.drawImage(img, \n                        centerX - drawWidth / 2, \n                        centerY - drawHeight / 2, \n                        drawWidth, \n                        drawHeight\n                    );\n                } else {\n                    // Fallback rendering\n                    const colors = ['#666', '#888', '#4da6ff', '#4dff4d', '#bf80ff', '#ff6666', '#ffcc00'];\n                    ctx.fillStyle = colors[tier] || '#999';\n                    ctx.beginPath();\n                    ctx.arc(centerX, centerY, (cellSize / 2 - 15) * scale, 0, Math.PI * 2);\n                    ctx.fill();\n                    \n                    ctx.fillStyle = '#fff';\n                    ctx.font = `bold ${24 * scale}px Orbitron`;\n                    ctx.textAlign = 'center';\n                    ctx.textBaseline = 'middle';\n                    ctx.fillText((tier + 1).toString(), centerX, centerY);\n                }\n            }\n            \n            function drawDraggedWeapon() {\n                if (!isDragging || !draggedCell) return;\n                \n                const tier = grid[draggedCell.row][draggedCell.col];\n                if (tier === null) return;\n                \n                drawWeaponAt(tier, dragCurrentPos.x, dragCurrentPos.y, 1.1);\n            }\n            \n            function drawAnimations(deltaTime) {\n                // Update and draw particles\n                const particleAsset = assetCache['merge_particle'];\n                \n                for (let i = particles.length - 1; i >= 0; i--) {\n                    const p = particles[i];\n                    p.x += p.vx * deltaTime;\n                    p.y += p.vy * deltaTime;\n                    p.life -= deltaTime * 2;\n                    p.vx *= 0.95;\n                    p.vy *= 0.95;\n                    \n                    if (p.life <= 0) {\n                        particles.splice(i, 1);\n                        continue;\n                    }\n                    \n                    ctx.globalAlpha = p.life;\n                    if (particleAsset && particleAsset.img.complete) {\n                        ctx.drawImage(particleAsset.img, \n                            p.x - p.size / 2, \n                            p.y - p.size / 2, \n                            p.size, p.size);\n                    } else {\n                        ctx.fillStyle = '#ffd700';\n                        ctx.beginPath();\n                        ctx.arc(p.x, p.y, p.size / 2, 0, Math.PI * 2);\n                        ctx.fill();\n                    }\n                    ctx.globalAlpha = 1;\n                }\n                \n                // Merge animations\n                for (let i = mergeAnimations.length - 1; i >= 0; i--) {\n                    const anim = mergeAnimations[i];\n                    anim.progress += deltaTime * 4;\n                    \n                    if (anim.progress >= 1) {\n                        mergeAnimations.splice(i, 1);\n                        continue;\n                    }\n                    \n                    // Flash effect\n                    const flash = Math.sin(anim.progress * Math.PI);\n                    ctx.globalAlpha = flash * 0.5;\n                    ctx.fillStyle = '#fff';\n                    ctx.beginPath();\n                    ctx.arc(anim.x, anim.y, cellSize * 0.6 * flash, 0, Math.PI * 2);\n                    ctx.fill();\n                    ctx.globalAlpha = 1;\n                }\n                \n                // Spawn animations\n                for (let i = spawnAnimations.length - 1; i >= 0; i--) {\n                    const anim = spawnAnimations[i];\n                    anim.progress += deltaTime * 3;\n                    \n                    if (anim.progress >= 1) {\n                        spawnAnimations.splice(i, 1);\n                        continue;\n                    }\n                    \n                    const tier = grid[anim.row][anim.col];\n                    if (tier === null) {\n                        spawnAnimations.splice(i, 1);\n                        continue;\n                    }\n                    \n                    const scale = easeOutBack(anim.progress);\n                    drawWeaponAt(tier, anim.x, anim.y, scale);\n                }\n                \n                // Invalid move animations\n                for (let i = invalidAnimations.length - 1; i >= 0; i--) {\n                    const anim = invalidAnimations[i];\n                    anim.progress += deltaTime * 4;\n                    \n                    if (anim.progress >= 1) {\n                        invalidAnimations.splice(i, 1);\n                        continue;\n                    }\n                    \n                    const shake = Math.sin(anim.progress * Math.PI * 4) * 5 * (1 - anim.progress);\n                    const pos = getCellPosition(anim.row, anim.col);\n                    \n                    ctx.fillStyle = `rgba(255, 100, 100, ${0.3 * (1 - anim.progress)})`;\n                    ctx.fillRect(pos.x + shake, pos.y, cellSize, cellSize);\n                }\n            }\n            \n            function easeOutBack(t) {\n                const c1 = 1.70158;\n                const c3 = c1 + 1;\n                return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);\n            }\n            \n            // ==================== INPUT HANDLING ====================\n            function getEventPosition(e) {\n                const rect = canvas.getBoundingClientRect();\n                const scaleX = canvas.width / rect.width;\n                const scaleY = canvas.height / rect.height;\n                \n                if (e.touches) {\n                    return {\n                        x: (e.touches[0].clientX - rect.left) * scaleX,\n                        y: (e.touches[0].clientY - rect.top) * scaleY\n                    };\n                }\n                return {\n                    x: (e.clientX - rect.left) * scaleX,\n                    y: (e.clientY - rect.top) * scaleY\n                };\n            }\n            \n            function handleDragStart(e) {\n                if (isGameOver || mode === 'edit') return;\n                \n                e.preventDefault();\n                const pos = getEventPosition(e);\n                const cell = getCellFromPosition(pos.x, pos.y);\n                \n                if (cell && grid[cell.row][cell.col] !== null) {\n                    isDragging = true;\n                    draggedCell = cell;\n                    dragStartPos = pos;\n                    dragCurrentPos = pos;\n                    \n                    // Resume audio context on user interaction\n                    if (audioContext.state === 'suspended') {\n                        audioContext.resume();\n                    }\n                }\n            }\n            \n            function handleDragMove(e) {\n                if (!isDragging || mode === 'edit') return;\n                \n                e.preventDefault();\n                dragCurrentPos = getEventPosition(e);\n                hoveredCell = getCellFromPosition(dragCurrentPos.x, dragCurrentPos.y);\n                \n                // Trash zone highlight\n                const rect = trashZone.getBoundingClientRect();\n                const clientX = e.touches ? e.touches[0].clientX : e.clientX;\n                const clientY = e.touches ? e.touches[0].clientY : e.clientY;\n                \n                if (clientX >= rect.left && clientX <= rect.right && \n                    clientY >= rect.top && clientY <= rect.bottom) {\n                    trashZone.classList.add('active');\n                } else {\n                    trashZone.classList.remove('active');\n                }\n            }\n            \n            function handleDragEnd(e) {\n                if (!isDragging || mode === 'edit') return;\n                \n                e.preventDefault();\n                \n                // Check trash\n                const rect = trashZone.getBoundingClientRect();\n                const clientX = e.changedTouches ? e.changedTouches[0].clientX : e.clientX;\n                const clientY = e.changedTouches ? e.changedTouches[0].clientY : e.clientY;\n                \n                if (clientX >= rect.left && clientX <= rect.right && \n                    clientY >= rect.top && clientY <= rect.bottom) {\n                    if (draggedCell) {\n                        grid[draggedCell.row][draggedCell.col] = null;\n                        money += 10;\n                        updateScoreDisplay();\n                        playSound('button_click_sound');\n                        createMergeParticles(dragCurrentPos.x, dragCurrentPos.y);\n                    }\n                    trashZone.classList.remove('active');\n                    isDragging = false;\n                    draggedCell = null;\n                    return;\n                }\n                \n                if (hoveredCell && draggedCell) {\n                    const fromTier = grid[draggedCell.row][draggedCell.col];\n                    const toTier = grid[hoveredCell.row][hoveredCell.col];\n                    \n                    // Check if valid merge\n                    if (hoveredCell.row !== draggedCell.row || hoveredCell.col !== draggedCell.col) {\n                        if (fromTier === toTier && fromTier !== null && \n                            fromTier < config.weaponTiers.length - 1) {\n                            mergeWeapons(draggedCell.row, draggedCell.col, hoveredCell.row, hoveredCell.col);\n                            \n                            // Check game over after merge\n                            if (checkGameOver()) {\n                                triggerGameOver();\n                            }\n                        } else if (toTier !== null) {\n                            // Invalid merge - show feedback\n                            invalidAnimations.push({\n                                row: hoveredCell.row,\n                                col: hoveredCell.col,\n                                progress: 0\n                            });\n                            playSound('invalid_move_sound');\n                        }\n                    }\n                }\n                \n                isDragging = false;\n                draggedCell = null;\n                hoveredCell = null;\n            }\n            \n            // Touch events\n            canvas.addEventListener('touchstart', handleDragStart, { passive: false });\n            canvas.addEventListener('touchmove', handleDragMove, { passive: false });\n            canvas.addEventListener('touchend', handleDragEnd, { passive: false });\n            canvas.addEventListener('touchcancel', handleDragEnd, { passive: false });\n            \n            // Mouse events\n            canvas.addEventListener('mousedown', handleDragStart);\n            canvas.addEventListener('mousemove', handleDragMove);\n            canvas.addEventListener('mouseup', handleDragEnd);\n            canvas.addEventListener('mouseleave', handleDragEnd);\n            \n            // Button handlers\n            spawnButton.addEventListener('click', () => {\n                if (isGameOver || mode === 'edit') return;\n                \n                const cost = config.settings.spawnCost || 10;\n                if (money < cost) {\n                    playSound('invalid_move_sound');\n                    spawnButton.classList.add('shake');\n                    setTimeout(() => spawnButton.classList.remove('shake'), 500);\n                    return;\n                }\n                \n                // Resume audio context\n                if (audioContext.state === 'suspended') {\n                    audioContext.resume().then(() => playMusic());\n                }\n                \n                playSound('button_click_sound');\n                \n                const emptyCells = getEmptyCells();\n                if (emptyCells.length === 0) {\n                    spawnButton.classList.add('shake');\n                    setTimeout(() => spawnButton.classList.remove('shake'), 500);\n                    return;\n                }\n                \n                money -= cost;\n                spawnWeapon();\n                \n                // Increase spawn cost by 10 per spawn\n                config.settings.spawnCost += 10;\n                \n                updateScoreDisplay();\n                \n                // Save periodically or after significant actions\n                lib.saveUserGameState({ \n                    highScore, \n                    money, \n                    discoveredTiers, \n                    themes: config.settings.themes \n                });\n                setTimeout(() => {\n                    if (checkGameOver()) {\n                        triggerGameOver();\n                    }\n                }, 300);\n            });\n            \n            indexButton.addEventListener('click', () => {\n                playSound('button_click_sound');\n                renderIndex();\n                indexOverlay.classList.add('visible');\n            });\n\n            closeIndex.addEventListener('click', () => {\n                indexOverlay.classList.remove('visible');\n            });\n\n            shopButton.addEventListener('click', () => {\n                playSound('button_click_sound');\n                renderShop();\n                shopOverlay.classList.add('visible');\n            });\n\n            closeShop.addEventListener('click', () => {\n                shopOverlay.classList.remove('visible');\n            });\n\n            function renderIndex() {\n                indexList.innerHTML = '';\n                config.weaponTiers.forEach((tier, i) => {\n                    const item = document.createElement('div');\n                    item.style.background = 'var(--bg-medium)';\n                    item.style.padding = '10px';\n                    item.style.borderRadius = '8px';\n                    item.style.textAlign = 'center';\n                    item.style.opacity = discoveredTiers.includes(i) ? '1' : '0.3';\n                    \n                    const name = discoveredTiers.includes(i) ? tier.name : '???';\n                    const income = discoveredTiers.includes(i) ? `+\u09f3${tier.income}/s` : 'Income: ???';\n                    \n                    item.innerHTML = `\n                        <div style=\"font-weight: bold; margin-bottom: 5px;\">${name}</div>\n                        <div style=\"font-size: 10px; color: var(--accent-cyan);\">${income}</div>\n                    `;\n                    indexList.appendChild(item);\n                });\n            }\n\n            function renderShop() {\n                shopList.innerHTML = '';\n                config.settings.themes.forEach((theme) => {\n                    const item = document.createElement('div');\n                    item.style.background = 'var(--bg-medium)';\n                    item.style.padding = '15px';\n                    item.style.borderRadius = '8px';\n                    item.style.display = 'flex';\n                    item.style.justifyContent = 'space-between';\n                    item.style.alignItems = 'center';\n                    item.style.border = config.settings.currentTheme === theme.id ? '2px solid var(--accent-yellow)' : 'none';\n                    \n                    const buyBtnText = theme.owned ? (config.settings.currentTheme === theme.id ? 'Equipped' : 'Equip') : `Buy: \u09f3${theme.price.toLocaleString()}`;\n                    const canAfford = theme.owned || money >= theme.price;\n\n                    item.innerHTML = `\n                        <div>\n                            <div style=\"font-weight: bold;\">${theme.name}</div>\n                            <div style=\"font-size: 12px; color: var(--text-dim);\">${theme.owned ? 'Owned' : `Cost: \u09f3${theme.price.toLocaleString()}`}</div>\n                        </div>\n                        <button class=\"restart-button\" style=\"width: 120px; font-size: 12px; ${!canAfford && !theme.owned ? 'opacity: 0.5;' : ''}\">${buyBtnText}</button>\n                    `;\n                    \n                    const btn = item.querySelector('button');\n                    btn.addEventListener('click', () => {\n                        if (theme.owned) {\n                            config.settings.currentTheme = theme.id;\n                            renderShop();\n                            drawBackground();\n                            // Save theme selection\n                            lib.saveUserGameState({ \n                                highScore, \n                                money, \n                                discoveredTiers, \n                                themes: config.settings.themes,\n                                currentTheme: config.settings.currentTheme\n                            });\n                        } else if (money >= theme.price) {\n                            money -= theme.price;\n                            theme.owned = true;\n                            config.settings.currentTheme = theme.id;\n                            updateScoreDisplay();\n                            renderShop();\n                            drawBackground();\n                            // Save after purchase\n                            lib.saveUserGameState({ \n                                highScore, \n                                money, \n                                discoveredTiers, \n                                themes: config.settings.themes,\n                                currentTheme: config.settings.currentTheme\n                            });\n                        }\n                    });\n                    \n                    shopList.appendChild(item);\n                });\n            }\n            \n            playAgainButton.addEventListener('click', () => {\n                playSound('button_click_sound');\n                restartGame();\n            });\n            \n            // Keyboard controls\n            window.addEventListener('keydown', (e) => {\n                if (mode === 'edit') return;\n                \n                if (e.code === 'Space' && !isGameOver) {\n                    e.preventDefault();\n                    spawnButton.click();\n                }\n            });\n            \n            // Clear states on blur\n            window.addEventListener('blur', () => {\n                isDragging = false;\n                draggedCell = null;\n                hoveredCell = null;\n            });\n            \n            // ==================== EDIT MODE ====================\n            let selectedCell = null;\n            \n            function handleEditClick(e) {\n                const pos = getEventPosition(e);\n                const cell = getCellFromPosition(pos.x, pos.y);\n                \n                if (cell) {\n                    selectedCell = cell;\n                    const currentTier = grid[cell.row][cell.col];\n                    \n                    // Cycle through tiers (null -> 0 -> 1 -> ... -> 6 -> null)\n                    if (currentTier === null) {\n                        grid[cell.row][cell.col] = 0;\n                    } else if (currentTier >= config.weaponTiers.length - 1) {\n                        grid[cell.row][cell.col] = null;\n                    } else {\n                        grid[cell.row][cell.col] = currentTier + 1;\n                    }\n                    \n                    // Update config with grid state\n                    updateGridConfig();\n                }\n            }\n            \n            function updateGridConfig() {\n                // Store current grid state in config for persistence\n                config.initialGrid = [];\n                for (let row = 0; row < config.grid.rows; row++) {\n                    config.initialGrid[row] = [];\n                    for (let col = 0; col < config.grid.cols; col++) {\n                        config.initialGrid[row][col] = grid[row][col];\n                    }\n                }\n            }\n            \n            function loadGridFromConfig() {\n                if (config.initialGrid && Array.isArray(config.initialGrid) && config.initialGrid.length > 0) {\n                    for (let row = 0; row < Math.min(config.grid.rows, config.initialGrid.length); row++) {\n                        const configRow = config.initialGrid[row];\n                        if (!configRow || !Array.isArray(configRow) || !grid[row]) continue;\n                        for (let col = 0; col < Math.min(config.grid.cols, configRow.length); col++) {\n                            grid[row][col] = configRow[col];\n                        }\n                    }\n                }\n            }\n            \n            function drawEditOverlay() {\n                // Draw edit mode grid highlights\n                for (let row = 0; row < config.grid.rows; row++) {\n                    for (let col = 0; col < config.grid.cols; col++) {\n                        const pos = getCellPosition(row, col);\n                        \n                        // Hover effect\n                        if (selectedCell && selectedCell.row === row && selectedCell.col === col) {\n                            ctx.strokeStyle = '#ff6b35';\n                            ctx.lineWidth = 4;\n                            ctx.strokeRect(pos.x - 2, pos.y - 2, cellSize + 4, cellSize + 4);\n                        }\n                        \n                        // Empty cell indicator\n                        if (grid[row][col] === null) {\n                            ctx.fillStyle = 'rgba(255, 255, 255, 0.1)';\n                            ctx.fillRect(pos.x, pos.y, cellSize, cellSize);\n                            \n                            ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';\n                            ctx.font = '32px Inter';\n                            ctx.textAlign = 'center';\n                            ctx.textBaseline = 'middle';\n                            ctx.fillText('+', pos.x + cellSize / 2, pos.y + cellSize / 2);\n                        }\n                    }\n                }\n            }\n            \n            // ==================== GAME PARAMETERS ====================\n            lib.showGameParameters({\n                name: 'Gun Fusion Settings',\n                params: {\n                    'Grid Rows': {\n                        key: 'grid.rows',\n                        type: 'slider',\n                        min: 3,\n                        max: 6,\n                        step: 1,\n                        onChange: (val) => {\n                            config.grid.rows = val;\n                            initializeGrid();\n                            calculateGridDimensions();\n                        }\n                    },\n                    'Grid Columns': {\n                        key: 'grid.cols',\n                        type: 'slider',\n                        min: 3,\n                        max: 5,\n                        step: 1,\n                        onChange: (val) => {\n                            config.grid.cols = val;\n                            initializeGrid();\n                            calculateGridDimensions();\n                        }\n                    },\n                    'Music Volume': {\n                        key: 'settings.musicVolume',\n                        type: 'slider',\n                        min: 0,\n                        max: 100,\n                        step: 5,\n                        onChange: (value) => {\n                            config.settings.musicVolume = value;\n                            updateMusicVolume();\n                        }\n                    },\n                    'SFX Volume': {\n                        key: 'settings.sfxVolume',\n                        type: 'slider',\n                        min: 0,\n                        max: 100,\n                        step: 5\n                    },\n                    'Starting Weapons': {\n                        key: 'settings.startingSpawnCount',\n                        type: 'slider',\n                        min: 2,\n                        max: 5,\n                        step: 1\n                    },\n                    'Spawn Cost': {\n                        key: 'settings.spawnCost',\n                        type: 'number',\n                        min: 0,\n                        max: 1000,\n                        onChange: (val) => {\n                            config.settings.spawnCost = val;\n                            updateScoreDisplay();\n                        }\n                    },\n                    'Initial Money': {\n                        key: 'settings.initialMoney',\n                        type: 'number',\n                        min: 0,\n                        max: 10000\n                    }\n                }\n            });\n            \n            // ==================== INITIALIZATION ====================\n            // Initialize dimensions and grid immediately so UI clicks don't crash during asset loading\n            calculateGridDimensions();\n            initializeGrid();\n\n            await preloadAssets();\n            \n            // Load player state from persistence\n            try {\n                const savedState = await lib.getUserGameState();\n                if (savedState && savedState.state) {\n                    // Restore high score\n                    if (savedState.state.highScore !== undefined) {\n                        highScore = savedState.state.highScore;\n                    }\n                    // Restore money\n                    if (savedState.state.money !== undefined) {\n                        money = savedState.state.money;\n                    }\n                    // Restore discovered weapon tiers\n                    if (savedState.state.discoveredTiers !== undefined && Array.isArray(savedState.state.discoveredTiers)) {\n                        discoveredTiers = savedState.state.discoveredTiers;\n                    }\n                    // Restore theme ownership and current selection\n                    if (savedState.state.themes !== undefined && Array.isArray(savedState.state.themes)) {\n                        // Merge saved theme data with config themes to preserve structure\n                        savedState.state.themes.forEach(savedTheme => {\n                            const configTheme = config.settings.themes.find(t => t.id === savedTheme.id);\n                            if (configTheme) {\n                                configTheme.owned = savedTheme.owned;\n                                // Preserve current theme selection if it was saved\n                                if (savedTheme.id === savedState.state.currentTheme) {\n                                    config.settings.currentTheme = savedTheme.id;\n                                }\n                            }\n                        });\n                    }\n                    // Restore current theme if directly saved\n                    if (savedState.state.currentTheme !== undefined) {\n                        config.settings.currentTheme = savedState.state.currentTheme;\n                    }\n                    \n                    lib.log('Player state loaded successfully');\n                }\n            } catch (e) {\n                lib.log('Could not load player state: ' + e.message);\n            }\n            \n            // Set trash icon\n            const trashAsset = lib.getAsset('trash_icon');\n            if (trashAsset) trashImg.src = trashAsset.url;\n            \n            // Capture local references after async operations to avoid race condition warnings\n            const spawnBtn = spawnButton;\n            const editInd = editIndicator;\n            \n            if (mode === 'edit') {\n                editInd.style.display = 'block';\n                spawnBtn.style.display = 'none';\n                \n                // Load existing grid config\n                loadGridFromConfig();\n                \n                // Edit mode click handler\n                canvas.addEventListener('click', handleEditClick);\n                canvas.addEventListener('touchend', (e) => {\n                    e.preventDefault();\n                    handleEditClick(e);\n                });\n            } else {\n                editInd.style.display = 'none';\n                spawnBtn.style.display = 'block';\n                \n                // Load from config or spawn initial weapons\n                if (config.initialGrid && config.initialGrid.length > 0) {\n                    loadGridFromConfig();\n                } else {\n                    for (let i = 0; i < config.settings.startingSpawnCount; i++) {\n                        spawnWeapon();\n                    }\n                }\n                \n                // Start music on first interaction\n                document.addEventListener('click', () => {\n                    if (audioContext.state === 'suspended') {\n                        audioContext.resume().then(() => playMusic());\n                    } else if (!musicSource) {\n                        playMusic();\n                    }\n                }, { once: true });\n            }\n            \n            updateScoreDisplay();\n            \n            // Draw background once\n            drawBackground();\n            \n            // ==================== GAME LOOP ====================\n            let lastTime = performance.now();\n            \n            function gameLoop(timestamp) {\n                let deltaTime = (timestamp - lastTime) / 1000;\n                deltaTime = Math.min(deltaTime, 0.11); // Cap delta time\n                lastTime = timestamp;\n                \n                // Passive Income\n                if (mode === 'play' && !isGameOver) {\n                    incomeTimer += deltaTime;\n                    if (incomeTimer >= 1.0) {\n                        incomeTimer -= 1.0;\n                        let totalIncome = 0;\n                        for (let row = 0; row < config.grid.rows; row++) {\n                            if (!grid[row]) continue;\n                            for (let col = 0; col < config.grid.cols; col++) {\n                                const tier = grid[row][col];\n                                if (tier !== null) {\n                                    totalIncome += config.weaponTiers[tier].income || 0;\n                                }\n                            }\n                        }\n                        money += totalIncome;\n                        updateScoreDisplay();\n                    }\n                }\n                \n                // Clear game canvas\n                ctx.clearRect(0, 0, canvas.width, canvas.height);\n                \n                // Recalculate grid if needed\n                calculateGridDimensions();\n                \n                // Draw game elements\n                drawGrid();\n                drawWeapons();\n                drawAnimations(deltaTime);\n                \n                if (mode === 'edit') {\n                    drawEditOverlay();\n                } else {\n                    drawDraggedWeapon();\n                }\n                \n                // Update spawn button state\n                const emptyCells = getEmptyCells();\n                const cost = config.settings.spawnCost || 10;\n                spawnButton.disabled = isGameOver || emptyCells.length === 0;\n                \n                requestAnimationFrame(gameLoop);\n            }\n            \n            requestAnimationFrame(gameLoop);\n        }\n    </script>\n</body>\n</html>", "game_config": "{\n  \"grid\": {\n    \"rows\": 5,\n    \"cols\": 4,\n    \"cellSize\": 150,\n    \"spacing\": 12\n  },\n  \"weaponTiers\": [\n    {\n      \"points\": 10,\n      \"income\": 10,\n      \"name\": \"Pistol\",\n      \"moneyReward\": 5\n    },\n    {\n      \"moneyReward\": 10,\n      \"name\": \"Revolver\",\n      \"points\": 25,\n      \"income\": 20\n    },\n    {\n      \"name\": \"Shotgun\",\n      \"moneyReward\": 25,\n      \"points\": 60,\n      \"income\": 30\n    },\n    {\n      \"moneyReward\": 60,\n      \"name\": \"Rifle\",\n      \"points\": 150,\n      \"income\": 40\n    },\n    {\n      \"income\": 50,\n      \"points\": 400,\n      \"name\": \"Sniper\",\n      \"moneyReward\": 150\n    },\n    {\n      \"moneyReward\": 400,\n      \"name\": \"Machine Gun\",\n      \"income\": 60,\n      \"points\": 1000\n    },\n    {\n      \"name\": \"Rocket Launcher\",\n      \"moneyReward\": 1000,\n      \"income\": 70,\n      \"points\": 2500\n    },\n    {\n      \"moneyReward\": 2500,\n      \"name\": \"Plasma Cannon\",\n      \"income\": 80,\n      \"points\": 6000\n    },\n    {\n      \"points\": 15000,\n      \"income\": 90,\n      \"name\": \"Railgun\",\n      \"moneyReward\": 6000\n    },\n    {\n      \"income\": 100,\n      \"points\": 40000,\n      \"moneyReward\": 15000,\n      \"name\": \"Mini-Nuke\"\n    },\n    {\n      \"moneyReward\": 50000,\n      \"name\": \"Omega Weapon\",\n      \"points\": 100000,\n      \"income\": 1000\n    },\n    {\n      \"points\": 250000,\n      \"income\": 2500,\n      \"moneyReward\": 100000,\n      \"name\": \"Antimatter Rifle\"\n    },\n    {\n      \"moneyReward\": 250000,\n      \"name\": \"Singularity Cannon\",\n      \"income\": 10000,\n      \"points\": 750000\n    },\n    {\n      \"moneyReward\": 1000000,\n      \"name\": \"Cosmic Obliterator\",\n      \"income\": 50000,\n      \"points\": 2000000\n    },\n    {\n      \"income\": 100000,\n      \"points\": 10000000,\n      \"name\": \"Galaxy Shredder\",\n      \"moneyReward\": 5000000\n    },\n    {\n      \"income\": 1000000000000000.0,\n      \"points\": 999999999,\n      \"name\": \"Infinite Paradox\",\n      \"moneyReward\": 0\n    },\n    {\n      \"income\": 2000000000000000.0,\n      \"points\": 20000000000,\n      \"moneyReward\": 10000000000,\n      \"name\": \"Chrono Displacer\"\n    },\n    {\n      \"moneyReward\": 20000000000,\n      \"name\": \"Void Render\",\n      \"income\": 5000000000000000.0,\n      \"points\": 50000000000\n    },\n    {\n      \"points\": 100000000000,\n      \"income\": 1e+16,\n      \"name\": \"Star Eater\",\n      \"moneyReward\": 50000000000\n    },\n    {\n      \"income\": 2e+16,\n      \"points\": 200000000000,\n      \"moneyReward\": 100000000000,\n      \"name\": \"Nebula Crusher\"\n    },\n    {\n      \"name\": \"Quantum Slicer\",\n      \"moneyReward\": 200000000000,\n      \"points\": 500000000000,\n      \"income\": 5e+16\n    },\n    {\n      \"moneyReward\": 500000000000,\n      \"name\": \"Dimensional Tearer\",\n      \"income\": 1e+17,\n      \"points\": 1000000000000\n    },\n    {\n      \"points\": 2000000000000,\n      \"income\": 2e+17,\n      \"name\": \"Reality Warper\",\n      \"moneyReward\": 1000000000000\n    },\n    {\n      \"income\": 5e+17,\n      \"points\": 5000000000000,\n      \"moneyReward\": 2000000000000,\n      \"name\": \"Existence Eraser\"\n    },\n    {\n      \"name\": \"Void Star Gun\",\n      \"moneyReward\": 5000000000000,\n      \"income\": 1e+18,\n      \"points\": 10000000000000\n    },\n    {\n      \"moneyReward\": 10000000000000,\n      \"name\": \"Soul Reaper Cannon\",\n      \"income\": 2e+18,\n      \"points\": 20000000000000\n    },\n    {\n      \"name\": \"Eternal Flame Cannon\",\n      \"moneyReward\": 20000000000000,\n      \"income\": 5e+18,\n      \"points\": 50000000000000\n    },\n    {\n      \"income\": 1e+19,\n      \"points\": 100000000000000,\n      \"name\": \"Absolute Zero Cannon\",\n      \"moneyReward\": 50000000000000\n    },\n    {\n      \"name\": \"Time Lord Blaster\",\n      \"moneyReward\": 100000000000000,\n      \"income\": 2e+19,\n      \"points\": 200000000000000\n    },\n    {\n      \"moneyReward\": 200000000000000,\n      \"name\": \"God Slayer Cannon\",\n      \"income\": 5e+19,\n      \"points\": 500000000000000\n    },\n    {\n      \"moneyReward\": 500000000000000,\n      \"name\": \"Omega Prime\",\n      \"points\": 1000000000000000.0,\n      \"income\": 1e+20\n    },\n    {\n      \"name\": \"Alpha Omega Gun\",\n      \"moneyReward\": 1000000000000000.0,\n      \"income\": 2e+20,\n      \"points\": 2000000000000000.0\n    },\n    {\n      \"points\": 5000000000000000.0,\n      \"income\": 5e+20,\n      \"moneyReward\": 2000000000000000.0,\n      \"name\": \"Universal Destructor\"\n    },\n    {\n      \"name\": \"Infinite Core Cannon\",\n      \"moneyReward\": 5000000000000000.0,\n      \"income\": 1e+21,\n      \"points\": 1e+16\n    },\n    {\n      \"points\": 2e+16,\n      \"income\": 2e+21,\n      \"name\": \"Supernova Launcher\",\n      \"moneyReward\": 1e+16\n    },\n    {\n      \"moneyReward\": 2e+16,\n      \"name\": \"Black Hole Projector\",\n      \"income\": 5e+21,\n      \"points\": 5e+16\n    },\n    {\n      \"moneyReward\": 5e+16,\n      \"name\": \"Galactic Annihilator\",\n      \"points\": 1e+17,\n      \"income\": 1e+22\n    },\n    {\n      \"moneyReward\": 1e+17,\n      \"name\": \"Multiverse Buster\",\n      \"points\": 2e+17,\n      \"income\": 2e+22\n    },\n    {\n      \"income\": 5e+22,\n      \"points\": 5e+17,\n      \"name\": \"Timeline Shredder\",\n      \"moneyReward\": 2e+17\n    },\n    {\n      \"moneyReward\": 5e+17,\n      \"name\": \"Chaos Bringer\",\n      \"income\": 1e+23,\n      \"points\": 1e+18\n    },\n    {\n      \"moneyReward\": 1e+18,\n      \"name\": \"Order Restorer\",\n      \"income\": 2e+23,\n      \"points\": 2e+18\n    },\n    {\n      \"moneyReward\": 2e+18,\n      \"name\": \"Celestial Beam\",\n      \"income\": 5e+23,\n      \"points\": 5e+18\n    },\n    {\n      \"points\": 1e+19,\n      \"income\": 1e+24,\n      \"name\": \"Stellar Pulse Rifle\",\n      \"moneyReward\": 5e+18\n    },\n    {\n      \"income\": 2e+24,\n      \"points\": 2e+19,\n      \"moneyReward\": 1e+19,\n      \"name\": \"Void Pulse Pistol\"\n    },\n    {\n      \"name\": \"Dark Matter SMG\",\n      \"moneyReward\": 2e+19,\n      \"points\": 5e+19,\n      \"income\": 5e+24\n    },\n    {\n      \"points\": 1e+20,\n      \"income\": 1e+25,\n      \"moneyReward\": 5e+19,\n      \"name\": \"Solar Flare Shotgun\"\n    },\n    {\n      \"moneyReward\": 1e+20,\n      \"name\": \"Lunar Edge Blade-Gun\",\n      \"income\": 2e+25,\n      \"points\": 2e+20\n    },\n    {\n      \"name\": \"Asteroid Crusher\",\n      \"moneyReward\": 2e+20,\n      \"income\": 5e+25,\n      \"points\": 5e+20\n    },\n    {\n      \"income\": 1e+26,\n      \"points\": 1e+21,\n      \"moneyReward\": 5e+20,\n      \"name\": \"Comet Tail Rifle\"\n    },\n    {\n      \"income\": 2e+26,\n      \"points\": 2e+21,\n      \"name\": \"Plasma Whip Gun\",\n      \"moneyReward\": 1e+21\n    },\n    {\n      \"income\": 5e+26,\n      \"points\": 5e+21,\n      \"moneyReward\": 2e+21,\n      \"name\": \"Ionic Storm Cannon\"\n    },\n    {\n      \"moneyReward\": 5e+21,\n      \"name\": \"Neutron Star Blaster\",\n      \"income\": 1e+27,\n      \"points\": 1e+22\n    },\n    {\n      \"points\": 2e+22,\n      \"income\": 2e+27,\n      \"moneyReward\": 1e+22,\n      \"name\": \"Pulsar Beam Rifle\"\n    },\n    {\n      \"name\": \"Quasar Core Launcher\",\n      \"moneyReward\": 2e+22,\n      \"points\": 5e+22,\n      \"income\": 5e+27\n    },\n    {\n      \"income\": 1e+28,\n      \"points\": 1e+23,\n      \"name\": \"Gamma Ray Projector\",\n      \"moneyReward\": 5e+22\n    },\n    {\n      \"moneyReward\": 1e+23,\n      \"name\": \"X-Ray Vaporizer\",\n      \"points\": 2e+23,\n      \"income\": 2e+28\n    },\n    {\n      \"income\": 5e+28,\n      \"points\": 5e+23,\n      \"moneyReward\": 2e+23,\n      \"name\": \"Microwave Melter\"\n    },\n    {\n      \"name\": \"Radio Wave Disrupter\",\n      \"moneyReward\": 5e+23,\n      \"points\": 1e+24,\n      \"income\": 1e+29\n    },\n    {\n      \"income\": 2e+29,\n      \"points\": 2e+24,\n      \"name\": \"Sonic Boom Cannon\",\n      \"moneyReward\": 1e+24\n    },\n    {\n      \"moneyReward\": 2e+24,\n      \"name\": \"Ultrasonic Shredder\",\n      \"points\": 5e+24,\n      \"income\": 5e+29\n    },\n    {\n      \"income\": 1e+30,\n      \"points\": 1e+25,\n      \"moneyReward\": 5e+24,\n      \"name\": \"Infrared Burner\"\n    },\n    {\n      \"moneyReward\": 1e+25,\n      \"name\": \"Ultraviolet Eradicator\",\n      \"points\": 2e+25,\n      \"income\": 2e+30\n    },\n    {\n      \"income\": 5e+30,\n      \"points\": 5e+25,\n      \"name\": \"Prism Beam Projector\",\n      \"moneyReward\": 2e+25\n    },\n    {\n      \"moneyReward\": 5e+25,\n      \"name\": \"Light Speed Blaster\",\n      \"points\": 1e+26,\n      \"income\": 1e+31\n    },\n    {\n      \"income\": 2e+31,\n      \"points\": 2e+26,\n      \"moneyReward\": 1e+26,\n      \"name\": \"Warp Drive Gun\"\n    },\n    {\n      \"income\": 5e+31,\n      \"points\": 5e+26,\n      \"moneyReward\": 2e+26,\n      \"name\": \"Tachyon Pulse Rifle\"\n    },\n    {\n      \"name\": \"Anti-Gravity Cannon\",\n      \"moneyReward\": 5e+26,\n      \"income\": 1e+32,\n      \"points\": 1e+27\n    },\n    {\n      \"moneyReward\": 1e+27,\n      \"name\": \"Zero Point energy Gun\",\n      \"income\": 2e+32,\n      \"points\": 2e+27\n    },\n    {\n      \"name\": \"Higgs Boson Blast\",\n      \"moneyReward\": 2e+27,\n      \"income\": 5e+32,\n      \"points\": 5e+27\n    },\n    {\n      \"income\": 1e+33,\n      \"points\": 1e+28,\n      \"moneyReward\": 5e+27,\n      \"name\": \"Hadron Collider Gun\"\n    },\n    {\n      \"income\": 2e+33,\n      \"points\": 2e+28,\n      \"name\": \"String Theory Rifle\",\n      \"moneyReward\": 1e+28\n    },\n    {\n      \"name\": \"M-Theory Launcher\",\n      \"moneyReward\": 2e+28,\n      \"points\": 5e+28,\n      \"income\": 5e+33\n    },\n    {\n      \"name\": \"Holographic Projector Gun\",\n      \"moneyReward\": 5e+28,\n      \"income\": 1e+34,\n      \"points\": 1e+29\n    },\n    {\n      \"points\": 2e+29,\n      \"income\": 2e+34,\n      \"name\": \"Simulated Reality Gun\",\n      \"moneyReward\": 1e+29\n    },\n    {\n      \"name\": \"Binary Blast Rifle\",\n      \"moneyReward\": 2e+29,\n      \"income\": 5e+34,\n      \"points\": 5e+29\n    },\n    {\n      \"points\": 1e+30,\n      \"income\": 1e+35,\n      \"name\": \"Quantum Computing Gun\",\n      \"moneyReward\": 5e+29\n    },\n    {\n      \"moneyReward\": 1e+30,\n      \"name\": \"AI Controlled Blaster\",\n      \"points\": 2e+30,\n      \"income\": 2e+35\n    },\n    {\n      \"name\": \"Neural Network Cannon\",\n      \"moneyReward\": 2e+30,\n      \"points\": 5e+30,\n      \"income\": 5e+35\n    },\n    {\n      \"moneyReward\": 5e+30,\n      \"name\": \"Synaptic Pulse Gun\",\n      \"points\": 1e+31,\n      \"income\": 1e+36\n    },\n    {\n      \"income\": 2e+36,\n      \"points\": 2e+31,\n      \"name\": \"DNA Sequence Rifle\",\n      \"moneyReward\": 1e+31\n    },\n    {\n      \"moneyReward\": 2e+31,\n      \"name\": \"Bio-Organic Cannon\",\n      \"income\": 5e+36,\n      \"points\": 5e+31\n    },\n    {\n      \"name\": \"Genetic Edge Blaster\",\n      \"moneyReward\": 5e+31,\n      \"points\": 1e+32,\n      \"income\": 1e+37\n    },\n    {\n      \"moneyReward\": 1e+32,\n      \"name\": \"Evolutionary Sniper\",\n      \"income\": 2e+37,\n      \"points\": 2e+32\n    },\n    {\n      \"points\": 5e+32,\n      \"income\": 5e+37,\n      \"name\": \"Mutation Ray Gun\",\n      \"moneyReward\": 2e+32\n    },\n    {\n      \"income\": 1e+38,\n      \"points\": 1e+33,\n      \"moneyReward\": 5e+32,\n      \"name\": \"Ancient Relic Gun\"\n    },\n    {\n      \"moneyReward\": 1e+33,\n      \"name\": \"Mythical Dragon Breath\",\n      \"points\": 2e+33,\n      \"income\": 2e+38\n    },\n    {\n      \"moneyReward\": 2e+33,\n      \"name\": \"Phoenix Heart Cannon\",\n      \"points\": 5e+33,\n      \"income\": 5e+38\n    },\n    {\n      \"moneyReward\": 5e+33,\n      \"name\": \"Titan Smasher Gun\",\n      \"income\": 1e+39,\n      \"points\": 1e+34\n    },\n    {\n      \"income\": 2e+39,\n      \"points\": 2e+34,\n      \"moneyReward\": 1e+34,\n      \"name\": \"Gods Hammer Blaster\"\n    },\n    {\n      \"name\": \"Heavenly Light Rifle\",\n      \"moneyReward\": 2e+34,\n      \"income\": 5e+39,\n      \"points\": 5e+34\n    },\n    {\n      \"name\": \"Hellfire Projector\",\n      \"moneyReward\": 5e+34,\n      \"income\": 1e+40,\n      \"points\": 1e+35\n    },\n    {\n      \"moneyReward\": 1e+35,\n      \"name\": \"Purgatory Pulse Gun\",\n      \"income\": 2e+40,\n      \"points\": 2e+35\n    },\n    {\n      \"income\": 5e+40,\n      \"points\": 5e+35,\n      \"moneyReward\": 2e+35,\n      \"name\": \"Spirit Realm Cannon\"\n    },\n    {\n      \"income\": 1e+41,\n      \"points\": 1e+36,\n      \"name\": \"Astral Plane Blaster\",\n      \"moneyReward\": 5e+35\n    },\n    {\n      \"name\": \"Etheric Edge Rifle\",\n      \"moneyReward\": 1e+36,\n      \"points\": 2e+36,\n      \"income\": 2e+41\n    },\n    {\n      \"income\": 5e+41,\n      \"points\": 5e+36,\n      \"name\": \"Void Walker Gun\",\n      \"moneyReward\": 2e+36\n    },\n    {\n      \"moneyReward\": 5e+36,\n      \"name\": \"Shadow Realm Cannon\",\n      \"points\": 1e+37,\n      \"income\": 1e+42\n    },\n    {\n      \"income\": 2e+42,\n      \"points\": 2e+37,\n      \"name\": \"Light Realm Blaster\",\n      \"moneyReward\": 1e+37\n    },\n    {\n      \"name\": \"Equilibrium Gun\",\n      \"moneyReward\": 2e+37,\n      \"points\": 5e+37,\n      \"income\": 5e+42\n    },\n    {\n      \"points\": 1e+38,\n      \"income\": 1e+43,\n      \"name\": \"Balance Bringer Rifle\",\n      \"moneyReward\": 5e+37\n    },\n    {\n      \"moneyReward\": 1e+38,\n      \"name\": \"End Game Blaster\",\n      \"points\": 2e+38,\n      \"income\": 2e+43\n    },\n    {\n      \"name\": \"Final Boss Cannon\",\n      \"moneyReward\": 2e+38,\n      \"points\": 5e+38,\n      \"income\": 5e+43\n    },\n    {\n      \"name\": \"Ultimate Truth Gun\",\n      \"moneyReward\": 5e+38,\n      \"points\": 1e+39,\n      \"income\": 1e+44\n    },\n    {\n      \"points\": 2e+39,\n      \"income\": 2e+44,\n      \"moneyReward\": 1e+39,\n      \"name\": \"The Creator Blaster\"\n    },\n    {\n      \"moneyReward\": 2e+39,\n      \"name\": \"The Destroyer Cannon\",\n      \"points\": 5e+39,\n      \"income\": 5e+44\n    },\n    {\n      \"moneyReward\": 5e+39,\n      \"name\": \"Alpha Point Gun\",\n      \"income\": 1e+45,\n      \"points\": 1e+40\n    },\n    {\n      \"income\": 2e+45,\n      \"points\": 2e+40,\n      \"name\": \"Omega Point Blaster\",\n      \"moneyReward\": 1e+40\n    },\n    {\n      \"moneyReward\": 2e+40,\n      \"name\": \"Singularity Core Gun Capsule\",\n      \"income\": 5e+45,\n      \"points\": 5e+40\n    },\n    {\n      \"points\": 1e+41,\n      \"income\": 1e+46,\n      \"name\": \"Big Bang Launcher\",\n      \"moneyReward\": 5e+40\n    },\n    {\n      \"moneyReward\": 1e+41,\n      \"name\": \"Cosmic Microwave Blaster\",\n      \"points\": 2e+41,\n      \"income\": 2e+46\n    },\n    {\n      \"name\": \"Entropic End Gun\",\n      \"moneyReward\": 2e+41,\n      \"points\": 5e+41,\n      \"income\": 5e+46\n    },\n    {\n      \"moneyReward\": 5e+41,\n      \"name\": \"Eternal Void Cannon\",\n      \"points\": 1e+42,\n      \"income\": 1e+47\n    },\n    {\n      \"points\": 2e+42,\n      \"income\": 2e+47,\n      \"name\": \"The All-Father Gun\",\n      \"moneyReward\": 1e+42\n    },\n    {\n      \"moneyReward\": 2e+42,\n      \"name\": \"God-Tier Obliterator\",\n      \"income\": 5e+47,\n      \"points\": 5e+42\n    },\n    {\n      \"name\": \"Infinite Omega Supreme\",\n      \"moneyReward\": 1e+60,\n      \"income\": 1e+60,\n      \"points\": 1e+60\n    }\n  ],\n  \"spawnProbabilities\": [\n    50,\n    30,\n    15,\n    5,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0\n  ],\n  \"settings\": {\n    \"startingSpawnCount\": 2,\n    \"musicVolume\": 50,\n    \"sfxVolume\": 70,\n    \"spawnCost\": 10,\n    \"initialMoney\": 10,\n    \"themes\": [\n      {\n        \"id\": \"grid_background\",\n        \"price\": 0,\n        \"owned\": true,\n        \"name\": \"Industrial Base\"\n      },\n      {\n        \"owned\": false,\n        \"name\": \"Cyber City\",\n        \"price\": 1000000,\n        \"id\": \"theme_cybercity\"\n      },\n      {\n        \"price\": 2000000,\n        \"id\": \"theme_desert_outpost\",\n        \"name\": \"Desert Outpost\",\n        \"owned\": false\n      },\n      {\n        \"name\": \"Arctic Station\",\n        \"owned\": false,\n        \"id\": \"theme_arctic_station\",\n        \"price\": 3000000\n      },\n      {\n        \"id\": \"theme_deep_sea\",\n        \"price\": 4000000,\n        \"owned\": false,\n        \"name\": \"Deep Sea Lab\"\n      },\n      {\n        \"price\": 5000000,\n        \"id\": \"theme_military_base\",\n        \"owned\": false,\n        \"name\": \"War Zone\"\n      },\n      {\n        \"name\": \"Volcanic Forge\",\n        \"owned\": false,\n        \"id\": \"theme_volcano_forge\",\n        \"price\": 6000000\n      },\n      {\n        \"price\": 100000000,\n        \"id\": \"theme_dyson_sphere\",\n        \"owned\": false,\n        \"name\": \"Dyson Sphere\"\n      },\n      {\n        \"id\": \"theme_nexus\",\n        \"price\": 10000000000,\n        \"owned\": false,\n        \"name\": \"Universal Nexus\"\n      }\n    ],\n    \"currentTheme\": \"grid_background\"\n  },\n  \"initialGrid\": []\n}", "game_design_document": "## Core Game Loop and Mechanics\n\n**Gun Fusion** is a merge puzzle game where players combine identical weapon parts to create more powerful guns. The core gameplay loop consists of:\n\n1. **Spawning**: New weapon parts appear randomly in empty grid cells at regular intervals or when the player taps a \"spawn\" button\n2. **Merging**: Players drag and drop identical weapon parts onto each other to merge them into the next tier weapon\n3. **Progression**: Each merge creates a more advanced weapon (pistol \u2192 revolver \u2192 shotgun \u2192 rifle \u2192 sniper \u2192 machine gun \u2192 rocket launcher)\n4. **Space Management**: Players must strategically manage limited grid space, planning merges to prevent the grid from filling up\n5. **Game Over**: The game ends when the grid is completely full and no more moves are possible\n6. **Scoring**: Players earn points for each merge, with higher-tier weapons worth exponentially more points\n\n**What makes it compelling:**\n- Satisfying visual and audio feedback when weapons merge and upgrade\n- Strategic depth in deciding which merges to prioritize\n- The tension of managing limited space while pursuing high-value merges\n- Clear progression system with visually distinct weapon tiers\n- Replayability through score chasing and trying to reach the highest weapon tier\n\n## Starting Scope\n\n**First Iteration (Implement Now):**\n- 4\u00d75 grid (4 columns, 5 rows) for mobile portrait orientation\n- 7 weapon tiers with distinct visual designs\n- Drag-and-drop merge mechanics with smooth animations\n- Manual spawn button (tap to add a random low-tier weapon part)\n- Score tracking and display\n- Game over detection and restart functionality\n- Particle effects and satisfying merge animations\n- Sound effects for merging and spawning\n- Simple background music loop\n\n**Future Iterations (Add Later):**\n- Auto-spawn mode with timer\n- Power-ups (shuffle grid, destroy item, undo move)\n- Daily challenges and leaderboards\n- Multiple grid sizes (easy/medium/hard)\n- Special rare weapon variants\n- Combo multipliers for consecutive merges\n- Achievement system\n\n## Game Perspective\n\n**Fixed-screen** - The entire game takes place on a single screen showing the merge grid, score, and controls. No scrolling or camera movement needed. This perspective works perfectly for merge games as players need to see all available pieces and plan their moves.\n\n## Game World and Level Design\n\nThe game takes place on a single screen with these key areas:\n\n**Top Section (0-200px from top):**\n- Score display (large, prominent numbers)\n- High score indicator\n- Settings/menu button (top-right corner)\n\n**Middle Section (200-1000px from top):**\n- 4\u00d75 merge grid (the main play area)\n- Each cell is 160\u00d7160 pixels with 10px gaps\n- Grid centered horizontally\n- Cells have subtle borders and background to show empty spaces\n- Weapon parts fill cells and have slight hover/press states\n\n**Bottom Section (1000-1280px from top):**\n- Large \"SPAWN\" button (centered, 200\u00d780 pixels)\n- Restart button (smaller, to the side)\n- Visual feedback area for combos/messages\n\n**Visual Theme:**\n- Dark military/industrial background (dark grays, blacks)\n- Weapon parts rendered with metallic textures and highlights\n- Neon accent colors (cyan, orange) for UI elements\n- Particle effects use bright colors (sparks, flashes) for merge feedback\n\n## Edit Mode Design\n\nPlayers can customize the following aspects in edit mode:\n\n**Grid Properties:**\n- Grid size: 3\u00d74 (easy), 4\u00d75 (default), 5\u00d76 (hard)\n- Cell spacing: 5-20 pixels\n- Background color/pattern\n\n**Weapon Tier Configuration:**\n- Number of weapon tiers: 5-10 (default 7)\n- Point values for each tier (exponential scaling recommended)\n- Spawn probability for each tier (lower tiers more common)\n\n**Gameplay Settings:**\n- Starting spawn count: 2-5 weapons\n- Manual vs auto-spawn mode\n- Auto-spawn interval (if enabled): 2-10 seconds\n\n**Visual Customization:**\n- Color scheme: Military (default), Neon, Retro, Minimal\n- Particle effect intensity: Low/Medium/High\n- Background theme selection\n\n**Audio Settings:**\n- Music volume: 0-100%\n- SFX volume: 0-100%\n- Enable/disable specific sound effects\n\n**Constraints:**\n- Grid must be at least 3\u00d73 to ensure playability\n- At least 5 weapon tiers required for progression depth\n- Point values must increase with each tier\n- Spawn probabilities must sum to reasonable distribution\n\n## Control Design\n\n**Mobile (Touch) Controls:**\n\n**Primary Interaction - Drag and Drop:**\n- Touch and hold a weapon part to pick it up\n- Visual feedback: the picked-up weapon scales up slightly (1.1x) and follows the finger\n- Drag the weapon over another weapon of the same tier\n- Valid merge target highlights with a glow effect\n- Release to merge (if valid) or return to original position (if invalid)\n- Smooth animation for both merge and return-to-position\n\n**Spawn Button:**\n- Large touch target (200\u00d780 pixels) in bottom section\n- Tap to spawn a new random weapon part in an empty cell\n- Button shows brief press animation and becomes temporarily disabled during spawn animation\n- Visual feedback if grid is full (button grays out, shake animation)\n\n**Menu/Restart:**\n- Small button in top-right (96\u00d796 pixels minimum) for settings\n- Restart button in bottom section (120\u00d760 pixels)\n\n**Desktop (Keyboard/Mouse) Controls:**\n\n**Primary Interaction - Drag and Drop:**\n- Click and hold left mouse button on a weapon part to pick it up\n- Weapon follows mouse cursor with same visual feedback as touch\n- Hover over valid merge targets shows highlight\n- Release mouse button to merge or return\n\n**Spawn:**\n- Click the spawn button with mouse\n- Keyboard shortcut: SPACE bar to spawn\n\n**Menu/Restart:**\n- Click buttons with mouse\n- Keyboard shortcut: R to restart, ESC for menu\n\n**Input Handling Details:**\n- Prevent default touch behaviors to avoid scrolling during drag\n- Track touch/mouse ID to handle only the first touch for dragging\n- Clear all input states on window blur\n- Disable dragging during animations to prevent conflicts\n- Show cursor pointer on hover over interactive elements\n\n## Art Style and Visual Theme\n\n**Overall Style: Industrial Sci-Fi**\n- Realistic weapon designs with sci-fi enhancements (glowing parts, energy effects)\n- Metallic textures with weathering and detail\n- Dark, moody color palette with bright accent colors\n- Clean, modern UI with military-inspired typography\n\n**Color Palette:**\n- Background: Dark grays (#1a1a1a, #2d2d2d), subtle texture\n- Grid cells: Darker gray (#252525) with cyan borders (#00d9ff)\n- Weapons: Metallic grays, blacks, with colored accents per tier\n- UI accents: Cyan (#00d9ff), orange (#ff6b35) for warnings\n- Particles: Bright yellows, oranges, whites for merge effects\n\n**Weapon Tier Progression (Visual Hierarchy):**\n1. **Pistol** (Tier 1): Small, simple, gray/black, minimal detail\n2. **Revolver** (Tier 2): Medium size, brown/silver accents\n3. **Shotgun** (Tier 3): Larger, blue energy accents\n4. **Rifle** (Tier 4): Long barrel, green tech details\n5. **Sniper** (Tier 5): Sleek, purple scope glow\n6. **Machine Gun** (Tier 6): Bulky, red ammunition belt\n7. **Rocket Launcher** (Tier 7): Massive, orange/gold missiles\n\n**Asset Sizes:**\n- Weapon parts: 140\u00d7140 pixels (to fit in 160\u00d7160 cells with padding)\n- Each weapon should be clearly recognizable at this size\n- Consistent viewing angle: 45-degree isometric view for all weapons\n- Transparent backgrounds for all weapon assets\n\n**Visual Effects:**\n- Merge animation: Bright flash, particle burst, weapons combine with rotation\n- Spawn animation: Weapon materializes with energy effect\n- Invalid move: Subtle shake and red flash\n- Game over: Screen darkens, weapons fade slightly\n\n**UI Style:**\n- Bold, sans-serif font for score (military stencil style)\n- Buttons have subtle gradients and borders\n- Hover/press states with scale and brightness changes\n- Minimalist icons for settings/restart", "persistent_storage_config": null, "game_metadata": {"game_id": "01KS8BHME7BWFJ8BJXPNRD0QJ7", "title": "Gun Fusion", "description": "Merge identical weapon parts on a small grid to craft increasingly powerful firearms and unlock the ultimate arsenal!", "thumbnail_url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/thumbnail_cf158f7e-d79f-4201-9994-11d73b1d13f3.webp", "created_at": 1779470830.0, "updated_at": 1779515064855.993, "art_style": null}}