{"asset_map": {"city_skyline_bg": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/city_skyline_bg_bba92bd5-9a46-4cbd-bc17-038ad0feea3c.webp", "type": "image", "prompt": "Generate a minimalist city skyline silhouette in a stick-figure art style. Solid dark gray (#2C2C2C) rectangular buildings of varying heights, tallest in the center and shorter on the sides. Very simple geometric shapes with a few small square windows. Clean, flat design, no gradients, matching a cartoon stick-man aesthetic. Wide 18:5 aspect ratio, transparent background.", "description": "Minimalist dark gray city skyline silhouette for the rooftop background", "subtype": "background", "aspect_ratio": [1289, 638], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "web_icon": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/web_icon_42910a67-6994-4d51-be4c-189392d5c3d0.webp", "type": "image", "prompt": "Generate a simple white spider web icon, 64x64 flat design. Minimalist concentric curved lines connected by radial lines. Pure white (#FFFFFF) clean lines, no gradients, no shadows. Professional game UI icon, stick-figure aesthetic, transparent background.", "description": "A clean, white minimalist spider web icon for the special attack button", "subtype": "ui", "aspect_ratio": [837, 820], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "game_logo": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/game_logo_97051ace-0a57-40e3-b2a0-2573c658e93a.webp", "type": "image", "prompt": "Generate a game logo text 'Stick-Man Spider-Sling' in a bold, dynamic comic-book style font. The text should be angled to suggest action and movement. Colors: Vibrant red and white with thick black outlines. Professional, energetic, and fun game branding, clean edges, transparent background.", "description": "The 'Stick-Man Spider-Sling' game logo in a bold, energetic comic-book style", "subtype": "ui", "aspect_ratio": [1310, 668], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "punch_sfx": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/punch_sfx_e3471177-6a2c-4e93-b7d3-16e3a67f83d3.mp3", "type": "audio", "prompt": "Quick, punchy air whoosh sound effect for a stick-figure punch. Light and snappy with a very brief high-frequency whistle, ending in a subtle soft impact. Retro arcade style, 8-bit aesthetic, clean and non-realistic, suitable for frequent repetition.", "description": "Quick whoosh sound for the player's basic punch attack.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 1.32, "loop": false, "animation": null}, "kick_sfx": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/kick_sfx_6fed22dd-bd1d-48b7-9038-08628a108c8c.mp3", "type": "audio", "prompt": "Powerful kick sound effect for a 2D action game. A deep, rushing wind whoosh followed immediately by a solid, bassy 'thud' impact. Cartoonish weight, arcade style, high clarity, satisfying and impactful.", "description": "Heavy impact sound for the player's kick attack.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 2.0, "loop": false, "animation": null}, "web_sfx": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/web_sfx_c66b9c1e-65e7-40ca-b2e5-8c24363713d8.mp3", "type": "audio", "prompt": "High-pitched 'thwip' sound effect, mechanical but elastic. A sharp, ascending synthetic zip followed by a quick snap. Inspired by comic book web-shooters, energetic, clean, and distinctive arcade sound.", "description": "Distinctive 'thwip' sound for the web-shooting special attack.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 1.54, "loop": false, "animation": null}, "enemy_hit_sfx": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/enemy_hit_sfx_4db2915e-86cf-4b04-b1a4-b125b246e013.mp3", "type": "audio", "prompt": "Classic cartoon impact sound, a short 'bonk' or 'thwack'. Mid-range frequency, dry texture with no echo, arcade-style feedback. Should feel physical and satisfying like a wooden bat hitting a soft target.", "description": "Satisfying thud for when enemies take damage.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 0.78, "loop": false, "animation": null}, "enemy_defeat_sfx": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/enemy_defeat_sfx_d207376b-8520-43c6-a48a-df8964857704.mp3", "type": "audio", "prompt": "Short comedic defeat sound effect, a descending whistle or 'wah-wah-wah' synth tone. Cartoon aesthetic, playful and slightly humorous, 8-bit retro vibe, signaling an enemy being knocked out of the arena.", "description": "Comedic descending sound for defeated enemies.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 2.0, "loop": false, "animation": null}, "player_hit_sfx": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/player_hit_sfx_28ccdc2c-193b-4e0b-abbc-1e9896119114.mp3", "type": "audio", "prompt": "Short, stylized stick-figure 'oof' grunt. Low-fidelity cartoon voice, non-violent, quick and clear. Sounds like a small impact-induced breath expulsion, arcade game feedback style.", "description": "Quick vocal-like grunt for player damage feedback.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 1.66, "loop": false, "animation": null}, "combo_sfx": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/combo_sfx_3e6f86ee-6eb0-45f0-a2a8-98e9facdcb4d.mp3", "type": "audio", "prompt": "Positive ascending musical chime, bright synthetic bells or glockenspiel. 3-4 rapid rising notes in a major scale. Shimmering, rewarding, and cheerful arcade reward sound.", "description": "Ascending chime to reward consecutive hits.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 1.37, "loop": false, "animation": null}, "wave_complete_sfx": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/wave_complete_sfx_d90582b5-c731-4e12-adc1-eb5e488c96fb.mp3", "type": "audio", "prompt": "Short 1.5 second victory jingle, retro chiptune style. Triumphant rising melody with a strong final major chord. Energetic, high-pitched square waves, celebratory arcade feel.", "description": "Short triumphant jingle played between waves.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 2.26, "loop": false, "animation": null}, "game_over_sfx": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/game_over_sfx_7dece97b-f595-40a3-b4d0-bed50001ff6a.mp3", "type": "audio", "prompt": "Descending 8-bit game over melody. 3-4 notes falling in pitch, ending on a low, slightly buzzy synth note. Classic arcade 'fail' sound, clear but encouraging a retry.", "description": "Descending melody played upon player defeat.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 2.67, "loop": false, "animation": null}, "background_music": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/background_music_1e33f97c-1c89-4ba7-9caf-1000602684a0.mp3", "type": "audio", "prompt": "High-energy retro chiptune action track, 150 BPM. Driving electronic drums, bouncy square-wave bassline, and a catchy, heroic synth-lead melody. Upbeat, urban fighting game vibe, seamless loop, 16-bit arcade aesthetic, energetic and motivating.", "description": "Fast-paced chiptune theme for the main gameplay.", "subtype": "music", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 30.0, "loop": true, "animation": null}, "victory_music": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/victory_music_47e42de5-ce44-45a7-aa69-8843842bbe3f.mp3", "type": "audio", "prompt": "Short triumphant fanfare for game completion. Bright brass-like synthesizers, bold orchestral hits in a major key, fast tempo, very celebratory and grand. 80s arcade hero ending style, loud and satisfying.", "description": "Bold fanfare for the end-game victory screen.", "subtype": "music", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 5.0, "loop": false, "animation": null}, "punch_icon": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/punch_icon_5cbca11b-ae86-4c99-8f38-42193a538dcc.webp", "type": "image", "prompt": "Generate a clean, minimalist white icon of a punching fist in an energetic comic book style. Bold outlines, simple shapes, high contrast, centered on a transparent background. Designed as a professional game UI button icon, matching the minimalist aesthetic of the web_icon and the energy of the game_logo.", "description": "A minimalist white comic-style punching fist icon for the melee attack button.", "subtype": "ui", "aspect_ratio": [816, 635], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "kick_icon": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/kick_icon_ea0e7854-9d75-4df2-b506-67250df475a7.webp", "type": "image", "prompt": "Generate a minimalist white icon of a leg performing a kick, comic book style, bold clean lines, flat design, isolated on a transparent background. Match the minimalist aesthetic and line weight of the punch_icon.", "description": "A minimalist white kicking leg icon for a game action button, matching the punch icon style.", "subtype": "ui", "aspect_ratio": [990, 446], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "jump_icon": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/jump_icon_078b3696-3b0c-4ec2-b4cf-f3eccb1ad49c.webp", "type": "image", "prompt": "Generate a minimalist white icon of a stylized figure jumping with motion lines or an upward burst, clean bold lines, comic book aesthetic, professional game UI asset, high contrast, transparent background.", "description": "A minimalist white comic-book style jumping icon for a game control button.", "subtype": "ui", "aspect_ratio": [989, 746], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "swing_icon": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/swing_icon_3cba69a3-8972-46da-ad99-fec94a1c44ab.webp", "type": "image", "prompt": "Generate a minimalist white icon of a stick-figure character swinging on a web line, comic book style, bold clean lines, high contrast, transparent background, professional UI game asset, matching the style of punch_icon and kick_icon", "description": "A minimalist white icon of a person swinging on a web for the game's movement button.", "subtype": "ui", "aspect_ratio": [602, 609], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "up_arrow_icon": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/up_arrow_icon_90421c65-0c32-43a6-8052-0bf7ae86894b.webp", "type": "image", "prompt": "Generate a bold, white minimalist upward arrow icon, clean vector style, comic-book aesthetic to match existing UI, transparent background, high contrast, professional game UI asset.", "description": "A bold, white minimalist upward arrow icon for the game's movement UI", "subtype": "ui", "aspect_ratio": [524, 632], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "down_arrow_icon": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/down_arrow_icon_7d84e11e-2df0-4bbf-856d-6d43f248793e.webp", "type": "image", "prompt": "Generate a bold, white minimalist downward arrow icon. The design should be clean, flat, and centered on a transparent background. Professional UI game asset quality, matching the aesthetic of a comic-book style game interface like image 1.", "description": "A bold, white minimalist downward arrow icon for a game button, matching the style of the up arrow.", "subtype": "ui", "aspect_ratio": [774, 974], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "left_arrow_icon": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/left_arrow_icon_3150df8e-053f-4831-b5a6-cb99781ab50e.webp", "type": "image", "prompt": "Generate a bold, white minimalist leftward arrow icon, clean vector style, centered on a transparent background. Designed as a UI button icon for a comic-book style game, matching the style of up_arrow_icon and down_arrow_icon.", "description": "A minimalist white leftward arrow icon for game navigation or movement buttons.", "subtype": "ui", "aspect_ratio": [730, 453], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "right_arrow_icon": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/right_arrow_icon_fbe08890-0632-42a5-8fcd-cbac2dd82343.webp", "type": "image", "prompt": "Generate a bold, white minimalist rightward arrow icon, clean vector style, comic-book aesthetic to match existing UI, centered on a transparent background, high contrast, professional game UI element.", "description": "A bold, white minimalist rightward arrow icon for a game button UI", "subtype": "ui", "aspect_ratio": [795, 486], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "special_power_icon": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/special_power_icon_2c316d32-9dfe-4d47-8ea7-ce0ab45decc6.webp", "type": "image", "prompt": "Generate a clean, minimalist white comic-book style starburst icon representing a special superpower. The design should be a bold silhouette with sharp edges, matching the visual style of the punch_icon and kick_icon. High contrast, professional game UI quality, transparent background, flat design.", "description": "A minimalist white comic-style burst icon for the special superpower button.", "subtype": "ui", "aspect_ratio": [835, 724], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}}, "game_code": "<!DOCTYPE html>\n<html>\n<head>\n    <title>Stick-Man Spider-Sling</title>\n    <style>\n        /* CSS Reset and Base Styles */\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: 'Roboto', 'Inter', sans-serif;\n        }\n\n        /* Custom Properties */\n        :root {\n            --transition-fast: 150ms;\n            --transition-normal: 300ms;\n            --transition-slow: 500ms;\n            --ease-out: cubic-bezier(0.0, 0, 0.2, 1);\n            --ease-bounce: cubic-bezier(0.68, -0.6, 0.32, 1.6);\n            --space-xs: 8px;\n            --space-sm: 16px;\n            --space-md: 24px;\n            --space-lg: 32px;\n            --space-xl: 48px;\n        }\n\n        /* Game Container */\n        #game-world {\n            position: relative;\n            width: 100%;\n            height: 100%;\n            background: linear-gradient(to bottom, #87CEEB, #4A90E2);\n        }\n\n        #game-canvas {\n            display: block;\n            width: 100%;\n            height: 100%;\n        }\n\n        /* UI Overlay */\n        .ui-overlay {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            pointer-events: none;\n        }\n\n        .ui-overlay > * {\n            pointer-events: auto;\n        }\n\n        /* HUD Elements */\n        .hud {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 80px;\n            display: flex;\n            justify-content: space-between;\n            align-items: center;\n            padding: 0 var(--space-sm);\n            box-sizing: border-box;\n        }\n\n        .health-bar-container {\n            width: 180px;\n            height: 30px;\n            background: rgba(0, 0, 0, 0.5);\n            border: 3px solid white;\n            border-radius: 4px;\n            overflow: hidden;\n        }\n\n        .health-bar {\n            height: 100%;\n            background: linear-gradient(to right, #ff0000, #ff4444);\n            transition: width 0.3s var(--ease-out);\n        }\n\n        .score-display {\n            font-size: 32px;\n            font-weight: bold;\n            color: white;\n            text-shadow: 3px 3px 0 rgba(0, 0, 0, 0.8);\n        }\n\n        .wave-indicator {\n            position: absolute;\n            top: 20px;\n            left: 50%;\n            transform: translateX(-50%);\n            font-size: 24px;\n            font-weight: bold;\n            color: white;\n            text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.8);\n        }\n\n        .combo-display {\n            position: absolute;\n            top: 100px;\n            left: 50%;\n            transform: translateX(-50%);\n            font-size: 40px;\n            font-weight: bold;\n            color: #FFD700;\n            text-shadow: 3px 3px 0 rgba(0, 0, 0, 0.8);\n            opacity: 0;\n            transition: opacity 0.3s;\n            animation: pulse 0.5s ease-in-out infinite;\n        }\n\n        .combo-display.active {\n            opacity: 1;\n        }\n\n        /* Touch Controls */\n        .touch-controls {\n            position: absolute;\n            bottom: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            pointer-events: none;\n        }\n\n        .touch-controls > * {\n            pointer-events: auto;\n        }\n\n        .movement-buttons {\n            position: absolute;\n            left: 20px;\n            bottom: 60px;\n            width: 220px;\n            height: 300px;\n            pointer-events: none;\n        }\n\n        .movement-buttons > * {\n            pointer-events: auto;\n        }\n\n        .move-button {\n            position: absolute;\n            width: 80px;\n            height: 80px;\n            border-radius: 50%;\n            border: 4px solid white;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            font-size: 32px;\n            font-weight: bold;\n            color: white;\n            text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.8);\n            transition: transform var(--transition-fast) var(--ease-out);\n            user-select: none;\n            background: rgba(255, 255, 255, 0.2);\n        }\n\n        .move-button:active {\n            transform: scale(0.9);\n            background: rgba(255, 255, 255, 0.4);\n        }\n\n        .left-button {\n            left: 0;\n            top: 100px;\n            background: linear-gradient(135deg, #666, #333);\n        }\n\n        .right-button {\n            right: 0;\n            top: 100px;\n            background: linear-gradient(135deg, #666, #333);\n        }\n\n        .up-button {\n            left: 70px;\n            top: 0;\n            background: linear-gradient(135deg, #666, #333);\n        }\n\n        .down-button {\n            left: 70px;\n            top: 200px;\n            background: linear-gradient(135deg, #666, #333);\n        }\n\n        .action-buttons {\n            position: absolute;\n            right: 20px;\n            bottom: 60px;\n            width: 220px;\n            height: 300px;\n        }\n\n        .action-button {\n            position: absolute;\n            width: 80px;\n            height: 80px;\n            border-radius: 50%;\n            border: 4px solid white;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            font-size: 32px;\n            font-weight: bold;\n            color: white;\n            text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.8);\n            transition: transform var(--transition-fast) var(--ease-out);\n            user-select: none;\n        }\n\n        .action-button:active {\n            transform: scale(0.9);\n        }\n\n        .btn-icon {\n            width: 60%;\n            height: 60%;\n            pointer-events: none;\n            filter: drop-shadow(2px 2px 2px rgba(0,0,0,0.5));\n        }\n\n        .punch-button {\n            background: linear-gradient(135deg, #ff4444, #cc0000);\n            left: 0;\n            top: 0;\n        }\n\n        .kick-button {\n            background: linear-gradient(135deg, #4444ff, #0000cc);\n            right: 0;\n            top: 0;\n        }\n\n        .jump-button {\n            background: linear-gradient(135deg, #44ff44, #00cc00);\n            left: 0;\n            top: 100px;\n        }\n\n        .swing-button {\n            background: linear-gradient(135deg, #ffcc00, #cc9900);\n            right: 0;\n            top: 100px;\n        }\n\n        .web-button {\n            background: linear-gradient(135deg, #ffffff, #cccccc);\n            left: 50%;\n            top: 200px;\n            transform: translateX(-50%);\n        }\n\n        .web-button img {\n            width: 48px;\n            height: 48px;\n        }\n\n        /* Menu Screens */\n        .menu-screen {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            background: rgba(0, 0, 0, 0.8);\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            justify-content: center;\n            opacity: 0;\n            pointer-events: none;\n            transition: opacity 0.3s;\n        }\n\n        .menu-screen.active {\n            opacity: 1;\n            pointer-events: auto;\n        }\n\n        .menu-title {\n            font-size: 60px;\n            font-weight: bold;\n            color: white;\n            text-shadow: 4px 4px 0 rgba(0, 0, 0, 0.8);\n            margin-bottom: var(--space-xl);\n        }\n\n        .game-logo {\n            width: 600px;\n            height: 150px;\n            margin-bottom: var(--space-xl);\n        }\n\n        .menu-text {\n            font-size: 28px;\n            color: white;\n            text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.8);\n            margin: var(--space-md) 0;\n            text-align: center;\n        }\n\n        .menu-stats {\n            font-size: 24px;\n            color: #FFD700;\n            text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.8);\n            margin: var(--space-sm) 0;\n        }\n\n        .menu-button {\n            width: 300px;\n            height: 96px;\n            background: linear-gradient(135deg, #ff4444, #cc0000);\n            border: 4px solid white;\n            border-radius: 16px;\n            font-size: 32px;\n            font-weight: bold;\n            color: white;\n            text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.8);\n            cursor: pointer;\n            transition: transform var(--transition-fast) var(--ease-out);\n            margin-top: var(--space-xl);\n        }\n\n        .menu-button:hover {\n            transform: scale(1.05);\n        }\n\n        .menu-button:active {\n            transform: scale(0.95);\n        }\n\n        /* Shop UI */\n        .shop-screen {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            background: rgba(0, 0, 0, 0.9);\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            justify-content: center;\n            opacity: 0;\n            pointer-events: none;\n            transition: opacity 0.3s;\n            z-index: 100;\n        }\n\n        .shop-screen.active {\n            opacity: 1;\n            pointer-events: auto;\n        }\n\n        .shop-grid {\n            display: grid;\n            grid-template-columns: 1fr 1fr;\n            gap: 20px;\n            margin-top: 20px;\n            width: 90%;\n            max-height: 700px;\n            overflow-y: auto;\n            padding: 20px;\n        }\n\n        .shop-item {\n            background: rgba(255, 255, 255, 0.1);\n            border: 2px solid white;\n            border-radius: 12px;\n            padding: 15px;\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            color: white;\n            text-align: center;\n        }\n\n        .shop-item img {\n            width: 60px;\n            height: 60px;\n            margin-bottom: 10px;\n        }\n\n        .shop-item-name {\n            font-size: 20px;\n            font-weight: bold;\n            margin-bottom: 5px;\n        }\n\n        .shop-item-cost {\n            font-size: 18px;\n            color: #FFD700;\n            margin-bottom: 10px;\n        }\n\n        .shop-buy-btn {\n            background: #ff4444;\n            border: none;\n            color: white;\n            padding: 8px 16px;\n            border-radius: 8px;\n            font-weight: bold;\n            cursor: pointer;\n        }\n\n        .shop-buy-btn:disabled {\n            background: #666;\n            cursor: not-allowed;\n        }\n\n        .close-shop-btn {\n            margin-top: 30px;\n            padding: 10px 30px;\n            background: white;\n            color: black;\n            border: none;\n            border-radius: 20px;\n            font-weight: bold;\n            cursor: pointer;\n        }\n\n        .shop-btn {\n            position: absolute;\n            top: 100px;\n            right: 20px;\n            width: 80px;\n            height: 80px;\n            border-radius: 50%;\n            background: linear-gradient(135deg, #FFD700, #DAA520);\n            border: 3px solid white;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            font-weight: bold;\n            color: black;\n            box-shadow: 0 4px 8px rgba(0,0,0,0.3);\n            pointer-events: auto;\n            z-index: 50;\n        }\n\n        .morph-btn {\n            position: absolute;\n            top: 100px;\n            right: 120px;\n            width: 80px;\n            height: 80px;\n            border-radius: 50%;\n            background: linear-gradient(135deg, #9933ff, #6600cc);\n            border: 3px solid white;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            font-weight: bold;\n            color: white;\n            box-shadow: 0 4px 8px rgba(0,0,0,0.3);\n            pointer-events: auto;\n            z-index: 50;\n            transition: transform 0.2s;\n        }\n\n        .morph-btn:active {\n            transform: scale(0.9);\n        }\n\n        .morph-btn.ready {\n            animation: glowPulse 1s infinite alternate;\n        }\n\n        @keyframes glowPulse {\n            from { box-shadow: 0 0 5px #fff; }\n            to { box-shadow: 0 0 20px #fff, 0 0 30px #9933ff; }\n        }\n\n        /* Animations */\n        @keyframes pulse {\n            0%, 100% { transform: translateX(-50%) scale(1); }\n            50% { transform: translateX(-50%) scale(1.1); }\n        }\n\n        @keyframes fadeOut {\n            from { opacity: 1; }\n            to { opacity: 0; }\n        }\n\n        @keyframes floatUp {\n            from {\n                transform: translateY(0);\n                opacity: 1;\n            }\n            to {\n                transform: translateY(-50px);\n                opacity: 0;\n            }\n        }\n\n        /* Reduced Motion Support */\n        @media (prefers-reduced-motion: reduce) {\n            * {\n                animation-duration: 0.01ms !important;\n                animation-iteration-count: 1 !important;\n                transition-duration: 0.01ms !important;\n            }\n        }\n    </style>\n</head>\n<body>\n    <div id=\"game-world\">\n        <canvas id=\"game-canvas\"></canvas>\n        \n        <div class=\"ui-overlay\">\n            <!-- HUD -->\n            <div class=\"hud\">\n                <div class=\"health-bar-container\">\n                    <div class=\"health-bar\" id=\"health-bar\"></div>\n                </div>\n                <div class=\"score-display\" id=\"score-display\">0</div>\n                <div class=\"shop-btn\" id=\"shop-open-btn\">SHOP</div>\n                <div class=\"morph-btn\" id=\"morph-btn\" style=\"display: none;\">MORPH</div>\n            </div>\n            <div class=\"wave-indicator\" id=\"wave-indicator\">Wave 1</div>\n            <div class=\"combo-display\" id=\"combo-display\">2x COMBO!</div>\n\n            <!-- Touch Controls -->\n            <div class=\"touch-controls\" id=\"touch-controls\" style=\"display: none;\">\n                <div class=\"movement-buttons\">\n                    <div class=\"move-button up-button\" id=\"up-button\"><img src=\"\" class=\"btn-icon\" id=\"up-icon\"></div>\n                    <div class=\"move-button left-button\" id=\"left-button\"><img src=\"\" class=\"btn-icon\" id=\"left-icon\"></div>\n                    <div class=\"move-button right-button\" id=\"right-button\"><img src=\"\" class=\"btn-icon\" id=\"right-icon\"></div>\n                    <div class=\"move-button down-button\" id=\"down-button\"><img src=\"\" class=\"btn-icon\" id=\"down-icon\"></div>\n                </div>\n                <div class=\"action-buttons\">\n                    <div class=\"action-button punch-button\" id=\"punch-button\"><img src=\"\" class=\"btn-icon\" id=\"punch-icon-img\"></div>\n                    <div class=\"action-button kick-button\" id=\"kick-button\"><img src=\"\" class=\"btn-icon\" id=\"kick-icon-img\"></div>\n                    <div class=\"action-button jump-button\" id=\"jump-button\"><img src=\"\" class=\"btn-icon\" id=\"jump-icon-img\"></div>\n                    <div class=\"action-button swing-button\" id=\"swing-button\"><img src=\"\" class=\"btn-icon\" id=\"swing-icon-img\"></div>\n                    <div class=\"action-button web-button\" id=\"web-button\">\n                        <img id=\"web-icon\" src=\"\" alt=\"Web\">\n                    </div>\n                </div>\n            </div>\n\n            <!-- Title Screen -->\n            <div class=\"menu-screen active\" id=\"title-screen\">\n                <img class=\"game-logo\" id=\"game-logo\" src=\"\" alt=\"Stick-Man Spider-Sling\">\n                <div class=\"menu-text\" id=\"start-text\">Tap to Start</div>\n                <div class=\"menu-text\">Survive as long as you can!</div>\n            </div>\n\n            <!-- Victory Screen -->\n            <div class=\"menu-screen\" id=\"victory-screen\">\n                <div class=\"menu-title\">VICTORY!</div>\n                <div class=\"menu-stats\" id=\"final-score\">Score: 0</div>\n                <div class=\"menu-stats\" id=\"waves-completed\">Waves: 0/0</div>\n                <div class=\"menu-stats\" id=\"best-combo\">Best Combo: 0x</div>\n                <button class=\"menu-button\" id=\"play-again-button\">Play Again</button>\n            </div>\n\n            <!-- Game Over Screen -->\n            <div class=\"menu-screen\" id=\"game-over-screen\">\n                <div class=\"menu-title\">GAME OVER</div>\n                <div class=\"menu-stats\" id=\"game-over-score\">Score: 0</div>\n                <div class=\"menu-stats\" id=\"wave-reached\">Wave Reached: 0/0</div>\n                <button class=\"menu-button\" id=\"try-again-button\">TRY AGAIN</button>\n            </div>\n\n            <div class=\"shop-screen\" id=\"shop-screen\">\n                <div class=\"menu-title\">HERO SHOP</div>\n                <div class=\"menu-text\">Hire partners to fight alongside you!</div>\n                <div class=\"shop-grid\" id=\"shop-grid\">\n                    <!-- Shop items will be injected here -->\n                </div>\n                <button class=\"close-shop-btn\" id=\"close-shop-btn\">Back to Game</button>\n            </div>\n        </div>\n    </div>\n\n    <script>\n        /**\n         * STICK-MAN SPIDER-SLING\n         * A side-scrolling stick-figure fighting game where players battle waves of enemies\n         * using punch, kick, and web attacks. Features combo system, wave progression,\n         * and both touch and keyboard controls.\n         */\n\n        async function run(mode) {\n            const gameWorld = document.getElementById('game-world');\n            const canvas = document.getElementById('game-canvas');\n            const ctx = canvas.getContext('2d');\n\n            // Initialize display\n            function resizeCanvas() {\n                canvas.width = gameWorld.clientWidth;\n                canvas.height = gameWorld.clientHeight;\n            }\n            resizeCanvas();\n            window.addEventListener('resize', resizeCanvas);\n\n            // Asset cache\n            const assets = {};\n            \n            // Preload assets\n            async function preloadAssets() {\n                const assetIds = [\n                    'city_skyline_bg', 'web_icon', 'game_logo', 'special_power_icon',\n                    'punch_sfx', 'kick_sfx', 'web_sfx', 'enemy_hit_sfx',\n                    'enemy_defeat_sfx', 'player_hit_sfx', 'combo_sfx',\n                    'wave_complete_sfx', 'game_over_sfx', 'background_music', 'victory_music',\n                    'punch_icon', 'kick_icon', 'jump_icon', 'swing_icon',\n                    'up_arrow_icon', 'down_arrow_icon', 'left_arrow_icon', 'right_arrow_icon'\n                ];\n\n                const audioContext = new (window.AudioContext || window.webkitAudioContext)();\n                \n                for (const id of assetIds) {\n                    const assetInfo = lib.getAsset(id);\n                    if (!assetInfo) continue;\n\n                    if (assetInfo.type === 'image') {\n                        const img = new Image();\n                        img.src = assetInfo.url;\n                        await new Promise((resolve, reject) => {\n                            img.onload = resolve;\n                            img.onerror = reject;\n                        });\n                        assets[id] = { img, ...assetInfo };\n                    } else if (assetInfo.type === 'audio') {\n                        try {\n                            const response = await fetch(assetInfo.url);\n                            const arrayBuffer = await response.arrayBuffer();\n                            const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);\n                            assets[id] = { buffer: audioBuffer, context: audioContext, ...assetInfo };\n                        } catch (error) {\n                            lib.log(`Failed to load audio: ${id}`);\n                        }\n                    }\n                }\n\n                // Set UI images\n                if (assets.web_icon) document.getElementById('web-icon').src = assets.web_icon.img.src;\n                if (assets.game_logo) document.getElementById('game-logo').src = assets.game_logo.img.src;\n                \n                if (assets.punch_icon) document.getElementById('punch-icon-img').src = assets.punch_icon.img.src;\n                if (assets.kick_icon) document.getElementById('kick-icon-img').src = assets.kick_icon.img.src;\n                if (assets.jump_icon) document.getElementById('jump-icon-img').src = assets.jump_icon.img.src;\n                if (assets.swing_icon) document.getElementById('swing-icon-img').src = assets.swing_icon.img.src;\n                \n                if (assets.up_arrow_icon) document.getElementById('up-icon').src = assets.up_arrow_icon.img.src;\n                if (assets.down_arrow_icon) document.getElementById('down-icon').src = assets.down_arrow_icon.img.src;\n                if (assets.left_arrow_icon) document.getElementById('left-icon').src = assets.left_arrow_icon.img.src;\n                if (assets.right_arrow_icon) document.getElementById('right-icon').src = assets.right_arrow_icon.img.src;\n            }\n\n            await preloadAssets();\n\n            // Audio playback helper\n            function playSound(soundId, volume = 1.0) {\n                const asset = assets[soundId];\n                if (!asset || !asset.buffer) return;\n\n                const source = asset.context.createBufferSource();\n                const gainNode = asset.context.createGain();\n                \n                source.buffer = asset.buffer;\n                gainNode.gain.value = volume;\n                \n                source.connect(gainNode);\n                gainNode.connect(asset.context.destination);\n                \n                source.start(0);\n            }\n\n            // Background music management\n            let musicSource = null;\n            function playBackgroundMusic() {\n                if (musicSource) return;\n                const asset = assets.background_music;\n                if (!asset || !asset.buffer) return;\n\n                musicSource = asset.context.createBufferSource();\n                const gainNode = asset.context.createGain();\n                \n                musicSource.buffer = asset.buffer;\n                musicSource.loop = true;\n                gainNode.gain.value = 0.3;\n                \n                musicSource.connect(gainNode);\n                gainNode.connect(asset.context.destination);\n                \n                musicSource.start(0);\n            }\n            function stopBackgroundMusic() {\n                if (musicSource) {\n                    musicSource.stop();\n                    musicSource = null;\n                }\n            }\n\n            // Projectile class\n            class Projectile {\n                constructor(x, y, vx, vy, type, owner = 'player') {\n                    this.x = x;\n                    this.y = y;\n                    this.vx = vx;\n                    this.vy = vy;\n                    this.type = type; // 'web', 'bomb', 'bolt'\n                    this.owner = owner;\n                    this.lifetime = 2.0;\n                    this.age = 0;\n                    this.radius = type === 'bomb' ? 15 : 10;\n                }\n\n                update(dt) {\n                    if (this.owner === 'player' && (this.type === 'shield' || this.type === 'bolt')) {\n                        const target = getNearestEnemy(this.x, this.y, 1000);\n                        if (target) {\n                            const dx = target.x - this.x;\n                            const dy = (target.y - 30) - this.y;\n                            const dist = Math.sqrt(dx * dx + dy * dy);\n                            if (dist > 10) {\n                                const targetVx = (dx / dist) * 1200;\n                                const targetVy = (dy / dist) * 1200;\n                                // Smoothly steer towards target\n                                this.vx += (targetVx - this.vx) * dt * 5;\n                                this.vy += (targetVy - this.vy) * dt * 5;\n                            }\n                        }\n                    }\n\n                    this.x += this.vx * dt;\n                    this.y += this.vy * dt;\n                    this.age += dt;\n\n                    if (this.owner === 'player') {\n                        // Check collision with enemies\n                        for (const enemy of enemies) {\n                            if (enemy.defeated) continue;\n                            const dx = this.x - enemy.x;\n                            const dy = this.y - (enemy.y - 30);\n                            const dist = Math.sqrt(dx * dx + dy * dy);\n                            if (dist < 40) {\n                                if (this.type === 'web') {\n                                    enemy.stun(3.0);\n                                    enemy.takeDamage(10);\n                                    playSound('web_sfx', 0.5);\n                                    \n                                    for (let i = 0; i < 10; i++) {\n                                        particles.push({\n                                            type: 'web',\n                                            x1: this.x, y1: this.y,\n                                            x2: this.x + (Math.random() - 0.5) * 50,\n                                            y2: this.y + (Math.random() - 0.5) * 50,\n                                            lifetime: 0.5, age: 0\n                                        });\n                                    }\n                                } else if (this.type === 'bolt' || this.type === 'shield') {\n                                    const damage = this.type === 'shield' ? 50 : 20;\n                                    enemy.takeDamage(damage);\n                                    playSound('enemy_hit_sfx', 0.5);\n                                }\n                                return true;\n                            }\n                        }\n                    } else {\n                        // Check collision with player\n                        if (player) {\n                            const dx = this.x - player.x;\n                            const dy = this.y - (player.y - 30);\n                            const dist = Math.sqrt(dx * dx + dy * dy);\n                            if (dist < 30) {\n                                player.takeDamage(15);\n                                return true;\n                            }\n                        }\n                    }\n                    return this.age >= this.lifetime || this.x < -100 || this.x > canvas.width + 100 || this.y > GROUND_Y + 50;\n                }\n\n                draw() {\n                    ctx.save();\n                    if (this.type === 'web') {\n                        ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';\n                        ctx.beginPath();\n                        ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);\n                        ctx.fill();\n                        ctx.strokeStyle = 'white';\n                        ctx.lineWidth = 2;\n                        ctx.stroke();\n                        for (let i = 0; i < 8; i++) {\n                            const ang = (i / 8) * Math.PI * 2;\n                            ctx.moveTo(this.x, this.y);\n                            ctx.lineTo(this.x + Math.cos(ang) * 15, this.y + Math.sin(ang) * 15);\n                        }\n                        ctx.stroke();\n                    } else if (this.type === 'bomb') {\n                        ctx.fillStyle = '#ff9900';\n                        ctx.beginPath();\n                        ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);\n                        ctx.fill();\n                        ctx.strokeStyle = '#ff4400';\n                        ctx.lineWidth = 3;\n                        ctx.stroke();\n                        // Fuse spark\n                        ctx.fillStyle = 'yellow';\n                        ctx.beginPath();\n                        ctx.arc(this.x + 10, this.y - 10, 3, 0, Math.PI * 2);\n                        ctx.fill();\n                    } else if (this.type === 'bolt') {\n                        ctx.strokeStyle = '#ffff00';\n                        ctx.lineWidth = 4;\n                        ctx.beginPath();\n                        ctx.moveTo(this.x, this.y);\n                        ctx.lineTo(this.x - (this.vx / 1200) * 20, this.y - (this.vy / 1200) * 20);\n                        ctx.stroke();\n                        // Glow\n                        ctx.strokeStyle = 'rgba(255, 255, 0, 0.5)';\n                        ctx.lineWidth = 8;\n                        ctx.stroke();\n                    } else if (this.type === 'shield') {\n                        ctx.translate(this.x, this.y);\n                        ctx.rotate(this.age * 25); // Faster spin\n                        ctx.lineWidth = 2;\n                        // Outer red\n                        ctx.fillStyle = '#cc0000';\n                        ctx.beginPath(); ctx.arc(0, 0, 20, 0, Math.PI * 2); ctx.fill();\n                        // White ring\n                        ctx.fillStyle = '#ffffff';\n                        ctx.beginPath(); ctx.arc(0, 0, 15, 0, Math.PI * 2); ctx.fill();\n                        // Red ring\n                        ctx.fillStyle = '#cc0000';\n                        ctx.beginPath(); ctx.arc(0, 0, 10, 0, Math.PI * 2); ctx.fill();\n                        // Blue center\n                        ctx.fillStyle = '#000088';\n                        ctx.beginPath(); ctx.arc(0, 0, 6, 0, Math.PI * 2); ctx.fill();\n                        // Star\n                        ctx.fillStyle = 'white';\n                        ctx.beginPath();\n                        for(let i=0; i<5; i++) {\n                            const a = (i/5)*Math.PI*2 - Math.PI/2;\n                            ctx.lineTo(Math.cos(a)*5, Math.sin(a)*5);\n                            const a2 = a + Math.PI/5;\n                            ctx.lineTo(Math.cos(a2)*2, Math.sin(a2)*2);\n                        }\n                        ctx.closePath();\n                        ctx.fill();\n                    }\n                    ctx.restore();\n                }\n            }\n\n            // Detect touch vs keyboard\n            const isTouchDevice = lib.shouldUseTouchControls();\n            if (isTouchDevice) {\n                document.getElementById('touch-controls').style.display = 'block';\n                document.getElementById('start-text').textContent = 'Tap to Start';\n            } else {\n                document.getElementById('start-text').textContent = 'Press Space to Start';\n                // Add key hints\n                const hints = document.createElement('div');\n                hints.className = 'menu-text';\n                hints.style.fontSize = '20px';\n                hints.innerHTML = 'A/D: Move | W: Jump | S: Swing<br>Space: Punch | Shift: Kick | E: Web';\n                document.getElementById('title-screen').appendChild(hints);\n            }\n\n            // Game configuration\n            const config = window.gameConfig;\n\n            // Game state\n            let gameState = 'title'; // title, playing, victory, gameover, shop\n            let player = null;\n            let enemies = [];\n            let partners = [];\n            let projectiles = [];\n            let powerUps = [];\n            let particles = [];\n            let floatingTexts = [];\n            let score = 0;\n            let currentWave = 0;\n            let enemiesSpawnedThisWave = 0;\n            let waveRestTimer = 0;\n            let combo = 0;\n            let comboTimer = 0;\n            let bestCombo = 0;\n            let lastFrameTime = 0;\n            let currentMorphIndex = -1; // -1 means original spiderman\n\n            // Helper to find nearest enemy\n            function getNearestEnemy(x, y, maxRange = Infinity) {\n                let nearest = null;\n                let minDistSq = maxRange * maxRange;\n                enemies.forEach(enemy => {\n                    if (enemy.defeated) return;\n                    const dx = enemy.x - x;\n                    const dy = (enemy.y - 30) - y;\n                    const distSq = dx * dx + dy * dy;\n                    if (distSq < minDistSq) {\n                        minDistSq = distSq;\n                        nearest = enemy;\n                    }\n                });\n                return nearest;\n            }\n\n            // Constants\n            const GROUND_Y = 800;\n            const PLAYER_START_X = 360;\n            const MIN_X = 100;\n            const MAX_X = 620;\n\n            // Partner class\n            class Partner {\n                constructor(type) {\n                    this.type = type; \n                    this.x = player.x - 50;\n                    this.y = player.y;\n                    this.vx = 0;\n                    this.vy = 0;\n                    this.facing = 1;\n                    this.animation = 'idle';\n                    this.frame = 0;\n                    this.attackTimer = 0;\n                    \n                    const pData = shopItems.find(item => item.id === type);\n                    this.color = pData.color;\n                    this.damage = pData.damage;\n                    this.attackCooldown = pData.cooldown;\n                    this.scale = pData.scale || 1.2;\n                    this.ability = pData.ability;\n                }\n\n                update(dt) {\n                    this.frame += dt * 10;\n                    \n                    // Follow player logic - move freely but stay close\n                    const index = partners.indexOf(this);\n                    const staggerX = 80 + (index * 45); // Spread them out\n                    const targetX = player.x - (player.facing * staggerX);\n                    const targetY = player.y - (Math.sin(this.frame * 0.05 + index) * 20); // Individual vertical bobbing\n                    \n                    const dx = targetX - this.x;\n                    const dy = targetY - this.y;\n                    const distSq = dx * dx + dy * dy;\n                    \n                    // Faster follow speed to keep up with player and swinging movement\n                    const followSpeed = 1200; \n                    \n                    if (distSq > 2000 * 2000) {\n                        this.x = player.x;\n                        this.y = player.y;\n                    } else if (distSq > 120 * 120) {\n                        const dist = Math.sqrt(distSq);\n                        this.x += (dx / dist) * followSpeed * dt;\n                        this.y += (dy / dist) * followSpeed * dt;\n                        this.animation = 'walk';\n                    } else if (distSq < 60 * 60) {\n                        const dist = Math.sqrt(distSq);\n                        this.x -= (dx / dist) * 200 * dt;\n                        this.y -= (dy / dist) * 200 * dt;\n                        this.animation = 'walk';\n                    } else {\n                        this.animation = 'idle';\n                        // Gentle adjustment to maintain position\n                        this.y += (targetY - this.y) * dt * 5;\n                        this.x += (targetX - this.x) * dt * 5;\n                    }\n                    \n                    this.facing = player.facing;\n\n                    // Attack logic\n                    this.attackTimer -= dt;\n                    if (this.attackTimer <= 0) {\n                        const nearest = getNearestEnemy(this.x, this.y, 500);\n                        if (nearest) {\n                            this.useAbility(nearest);\n                            this.attackTimer = this.attackCooldown;\n                        }\n                    }\n                }\n\n                useAbility(target) {\n                    this.animation = 'punch';\n                    setTimeout(() => this.animation = 'idle', 400);\n                    \n                    const dx = target.x - this.x;\n                    const dy = (target.y - 30) - (this.y - 30);\n                    const dist = Math.sqrt(dx * dx + dy * dy) || 1;\n                    const vx = (dx / dist) * 1200;\n                    const vy = (dy / dist) * 1200;\n                    this.facing = dx > 0 ? 1 : -1;\n\n                    switch(this.ability) {\n                        case 'repulsor':\n                            projectiles.push(new Projectile(this.x, this.y - 30, vx, vy, 'bolt', 'player'));\n                            playSound('web_sfx', 0.3);\n                            break;\n                        case 'shield':\n                            projectiles.push(new Projectile(this.x, this.y - 30, vx, vy, 'shield', 'player'));\n                            playSound('punch_sfx', 0.5);\n                            break;\n                        case 'lightning':\n                            projectiles.push(new Projectile(target.x, 0, 0, 1500, 'bolt', 'player'));\n                            target.takeDamage(this.damage);\n                            break;\n                        case 'gun':\n                            projectiles.push(new Projectile(this.x, this.y - 30, vx, vy, 'bolt', 'player'));\n                            break;\n                        case 'cosmic':\n                            for(let i=-1; i<=1; i++) {\n                                projectiles.push(new Projectile(this.x, this.y - 30, vx, vy + i*100, 'bolt', 'player'));\n                            }\n                            break;\n                        default:\n                            target.takeDamage(this.damage);\n                            if (this.type === 'hulk') target.knockback(Math.sign(target.x - this.x) * 150);\n                            playSound('punch_sfx', 0.4);\n                            break;\n                    }\n                }\n\n                draw() {\n                    ctx.save();\n                    ctx.scale(this.facing, 1);\n                    drawStickFigure(this.x * this.facing, this.y, this.color, this.scale, this.animation, this.frame, false, this.type);\n                    ctx.restore();\n                    \n                    ctx.fillStyle = 'white';\n                    ctx.font = 'bold 16px Arial';\n                    ctx.textAlign = 'center';\n                    ctx.fillText(this.type.toUpperCase(), this.x, this.y - 90);\n                }\n            }\n            const shopItems = [\n                { id: 'venom', name: 'Venom', cost: 2000, color: '#000000', damage: 30, cooldown: 1.0, scale: 1.4, ability: 'bite', description: 'Lunges and bites enemies' },\n                { id: 'cap', name: 'Captain America', cost: 4000, color: '#0000ff', damage: 40, cooldown: 1.2, scale: 1.3, ability: 'shield', description: 'Throws shield' },\n                { id: 'ironman', name: 'Iron Man', cost: 6000, color: '#ff0000', damage: 25, cooldown: 0.8, scale: 1.2, ability: 'repulsor', description: 'Energy blasts' },\n                { id: 'hulk', name: 'Hulk', cost: 10000, color: '#008000', damage: 80, cooldown: 2.0, scale: 1.8, ability: 'smash', description: 'Ground slam' },\n                { id: 'thor', name: 'Thor', cost: 15000, color: '#c0c0c0', damage: 60, cooldown: 1.5, scale: 1.4, ability: 'lightning', description: 'Summons lightning' },\n                { id: 'blackpanther', name: 'Black Panther', cost: 8000, color: '#1a1a1a', damage: 50, cooldown: 1.1, scale: 1.3, ability: 'claw', description: 'Quick multi-slash' },\n                { id: 'deadpool', name: 'Deadpool', cost: 7000, color: '#cc0000', damage: 45, cooldown: 0.9, scale: 1.2, ability: 'gun', description: 'Rapid gunfire' },\n                { id: 'strange', name: 'Doctor Strange', cost: 12000, color: '#9933ff', damage: 35, cooldown: 1.4, scale: 1.2, ability: 'magic', description: 'Magical barriers' },\n                { id: 'surfer', name: 'Silver Surfer', cost: 14000, color: '#d4af37', damage: 70, cooldown: 1.3, scale: 1.3, ability: 'cosmic', description: 'Cosmic energy waves' },\n                { id: 'widow', name: 'Black Widow', cost: 6500, color: '#2d2d2d', damage: 55, cooldown: 1.0, scale: 1.1, ability: 'assassin', description: 'Shadow strike' }\n            ];\n\n            // Stick figure rendering\n            function drawStickFigure(x, y, color, scale = 1, animation = 'idle', frame = 0, isStunned = false, charType = 'basic') {\n                ctx.save();\n                ctx.translate(x, y);\n                ctx.scale(scale, scale);\n                ctx.lineWidth = 5; // Fixed: Set consistent line width for visibility\n\n                if (isStunned) {\n                    ctx.strokeStyle = 'rgba(255, 255, 255, 0.6)';\n                    ctx.lineWidth = 2;\n                    for (let i = 0; i < 12; i++) {\n                        const ang = (i / 12) * Math.PI * 2;\n                        ctx.beginPath();\n                        ctx.moveTo(0, -30);\n                        ctx.lineTo(Math.cos(ang) * 40, -30 + Math.sin(ang) * 40);\n                        ctx.stroke();\n                    }\n                }\n\n                // Character specific features\n                if (charType === 'player') {\n                    ctx.strokeStyle = '#00000033';\n                    ctx.lineWidth = 1;\n                    ctx.beginPath();\n                    ctx.arc(0, -50, 10, 0, Math.PI * 2);\n                    ctx.stroke();\n                } else if (charType === 'goblin') {\n                    ctx.fillStyle = '#444';\n                    ctx.beginPath();\n                    ctx.moveTo(-30, 0); ctx.lineTo(30, 0); ctx.lineTo(0, 10);\n                    ctx.fill();\n                    ctx.fillStyle = '#ff4400'; ctx.fillRect(-10, 5, 20, 5); \n                } else if (charType === 'docock') {\n                    ctx.strokeStyle = '#888';\n                    ctx.lineWidth = 3;\n                    for (let i = 0; i < 4; i++) {\n                        const ang = Math.sin(frame * 0.1 + i) * 0.5 + (i < 2 ? -Math.PI : 0);\n                        ctx.beginPath(); ctx.moveTo(0, -30);\n                        ctx.quadraticCurveTo(Math.cos(ang) * 60, -30 + Math.sin(ang) * 60, Math.cos(ang) * 80, -30 + Math.sin(ang) * 80);\n                        ctx.stroke();\n                        ctx.fillStyle = '#ff0000'; ctx.beginPath(); ctx.arc(Math.cos(ang)*80, -30+Math.sin(ang)*80, 4, 0, Math.PI*2); ctx.fill();\n                    }\n                } else if (charType === 'electro') {\n                    ctx.strokeStyle = '#ffff00'; ctx.lineWidth = 2;\n                    for(let i=0; i<3; i++) {\n                        ctx.beginPath(); ctx.moveTo(0, -30);\n                        ctx.lineTo((Math.random()-0.5)*100, -30+(Math.random()-0.5)*100); ctx.stroke();\n                    }\n                } else if (charType === 'sandman') {\n                    ctx.fillStyle = '#d2b48c'; ctx.beginPath(); ctx.arc(0, -30, 25, 0, Math.PI*2); ctx.fill();\n                } else if (charType === 'venom') {\n                    ctx.scale(1.3, 1.1);\n                    ctx.fillStyle = 'white';\n                    ctx.beginPath(); ctx.ellipse(-5, -53, 4, 6, 0.4, 0, Math.PI * 2); ctx.fill();\n                    ctx.beginPath(); ctx.ellipse(5, -53, 4, 6, -0.4, 0, Math.PI * 2); ctx.fill();\n                    ctx.fillStyle = 'red'; ctx.fillRect(-2, -45, 4, 10); \n                } else if (charType === 'cap') {\n                    ctx.fillStyle = 'blue'; ctx.beginPath(); ctx.arc(-15, -30, 12, 0, Math.PI * 2); ctx.fill();\n                    ctx.strokeStyle = 'white'; ctx.lineWidth = 2; ctx.stroke();\n                    ctx.fillStyle = 'red'; ctx.beginPath(); ctx.arc(-15, -30, 8, 0, Math.PI * 2); ctx.fill();\n                    ctx.fillStyle = 'white'; ctx.beginPath(); ctx.arc(-15, -30, 4, 0, Math.PI * 2); ctx.fill();\n                } else if (charType === 'ironman') {\n                    ctx.fillStyle = '#00ffff'; ctx.beginPath(); ctx.arc(0, -30, 4, 0, Math.PI * 2); ctx.fill(); // Larger reactor\n                    ctx.strokeStyle = 'white'; ctx.lineWidth = 1; ctx.stroke();\n                    if(animation === 'fly' || animation === 'web') {\n                        ctx.fillStyle = 'orange'; ctx.fillRect(-5, 5, 4, 12); ctx.fillRect(1, 5, 4, 12);\n                    }\n                } else if (charType === 'hulk') {\n                    ctx.scale(1.5, 1.3);\n                } else if (charType === 'thor') {\n                    ctx.fillStyle = '#888'; ctx.fillRect(15, -40, 15, 10);\n                    ctx.strokeStyle = '#444'; ctx.beginPath(); ctx.moveTo(22, -30); ctx.lineTo(22, -20); ctx.stroke();\n                    ctx.fillStyle = '#ff0000'; ctx.fillRect(-10, -40, 5, 30);\n                    if (animation === 'fly') {\n                        ctx.save(); ctx.translate(22, -35); ctx.rotate(frame * 0.5); \n                        ctx.fillStyle = '#888'; ctx.fillRect(-15, -5, 15, 10); ctx.restore();\n                    }\n                } else if (charType === 'blackpanther') {\n                    ctx.fillStyle = '#555'; ctx.beginPath(); ctx.moveTo(-5, -60); ctx.lineTo(-10, -70); ctx.lineTo(-2, -60); ctx.fill();\n                    ctx.beginPath(); ctx.moveTo(5, -60); ctx.lineTo(10, -70); ctx.lineTo(2, -60); ctx.fill(); \n                } else if (charType === 'deadpool') {\n                    ctx.strokeStyle = 'black'; ctx.lineWidth = 3;\n                    ctx.beginPath(); ctx.moveTo(-5, -20); ctx.lineTo(-15, -40); ctx.stroke(); \n                    ctx.beginPath(); ctx.moveTo(5, -20); ctx.lineTo(15, -40); ctx.stroke();\n                } else if (charType === 'strange') {\n                    ctx.fillStyle = '#800000'; ctx.fillRect(-12, -50, 24, 40); \n                    ctx.strokeStyle = '#9933ff'; ctx.beginPath(); ctx.arc(0, -30, 25, 0, Math.PI*2); ctx.stroke(); \n                    if (animation === 'fly') {\n                        ctx.strokeStyle = '#ffff00'; ctx.lineWidth = 1; ctx.beginPath(); ctx.arc(0, 10, 20, 0, Math.PI*2); ctx.stroke();\n                    }\n                } else if (charType === 'surfer') {\n                    ctx.fillStyle = '#silver'; ctx.beginPath(); ctx.ellipse(0, 5, 30, 5, 0, 0, Math.PI*2); ctx.fill(); \n                    if (animation === 'fly') {\n                        ctx.shadowBlur = 15; ctx.shadowColor = 'white';\n                    }\n                } else if (charType === 'widow') {\n                    ctx.fillStyle = '#f00'; ctx.beginPath(); ctx.moveTo(0, -35); ctx.lineTo(5, -30); ctx.lineTo(0, -25); ctx.lineTo(-5, -30); ctx.fill(); \n                }\n\n                // Head\n                ctx.beginPath();\n                ctx.arc(0, -50, 10, 0, Math.PI * 2);\n                ctx.fillStyle = color;\n                ctx.fill();\n                \n                // Torso (Fixed: Added missing body line)\n                ctx.strokeStyle = color;\n                ctx.lineWidth = 5;\n                if (charType === 'player') {\n                    ctx.strokeStyle = '#f00'; // Superhero red torso\n                }\n                ctx.beginPath();\n                ctx.moveTo(0, -40);\n                ctx.lineTo(0, -10);\n                ctx.stroke();\n\n                // Player/Hero Specific Details\n                if (charType === 'player') {\n                    // Head Web Pattern\n                    ctx.strokeStyle = 'rgba(0,0,0,0.5)';\n                    ctx.lineWidth = 1;\n                    for (let i = 0; i < 4; i++) {\n                        const ang = (i / 4) * Math.PI;\n                        ctx.beginPath();\n                        ctx.moveTo(Math.cos(ang) * 10, -50 + Math.sin(ang) * 10);\n                        ctx.lineTo(-Math.cos(ang) * 10, -50 - Math.sin(ang) * 10);\n                        ctx.stroke();\n                    }\n                    ctx.beginPath(); ctx.arc(0, -50, 6, 0, Math.PI * 2); ctx.stroke();\n\n                    // Eyes\n                    ctx.fillStyle = 'white';\n                    ctx.beginPath();\n                    ctx.ellipse(-4, -53, 3, 5, 0.3, 0, Math.PI * 2);\n                    ctx.fill();\n                    ctx.strokeStyle = 'black'; ctx.lineWidth = 1; ctx.stroke();\n                    ctx.beginPath();\n                    ctx.ellipse(4, -53, 3, 5, -0.3, 0, Math.PI * 2);\n                    ctx.fill();\n                    ctx.stroke();\n\n                    // Spider Logo on chest\n                    ctx.fillStyle = 'black';\n                    ctx.beginPath();\n                    ctx.arc(0, -30, 3, 0, Math.PI * 2);\n                    ctx.fill();\n                    ctx.lineWidth = 1;\n                    ctx.strokeStyle = 'black';\n                    for (let i = 0; i < 4; i++) {\n                        ctx.beginPath(); ctx.moveTo(0, -30); ctx.lineTo(-7, -35 + i * 3); ctx.stroke();\n                        ctx.beginPath(); ctx.moveTo(0, -30); ctx.lineTo(7, -35 + i * 3); ctx.stroke();\n                    }\n                }\n\n                ctx.lineWidth = 5; // Reset for limbs\n                let leftArmAngle = 0, rightArmAngle = 0, leftLegAngle = 0, rightLegAngle = 0;\n                switch (animation) {\n                    case 'idle':\n                        const bob = Math.sin(frame * 0.1) * 2;\n                        ctx.translate(0, bob);\n                        leftArmAngle = -30; rightArmAngle = 30; leftLegAngle = -10; rightLegAngle = 10;\n                        break;\n                    case 'walk':\n                        leftArmAngle = Math.sin(frame * 0.2) * 30;\n                        rightArmAngle = -Math.sin(frame * 0.2) * 30;\n                        leftLegAngle = Math.sin(frame * 0.2) * 40;\n                        rightLegAngle = -Math.sin(frame * 0.2) * 40;\n                        break;\n                    case 'punch':\n                        rightArmAngle = 90; leftArmAngle = -20;\n                        break;\n                    case 'kick':\n                        rightLegAngle = 70; leftLegAngle = -10;\n                        break;\n                    case 'web': case 'fly':\n                        leftArmAngle = 60; rightArmAngle = 60;\n                        break;\n                }\n\n                const armLength = 20;\n                ctx.strokeStyle = color;\n                if (charType === 'player') ctx.strokeStyle = '#f00'; \n                ctx.beginPath(); ctx.moveTo(0, -30);\n                const leftArmX = Math.sin(leftArmAngle * Math.PI / 180) * armLength;\n                const leftArmY = Math.cos(leftArmAngle * Math.PI / 180) * armLength;\n                ctx.lineTo(-leftArmX, -30 + leftArmY); ctx.stroke();\n\n                ctx.beginPath(); ctx.moveTo(0, -30);\n                const rightArmX = Math.sin(rightArmAngle * Math.PI / 180) * armLength;\n                const rightArmY = Math.cos(rightArmAngle * Math.PI / 180) * armLength;\n                ctx.lineTo(rightArmX, -30 + rightArmY); ctx.stroke();\n\n                const legLength = 25;\n                if (charType === 'player') ctx.strokeStyle = '#00f'; \n                ctx.beginPath(); ctx.moveTo(0, -10);\n                const leftLegX = Math.sin(leftLegAngle * Math.PI / 180) * legLength;\n                const leftLegY = Math.cos(leftLegAngle * Math.PI / 180) * legLength;\n                ctx.lineTo(-leftLegX, -10 + leftLegY); ctx.stroke();\n\n                ctx.beginPath(); ctx.moveTo(0, -10);\n                const rightLegX = Math.sin(rightLegAngle * Math.PI / 180) * legLength;\n                const rightLegY = Math.cos(rightLegAngle * Math.PI / 180) * legLength;\n                ctx.lineTo(rightLegX, -10 + rightLegY); ctx.stroke();\n\n                ctx.restore();\n            }\n\n            // PowerUp class\n            class PowerUp {\n                constructor(x, y, type) {\n                    this.x = x;\n                    this.y = y;\n                    this.type = type; // 'health', 'score'\n                    this.age = 0;\n                    this.collected = false;\n                    this.radius = 25;\n                }\n\n                update(dt) {\n                    this.age += dt;\n                    // Floating effect\n                    this.yOffset = Math.sin(this.age * 3) * 10;\n                    \n                    // Collision with player\n                    if (player && !this.collected) {\n                        const dx = this.x - player.x;\n                        const dy = (this.y + this.yOffset) - (player.y - 30);\n                        const dist = Math.sqrt(dx * dx + dy * dy);\n                        if (dist < 60) {\n                            this.collect();\n                        }\n                    }\n                }\n\n                collect() {\n                    this.collected = true;\n                    if (this.type === 'health') {\n                        player.health = Math.min(player.maxHealth, player.health + 50);\n                        floatingTexts.push({\n                            text: \"HEAL!\", x: this.x, y: this.y, color: \"#00ff00\", lifetime: 1.5, age: 0\n                        });\n                        playSound('combo_sfx', 0.5);\n                    } else {\n                        score += 500;\n                        floatingTexts.push({\n                            text: \"+500\", x: this.x, y: this.y, color: \"#FFD700\", lifetime: 1.5, age: 0\n                        });\n                        playSound('combo_sfx', 0.5);\n                    }\n                }\n\n                draw() {\n                    if (this.collected) return;\n                    ctx.save();\n                    ctx.translate(this.x, this.y + this.yOffset);\n                    \n                    // Glowing aura\n                    const glow = ctx.createRadialGradient(0, 0, 5, 0, 0, this.radius + 10);\n                    glow.addColorStop(0, this.type === 'health' ? 'rgba(255, 0, 0, 0.8)' : 'rgba(255, 215, 0, 0.8)');\n                    glow.addColorStop(1, 'rgba(255, 255, 255, 0)');\n                    ctx.fillStyle = glow;\n                    ctx.beginPath();\n                    ctx.arc(0, 0, this.radius + 10, 0, Math.PI * 2);\n                    ctx.fill();\n\n                    // Icon\n                    ctx.fillStyle = 'white';\n                    ctx.textAlign = 'center';\n                    ctx.textBaseline = 'middle';\n                    ctx.font = 'bold 30px Arial';\n                    ctx.fillText(this.type === 'health' ? '\u2665' : '\u2605', 0, 0);\n                    \n                    ctx.restore();\n                }\n            }\n            // Player class\n            class Player {\n                constructor() {\n                    this.x = PLAYER_START_X;\n                    this.y = GROUND_Y;\n                    this.vx = 0;\n                    this.vy = 0;\n                    this.health = config.playerHealth;\n                    this.maxHealth = config.playerHealth;\n                    this.facing = 1; // 1 = right, -1 = left\n                    this.animation = 'idle';\n                    this.animationTimer = 0;\n                    this.frame = 0;\n                    \n                    // Morph state\n                    this.morphedInto = null; // null means spiderman\n                    this.morphTime = 0;\n                    this.maxMorphTime = 20; // 20 seconds of morph\n                    this.morphCooldown = 0;\n\n                    // Attack cooldowns\n                    this.punchCooldown = 0;\n                    this.kickCooldown = 0;\n                    this.webCooldown = 0;\n                    this.specialCooldown = 0;\n                    \n                    // Hit flash\n                    this.hitFlash = 0;\n\n                    // Physics state\n                    this.isGrounded = true;\n                    this.isSwinging = false;\n                    this.isFlying = false;\n                    this.flightTarget = { x: 0, y: 0 };\n                    this.swingAnchor = { x: 0, y: 0 };\n                    this.swingLength = 0;\n                    this.swingAngle = 0;\n                    this.swingAngularVelocity = 0;\n                }\n\n                update(dt, inputY = 0) {\n                    if (this.morphTime > 0) {\n                        this.morphTime -= dt;\n                        if (this.morphTime <= 0) {\n                            this.unmorph();\n                        }\n                    }\n                    if (this.morphCooldown > 0) {\n                        this.morphCooldown -= dt;\n                    }\n\n                    if (this.isFlying) {\n                        // Flying physics - move directly to target\n                        const dx = this.flightTarget.x - this.x;\n                        const dy = this.flightTarget.y - this.y;\n                        const distSq = dx * dx + dy * dy;\n                        const speed = 800;\n\n                        if (distSq > 100) {\n                            const dist = Math.sqrt(distSq);\n                            this.vx = (dx / dist) * speed;\n                            this.vy = (dy / dist) * speed;\n                            this.x += this.vx * dt;\n                            this.y += this.vy * dt;\n                        } else {\n                            this.vx *= 0.9;\n                            this.vy *= 0.9;\n                            this.isFlying = false; // Stop when reached\n                        }\n                        this.animation = 'fly';\n                    } else if (this.isSwinging) {\n                        // Pendulum physics\n                        const gravity = 1500;\n                        const angularAcceleration = -(gravity / this.swingLength) * Math.sin(this.swingAngle);\n                        \n                        // Air resistance / damping\n                        this.swingAngularVelocity *= 0.995; \n                        this.swingAngularVelocity += angularAcceleration * dt;\n                        this.swingAngle += this.swingAngularVelocity * dt;\n\n                        // Up/Down movement while swinging\n                        if (inputY < 0) {\n                            this.swingLength = Math.max(100, this.swingLength - 300 * dt);\n                        } else if (inputY > 0) {\n                            this.swingLength = Math.min(800, this.swingLength + 300 * dt);\n                        }\n\n                        // Update position\n                        this.x = this.swingAnchor.x + Math.sin(this.swingAngle) * this.swingLength;\n                        this.y = this.swingAnchor.y + Math.cos(this.swingAngle) * this.swingLength;\n\n                        // Update velocity for when we let go\n                        this.vx = Math.cos(this.swingAngle) * this.swingAngularVelocity * this.swingLength;\n                        this.vy = -Math.sin(this.swingAngle) * this.swingAngularVelocity * this.swingLength;\n\n                        this.animation = 'web';\n                    } else {\n                        // Regular movement and gravity\n                        this.x += this.vx * dt;\n                        \n                        if (!this.isGrounded) {\n                            this.vy += 1500 * dt; // Gravity\n                            this.y += this.vy * dt;\n                            \n                            if (this.y >= GROUND_Y) {\n                                this.y = GROUND_Y;\n                                this.vy = 0;\n                                this.isGrounded = true;\n                                this.animation = 'idle';\n                            } else {\n                                this.animation = 'web'; // Use web pose for air\n                            }\n                        }\n\n                        // Apply a bit of air friction if not swinging\n                        if (!this.isGrounded) {\n                            this.vx *= 0.99;\n                        }\n                    }\n\n                    this.x = Math.max(MIN_X, Math.min(MAX_X, this.x));\n\n                    // Update animation frame\n                    this.frame += dt * 10;\n                    \n                    // Update facing direction (Targeting System)\n                    const nearest = getNearestEnemy(this.x, this.y, 600);\n                    if (nearest) {\n                        this.facing = nearest.x > this.x ? 1 : -1;\n                    } else if (Math.abs(this.vx) > 10) {\n                        this.facing = this.vx > 0 ? 1 : -1;\n                    }\n\n                    if (this.animationTimer > 0) {\n                        this.animationTimer -= dt;\n                    } else if (this.isGrounded && !this.isSwinging) {\n                        this.animation = Math.abs(this.vx) > 10 ? 'walk' : 'idle';\n                    }\n                }\n\n                jump() {\n                    if (this.isGrounded) {\n                        this.vy = -800;\n                        this.isGrounded = false;\n                        this.isSwinging = false;\n                        this.animation = 'web';\n                        playSound('punch_sfx', 0.3); // Reuse jump-like sound\n                    }\n                }\n\n                toggleSwing(targetX, targetY) {\n                    const flyers = ['ironman', 'surfer', 'thor', 'strange'];\n                    const isFlyer = flyers.includes(this.morphedInto);\n\n                    if (this.isSwinging || this.isFlying) {\n                        this.isSwinging = false;\n                        this.isFlying = false;\n                        return;\n                    }\n\n                    if (isFlyer) {\n                        this.isFlying = true;\n                        this.flightTarget = { \n                            x: targetX !== undefined ? targetX : this.x + this.facing * 300, \n                            y: targetY !== undefined ? targetY : this.y - 300 \n                        };\n                        this.isGrounded = false;\n                        this.isSwinging = false;\n                        playSound('web_sfx', 0.2); // Glide sound\n                        return;\n                    }\n\n                    // Use target coordinates if provided, otherwise use default logic\n                    if (targetX !== undefined && targetY !== undefined) {\n                        this.swingAnchor = { x: targetX, y: targetY };\n                    } else {\n                        this.swingAnchor = { \n                            x: this.x + this.facing * 150, \n                            y: -100 // Default high sky anchor\n                        };\n                    }\n\n                    const dx = this.x - this.swingAnchor.x;\n                    const dy = this.y - this.swingAnchor.y;\n                    this.swingLength = Math.sqrt(dx*dx + dy*dy);\n                    this.swingAngle = Math.atan2(dx, dy); // Angle from vertical\n                    \n                    // Initial angular velocity based on current linear velocity\n                    this.swingAngularVelocity = (this.vx * Math.cos(this.swingAngle) - this.vy * Math.sin(this.swingAngle)) / this.swingLength;\n                    \n                    this.isSwinging = true;\n                    this.isGrounded = false;\n                    playSound('web_sfx', 0.5);\n                }\n\n                punch() {\n                    if (this.punchCooldown > 0) return;\n                    this.animation = 'punch';\n                    this.animationTimer = 0.2;\n                    this.punchCooldown = 0.3;\n                    playSound('punch_sfx', 0.5);\n                    \n                    this.checkHit(80, 30);\n                }\n\n                kick() {\n                    if (this.kickCooldown > 0) return;\n                    this.animation = 'kick';\n                    this.animationTimer = 0.3;\n                    this.kickCooldown = 0.6;\n                    playSound('kick_sfx', 0.5);\n                    \n                    const hitEnemies = this.checkHit(90, 50);\n                    hitEnemies.forEach(enemy => {\n                        enemy.knockback(this.facing * 40);\n                    });\n                }\n\n                webAttack() {\n                    if (this.webCooldown > 0) return;\n                    this.animation = 'web';\n                    this.animationTimer = 0.4;\n                    this.webCooldown = 1.0;\n                    playSound('web_sfx', 0.5);\n                    \n                    // Targeting system for web shooter\n                    const nearest = getNearestEnemy(this.x, this.y - 30);\n                    let vx, vy;\n                    \n                    if (nearest) {\n                        const dx = nearest.x - this.x;\n                        const dy = (nearest.y - 30) - (this.y - 30);\n                        const dist = Math.sqrt(dx * dx + dy * dy);\n                        vx = (dx / dist) * 1000;\n                        vy = (dy / dist) * 1000;\n                    } else {\n                        vx = this.facing * 800;\n                        vy = (Math.random() - 0.5) * 100;\n                    }\n                    \n                    projectiles.push(new Projectile(this.x, this.y - 30, vx, vy, 'web'));\n                }\n\n                checkHit(range, damage) {\n                    const hitEnemies = [];\n                    enemies.forEach(enemy => {\n                        if (enemy.health <= 0) return;\n                        const dist = Math.abs(enemy.x - this.x);\n                        if (dist < range && Math.sign(enemy.x - this.x) === this.facing) {\n                            enemy.takeDamage(damage);\n                            hitEnemies.push(enemy);\n                            \n                            // Update combo\n                            combo++;\n                            comboTimer = config.comboTimeWindow;\n                            bestCombo = Math.max(bestCombo, combo);\n                            \n                            // Particles\n                            for (let i = 0; i < 8; i++) {\n                                particles.push({\n                                    type: 'hit',\n                                    x: enemy.x,\n                                    y: enemy.y - 30,\n                                    vx: (Math.random() - 0.5) * 200,\n                                    vy: (Math.random() - 0.5) * 200,\n                                    lifetime: 0.3,\n                                    age: 0\n                                });\n                            }\n                        }\n                    });\n                    return hitEnemies;\n                }\n\n                takeDamage(damage) {\n                    if (this.morphedInto === 'strange' && this.animationTimer > 0) return; \n                    this.health -= damage;\n                    this.hitFlash = 0.2;\n                    playSound('player_hit_sfx', 0.5);\n                    \n                    if (this.health <= 0) {\n                        this.health = 0;\n                        gameOver();\n                    }\n                }\n\n                morph(partnerType) {\n                    const partnerData = shopItems.find(i => i.id === partnerType);\n                    if (!partnerData) return;\n\n                    this.morphedInto = partnerType;\n                    this.morphTime = this.maxMorphTime;\n                    this.color = partnerData.color;\n                    this.scale = partnerData.scale;\n                    \n                    playSound('combo_sfx', 1.0);\n                    // Particles\n                    for(let i=0; i<20; i++) {\n                        particles.push({\n                            type: 'hit', x: this.x, y: this.y-30,\n                            vx: (Math.random()-0.5)*400, vy: (Math.random()-0.5)*400,\n                            lifetime: 0.5, age: 0\n                        });\n                    }\n                }\n\n                unmorph() {\n                    this.morphedInto = null;\n                    this.morphCooldown = 0; \n                    this.isFlying = false;\n                    currentMorphIndex = -1; // Reset index so next click starts cycle fresh\n                    playSound('web_sfx', 0.5);\n                }\n\n                specialAbility() {\n                    if (!this.morphedInto || this.specialCooldown > 0) return;\n                    \n                    const partnerData = shopItems.find(i => i.id === this.morphedInto);\n                    this.specialCooldown = partnerData.cooldown * 2;\n                    this.animation = 'punch';\n                    this.animationTimer = 0.5;\n\n                    // Targeting system for special abilities\n                    const target = getNearestEnemy(this.x, this.y - 30, 800);\n                    let vx = this.facing * 1200;\n                    let vy = 0;\n                    if (target) {\n                        const dx = target.x - this.x;\n                        const dy = (target.y - 30) - (this.y - 30);\n                        const dist = Math.sqrt(dx * dx + dy * dy) || 1;\n                        vx = (dx / dist) * 1200;\n                        vy = (dy / dist) * 1200;\n                        this.facing = dx > 0 ? 1 : -1; // Ensure facing enemy\n                    }\n\n                    switch(this.morphedInto) {\n                        case 'venom':\n                            this.checkHit(150, 100);\n                            playSound('punch_sfx', 1.0);\n                            break;\n                        case 'cap':\n                            projectiles.push(new Projectile(this.x, this.y-30, vx, vy, 'shield', 'player')); // Shield throw with aiming\n                            break;\n                        case 'ironman':\n                            for(let i=-2; i<=2; i++) {\n                                projectiles.push(new Projectile(this.x, this.y-30, vx, vy + i*100, 'bolt', 'player'));\n                            }\n                            break;\n                        case 'hulk':\n                            this.checkHit(200, 150);\n                            // Hulk Smash shockwave\n                            for(let i=0; i<10; i++) {\n                                particles.push({\n                                    type: 'defeat', x: this.x + this.facing*100, y: GROUND_Y,\n                                    vx: (Math.random()-0.5)*200, vy: -Math.random()*500,\n                                    lifetime: 1.0, age: 0\n                                });\n                            }\n                            break;\n                        case 'thor':\n                            // Lightning strike\n                            enemies.forEach(e => {\n                                if (!e.defeated && Math.abs(e.x - this.x) < 400) {\n                                    e.takeDamage(100);\n                                    projectiles.push(new Projectile(e.x, 0, 0, 2000, 'bolt', 'player'));\n                                }\n                            });\n                            break;\n                        case 'blackpanther':\n                            this.vx = this.facing * 2000; // Dash\n                            this.checkHit(150, 80);\n                            break;\n                        case 'deadpool':\n                            projectiles.push(new Projectile(this.x, this.y-30, vx, vy, 'bolt', 'player'));\n                            setTimeout(() => projectiles.push(new Projectile(this.x, this.y-30, vx, vy, 'bolt', 'player')), 100);\n                            break;\n                        case 'strange':\n                            this.stunTimer = 2.0; // Invulnerable for 2s\n                            break;\n                        case 'surfer':\n                            projectiles.push(new Projectile(this.x, this.y-30, vx, vy, 'bolt', 'player'));\n                            break;\n                        case 'widow':\n                            this.x += this.facing * 300; // Teleport forward\n                            this.checkHit(100, 120);\n                            break;\n                    }\n                }\n\n                draw() {\n                    const charType = this.morphedInto || 'player';\n                    const displayColor = this.hitFlash > 0 ? '#ff8888' : (this.morphedInto ? shopItems.find(i => i.id === this.morphedInto).color : config.playerColor);\n                    const displayScale = this.morphedInto ? shopItems.find(i => i.id === this.morphedInto).scale : 1.5;\n                    \n                    ctx.save();\n                    ctx.scale(this.facing, 1);\n                    drawStickFigure(this.x * this.facing, this.y, displayColor, displayScale, this.animation, this.frame, false, charType);\n                    ctx.restore();\n                }\n\n                updateCooldowns(dt) {\n                    this.punchCooldown = Math.max(0, this.punchCooldown - dt);\n                    this.kickCooldown = Math.max(0, this.kickCooldown - dt);\n                    this.webCooldown = Math.max(0, this.webCooldown - dt);\n                    this.specialCooldown = Math.max(0, this.specialCooldown - dt);\n                    this.hitFlash = Math.max(0, this.hitFlash - dt);\n                }\n            }\n\n            // Enemy class\n            class Enemy {\n                constructor(type, side) {\n                    this.type = type;\n                    this.x = side === 'left' ? -100 : canvas.width + 100;\n                    this.y = GROUND_Y;\n                    this.targetX = PLAYER_START_X;\n                    this.speed = config.enemySpeed;\n                    this.facing = side === 'left' ? 1 : -1;\n                    this.animation = 'walk';\n                    this.frame = 0;\n                    this.isBoss = ['goblin', 'docock', 'electro', 'sandman', 'venom'].includes(type);\n                    \n                    if (type === 'basic') {\n                        this.health = 60;\n                        this.maxHealth = 60;\n                        this.damage = 10;\n                        this.attackCooldown = 1.5;\n                        this.scale = 1.2;\n                        this.scoreValue = 10;\n                        this.color = config.enemyColor;\n                    } else if (type === 'tough') {\n                        this.health = 120;\n                        this.maxHealth = 120;\n                        this.damage = 15;\n                        this.attackCooldown = 1.2;\n                        this.scale = 1.5;\n                        this.scoreValue = 20;\n                        this.color = '#555';\n                    } else if (this.isBoss) {\n                        this.health = 500 + (currentWave * 50);\n                        this.maxHealth = this.health;\n                        this.damage = 25;\n                        this.attackCooldown = 2.0;\n                        this.scale = 2.0;\n                        this.scoreValue = 500;\n                        \n                        switch(type) {\n                            case 'goblin': this.color = '#2e8b57'; this.y = GROUND_Y - 300; break;\n                            case 'docock': this.color = '#228b22'; break;\n                            case 'electro': this.color = '#ffd700'; this.speed *= 1.5; break;\n                            case 'sandman': this.color = '#c2b280'; this.scale = 2.5; this.health *= 1.5; break;\n                            case 'venom': this.color = '#000000'; this.speed *= 1.3; break;\n                        }\n                    }\n                    \n                    this.attackTimer = this.attackCooldown;\n                    this.stunTimer = 0;\n                    this.knockbackVx = 0;\n                    this.defeated = false;\n                    this.deathTimer = 0;\n                }\n\n                update(dt) {\n                    this.frame += dt * 10;\n\n                    if (this.defeated) {\n                        this.deathTimer += dt;\n                        this.y += dt * 100;\n                        return;\n                    }\n\n                    if (this.stunTimer > 0) {\n                        this.stunTimer -= dt;\n                        return;\n                    }\n\n                    if (Math.abs(this.knockbackVx) > 1) {\n                        this.x += this.knockbackVx * dt;\n                        this.knockbackVx *= 0.9;\n                    } else {\n                        this.knockbackVx = 0;\n                    }\n\n                    const dx = player.x - this.x;\n                    const dy = player.y - this.y;\n                    const distToPlayerX = Math.abs(dx);\n                    const distToPlayerY = Math.abs(dy);\n                    const meleeRangeY = 100; // Threshold for ground attacks reaching the air\n\n                    if (this.type === 'goblin') {\n                        this.y = GROUND_Y - 300 + Math.sin(this.frame * 0.2) * 50;\n                        this.animation = 'fly';\n                        if (distToPlayerX > 350) {\n                            this.x += Math.sign(dx) * this.speed * 1.5 * dt;\n                        } else if (distToPlayerX < 250) {\n                            this.x -= Math.sign(dx) * this.speed * 1.5 * dt;\n                        }\n                        this.facing = dx > 0 ? 1 : -1;\n                        \n                        this.attackTimer -= dt;\n                        if (this.attackTimer <= 0) {\n                            projectiles.push(new Projectile(this.x, this.y, dx, dy, 'bomb', 'enemy'));\n                            this.attackTimer = 1.0;\n                            playSound('punch_sfx', 0.5);\n                        }\n                    } else if (this.type === 'electro') {\n                        this.facing = dx > 0 ? 1 : -1;\n                        if (distToPlayerX > 300) {\n                            this.x += this.facing * this.speed * dt;\n                            this.animation = 'walk';\n                        } else {\n                            this.animation = 'idle';\n                            this.attackTimer -= dt;\n                            if (this.attackTimer <= 0) {\n                                projectiles.push(new Projectile(this.x, this.y-30, dx*2, dy*2, 'bolt', 'enemy'));\n                                this.attackTimer = 0.8;\n                            }\n                        }\n                    } else if (this.type === 'docock') {\n                        this.facing = dx > 0 ? 1 : -1;\n                        if (distToPlayerX > 100 || distToPlayerY > meleeRangeY) {\n                            this.x += this.facing * this.speed * dt;\n                            this.animation = 'walk';\n                        } else {\n                            this.animation = 'punch';\n                            this.attackTimer -= dt;\n                            if (this.attackTimer <= 0) {\n                                player.takeDamage(this.damage);\n                                this.attackTimer = 0.5;\n                            }\n                        }\n                    } else if (this.type === 'venom') {\n                        this.facing = dx > 0 ? 1 : -1;\n                        if (distToPlayerX > 80 || distToPlayerY > meleeRangeY) {\n                            this.x += this.facing * this.speed * 1.5 * dt;\n                            this.animation = 'walk';\n                        } else {\n                            this.animation = 'punch';\n                            this.attackTimer -= dt;\n                            if (this.attackTimer <= 0) {\n                                player.takeDamage(this.damage * 1.5);\n                                this.attackTimer = 1.0;\n                            }\n                        }\n                    } else if (this.type === 'sandman') {\n                        this.facing = dx > 0 ? 1 : -1;\n                        if (distToPlayerX > 150 || distToPlayerY > meleeRangeY) {\n                            this.x += this.facing * this.speed * 0.8 * dt;\n                            this.animation = 'walk';\n                        } else {\n                            this.animation = 'punch';\n                            this.attackTimer -= dt;\n                            if (this.attackTimer <= 0) {\n                                player.takeDamage(this.damage * 2);\n                                player.vx = this.facing * 800; // Major knockback\n                                this.attackTimer = 2.0;\n                            }\n                        }\n                    } else {\n                        // Regular enemies\n                        if (distToPlayerX > 60 || distToPlayerY > meleeRangeY) {\n                            this.facing = dx > 0 ? 1 : -1;\n                            this.x += this.facing * this.speed * dt;\n                            this.animation = 'walk';\n                        } else {\n                            this.animation = 'idle';\n                            this.attackTimer -= dt;\n                            if (this.attackTimer <= 0) {\n                                player.takeDamage(this.damage);\n                                this.attackTimer = this.attackCooldown;\n                            }\n                        }\n                    }\n                }\n\n                attack() {\n                    // This is now handled in update for better custom logic\n                }\n\n                takeDamage(damage) {\n                    this.health -= damage;\n                    playSound('enemy_hit_sfx', 0.3);\n                    \n                    floatingTexts.push({\n                        text: `-${damage}`, x: this.x, y: this.y - 60, lifetime: 1.0, age: 0, color: this.isBoss ? '#ff0000' : '#ffffff'\n                    });\n\n                    if (this.health <= 0) {\n                        this.defeat();\n                    }\n                }\n\n                defeat() {\n                    this.defeated = true;\n                    playSound('enemy_defeat_sfx', 0.4);\n                    \n                    const points = this.scoreValue * Math.max(1, combo);\n                    score += points;\n                    \n                    floatingTexts.push({\n                        text: this.isBoss ? \"BOSS DEFEATED!\" : `+${points}`, x: this.x, y: this.y - 80, lifetime: 2.0, age: 0, color: '#FFD700'\n                    });\n\n                    for (let i = 0; i < (this.isBoss ? 50 : 12); i++) {\n                        particles.push({\n                            type: 'defeat', x: this.x, y: this.y - 30,\n                            vx: (Math.random() - 0.5) * 300, vy: -Math.random() * 300,\n                            lifetime: 1.0, age: 0\n                        });\n                    }\n                    \n                    if (this.isBoss) {\n                         powerUps.push(new PowerUp(this.x, GROUND_Y - 200, 'health'));\n                         powerUps.push(new PowerUp(this.x, GROUND_Y - 200, 'score'));\n                    }\n                }\n\n                stun(duration) {\n                    if (!this.isBoss) this.stunTimer = duration;\n                }\n\n                knockback(force) {\n                    if (!this.isBoss) this.knockbackVx = force;\n                }\n\n                draw() {\n                    if (this.defeated && this.deathTimer > 0.5) return;\n                    const alpha = this.defeated ? 1 - (this.deathTimer / 0.5) : 1;\n                    ctx.globalAlpha = alpha;\n\n                    const drawColor = this.stunTimer > 0 ? '#888888' : this.color;\n                    ctx.save();\n                    ctx.scale(this.facing, 1);\n                    drawStickFigure(this.x * this.facing, this.y, drawColor, this.scale, this.animation, this.frame, this.stunTimer > 0, this.type);\n                    ctx.restore();\n\n                    // Boss Health Bar\n                    if (this.isBoss && !this.defeated) {\n                        const barWidth = 100;\n                        ctx.fillStyle = 'rgba(0,0,0,0.5)';\n                        ctx.fillRect(this.x - barWidth/2, this.y - 150, barWidth, 10);\n                        ctx.fillStyle = 'red';\n                        ctx.fillRect(this.x - barWidth/2, this.y - 150, barWidth * (this.health / this.maxHealth), 10);\n                    }\n\n                    ctx.globalAlpha = 1;\n                }\n            }\n\n            function getCurrentWaveData() {\n                const waveNum = currentWave + 1;\n                const isBossWave = waveNum % 5 === 0;\n                \n                if (isBossWave) {\n                    const bossPool = ['goblin', 'docock', 'electro', 'sandman', 'venom'];\n                    const bossType = bossPool[Math.floor((waveNum / 5 - 1) % bossPool.length)];\n                    return {\n                        enemyCount: 1,\n                        enemyType: bossType,\n                        isBoss: true\n                    };\n                }\n\n                // Normal waves\n                const enemyCount = 3 + Math.floor(currentWave * 1.5);\n                let enemyType = 'mixed';\n                if (currentWave === 0) enemyType = 'basic';\n                \n                return {\n                    enemyCount: enemyCount,\n                    enemyType: enemyType,\n                    isBoss: false\n                };\n            }\n\n            // Wave management\n            function startWave() {\n                enemiesSpawnedThisWave = 0;\n                waveRestTimer = 0;\n                const waveData = getCurrentWaveData();\n                const indicator = document.getElementById('wave-indicator');\n                if (waveData.isBoss) {\n                    indicator.textContent = `BOSS WAVE: ${waveData.enemyType.toUpperCase()}!`;\n                    indicator.style.color = '#ff4444';\n                } else {\n                    indicator.textContent = `Wave ${currentWave + 1}`;\n                    indicator.style.color = 'white';\n                }\n                updateUI();\n            }\n\n            function spawnEnemy() {\n                const wave = getCurrentWaveData();\n                if (enemiesSpawnedThisWave >= wave.enemyCount) return;\n\n                let type = wave.enemyType;\n                if (wave.enemyType === 'mixed') {\n                    type = Math.random() > 0.4 ? 'basic' : 'tough';\n                }\n\n                const side = Math.random() > 0.5 ? 'left' : 'right';\n                enemies.push(new Enemy(type, side));\n                enemiesSpawnedThisWave++;\n            }\n\n            // Game state management\n            function startGame() {\n                gameState = 'playing';\n                player = new Player();\n                enemies = [];\n                partners = [];\n                projectiles = [];\n                powerUps = [];\n                particles = [];\n                floatingTexts = [];\n                score = 0;\n                currentWave = 0;\n                combo = 0;\n                comboTimer = 0;\n                bestCombo = 0;\n                \n                document.getElementById('title-screen').classList.remove('active');\n                document.getElementById('victory-screen').classList.remove('active');\n                document.getElementById('game-over-screen').classList.remove('active');\n                \n                startWave();\n                playBackgroundMusic();\n            }\n\n            function victory() {\n                gameState = 'victory';\n                stopBackgroundMusic();\n                playSound('victory_music', 0.6);\n                \n                document.getElementById('final-score').textContent = `Score: ${score}`;\n                document.getElementById('waves-completed').textContent = `Waves: ${config.waves.length}/${config.waves.length}`;\n                document.getElementById('best-combo').textContent = `Best Combo: ${bestCombo}x`;\n                document.getElementById('victory-screen').classList.add('active');\n            }\n\n            function gameOver() {\n                gameState = 'gameover';\n                stopBackgroundMusic();\n                playSound('game_over_sfx', 0.6);\n                \n                document.getElementById('game-over-score').textContent = `Score: ${score}`;\n                document.getElementById('wave-reached').textContent = `Wave Reached: ${currentWave + 1}`;\n                document.getElementById('game-over-screen').classList.add('active');\n            }\n\n            // Input handling\n            const keys = {};\n            const touches = {};\n\n            if (!isTouchDevice) {\n                window.addEventListener('keydown', (e) => {\n                    keys[e.key.toLowerCase()] = true;\n                    \n                    if (gameState === 'title' && e.key === ' ') {\n                        startGame();\n                    }\n\n                    if (gameState === 'playing' && player) {\n                        if (e.key === 'w' || e.key === 'arrowup') player.jump();\n                        if (e.key === 's' || e.key === 'arrowdown') player.toggleSwing();\n                        if (e.key === ' ') player.punch();\n                        if (e.key === 'shift') player.kick();\n                        if (e.key === 'e') {\n                            if (player.morphedInto) {\n                                player.specialAbility();\n                            } else {\n                                player.webAttack();\n                            }\n                        }\n                        if (e.key === 'q') {\n                            if (partners.length > 0) {\n                                currentMorphIndex++;\n                                if (currentMorphIndex >= partners.length) {\n                                    currentMorphIndex = -1;\n                                    player.unmorph();\n                                } else {\n                                    player.morph(partners[currentMorphIndex].type);\n                                }\n                            }\n                        }\n                    }\n                });\n\n                window.addEventListener('keyup', (e) => {\n                    keys[e.key.toLowerCase()] = false;\n                });\n\n                window.addEventListener('blur', () => {\n                    Object.keys(keys).forEach(key => keys[key] = false);\n                });\n            }\n\n            // Touch controls\n            const movementState = { up: false, down: false, left: false, right: false };\n            \n            function handleTouchStart(e) {\n                e.preventDefault();\n                Array.from(e.changedTouches).forEach(touch => {\n                    const rect = canvas.getBoundingClientRect();\n                    const x = touch.clientX - rect.left;\n                    const y = touch.clientY - rect.top;\n\n                    touches[touch.identifier] = { x, y };\n                    \n                    // Check button presses\n                    checkButtonPress(x, y, true, touch.identifier);\n                });\n            }\n\n            function handleTouchMove(e) {\n                e.preventDefault();\n                Array.from(e.changedTouches).forEach(touch => {\n                    if (touches[touch.identifier]) {\n                        const rect = canvas.getBoundingClientRect();\n                        const x = touch.clientX - rect.left;\n                        const y = touch.clientY - rect.top;\n                        touches[touch.identifier].x = x;\n                        touches[touch.identifier].y = y;\n                    }\n                });\n            }\n\n            function handleTouchEnd(e) {\n                e.preventDefault();\n                Array.from(e.changedTouches).forEach(touch => {\n                    const rect = canvas.getBoundingClientRect();\n                    const x = touch.clientX - rect.left;\n                    const y = touch.clientY - rect.top;\n                    \n                    checkButtonPress(x, y, false, touch.identifier);\n                    delete touches[touch.identifier];\n                });\n            }\n\n            function checkButtonPress(x, y, isDown, touchId) {\n                if (isDown && gameState === 'title') {\n                    startGame();\n                    return true;\n                }\n\n                if (gameState !== 'playing' || !player) return false;\n\n                // If in the sky, set swing anchor (only for taps/clicks that reach the canvas)\n                if (isDown && y < GROUND_Y - 100) {\n                    const flyers = ['ironman', 'surfer', 'thor', 'strange'];\n                    if (flyers.includes(player.morphedInto)) {\n                        player.isFlying = true;\n                        player.flightTarget = { x, y };\n                        player.isGrounded = false;\n                        player.isSwinging = false;\n                    } else {\n                        if (player.isSwinging) {\n                            player.isSwinging = false;\n                        } else {\n                            player.toggleSwing(x, y);\n                        }\n                    }\n                    return true;\n                }\n\n                return false;\n            }\n\n            if (isTouchDevice) {\n                canvas.addEventListener('touchstart', handleTouchStart, { passive: false });\n                canvas.addEventListener('touchmove', handleTouchMove, { passive: false });\n                canvas.addEventListener('touchend', handleTouchEnd, { passive: false });\n                canvas.addEventListener('touchcancel', handleTouchEnd, { passive: false });\n                \n                // Title screen tap\n                document.getElementById('title-screen').addEventListener('touchstart', (e) => {\n                    if (gameState === 'title') {\n                        e.preventDefault();\n                        startGame();\n                    }\n                });\n            } else {\n                // Mouse click for title screen\n                document.getElementById('title-screen').addEventListener('click', () => {\n                    if (gameState === 'title') {\n                        startGame();\n                    }\n                });\n            }\n\n            // Helper to add touch/mouse listeners to buttons\n            function setupButton(id, actionDown, actionUp) {\n                const btn = document.getElementById(id);\n                if (!btn) return;\n\n                const onDown = (e) => {\n                    e.preventDefault();\n                    if (actionDown) actionDown();\n                };\n                const onUp = (e) => {\n                    e.preventDefault();\n                    if (actionUp) actionUp();\n                };\n\n                btn.addEventListener('touchstart', onDown);\n                btn.addEventListener('touchend', onUp);\n                btn.addEventListener('touchcancel', onUp);\n                \n                // Desktop support\n                btn.addEventListener('mousedown', onDown);\n                btn.addEventListener('mouseup', onUp);\n                btn.addEventListener('mouseleave', onUp);\n            }\n\n            // Movement buttons (hold to move)\n            setupButton('up-button', () => movementState.up = true, () => movementState.up = false);\n            setupButton('down-button', () => movementState.down = true, () => movementState.down = false);\n            setupButton('left-button', () => movementState.left = true, () => movementState.left = false);\n            setupButton('right-button', () => movementState.right = true, () => movementState.right = false);\n\n            // Action buttons (one-shot)\n            setupButton('punch-button', () => player?.punch());\n            setupButton('kick-button', () => player?.kick());\n            setupButton('jump-button', () => player?.jump());\n            setupButton('swing-button', () => player?.toggleSwing());\n            setupButton('web-button', () => {\n                if (!player) return;\n                if (player.morphedInto) {\n                    player.specialAbility();\n                } else {\n                    player.webAttack();\n                }\n            });\n\n            setupButton('morph-btn', () => {\n                if (!player || partners.length === 0) return;\n                currentMorphIndex++;\n                if (currentMorphIndex >= partners.length) {\n                    currentMorphIndex = -1;\n                    player.unmorph();\n                } else {\n                    player.morph(partners[currentMorphIndex].type);\n                }\n            });\n\n            document.getElementById('play-again-button').addEventListener('click', startGame);\n            document.getElementById('try-again-button').addEventListener('click', startGame);\n\n            // Shop logic\n            const shopScreen = document.getElementById('shop-screen');\n            const shopGrid = document.getElementById('shop-grid');\n            \n            function openShop() {\n                if (gameState !== 'playing') return;\n                gameState = 'shop';\n                renderShop();\n                shopScreen.classList.add('active');\n            }\n\n            function closeShop() {\n                gameState = 'playing';\n                shopScreen.classList.remove('active');\n            }\n\n            function renderShop() {\n                shopGrid.innerHTML = '';\n                shopItems.forEach(item => {\n                    const isOwned = partners.some(p => p.type === item.id);\n                    const canAfford = score >= item.cost;\n                    \n                    const div = document.createElement('div');\n                    div.className = 'shop-item';\n                    div.innerHTML = `\n                        <div class=\"shop-item-name\">${item.name}</div>\n                        <div class=\"shop-item-cost\">${item.cost} Points</div>\n                        <button class=\"shop-buy-btn\" ${isOwned || !canAfford ? 'disabled' : ''}>\n                            ${isOwned ? 'OWNED' : (canAfford ? 'BUY' : 'NEED MORE POINTS')}\n                        </button>\n                    `;\n                    \n                    const btn = div.querySelector('.shop-buy-btn');\n                    btn.addEventListener('click', () => buyPartner(item));\n                    \n                    shopGrid.appendChild(div);\n                });\n            }\n\n            function buyPartner(item) {\n                if (score >= item.cost) {\n                    score -= item.cost;\n                    partners.push(new Partner(item.id));\n                    renderShop();\n                    updateUI();\n                    playSound('combo_sfx', 0.8);\n                }\n            }\n\n            document.getElementById('shop-open-btn').addEventListener('click', openShop);\n            document.getElementById('close-shop-btn').addEventListener('click', closeShop);\n\n            // Update UI\n            function updateUI() {\n                document.getElementById('score-display').textContent = score;\n                \n                if (player) {\n                    const healthPercent = (player.health / player.maxHealth) * 100;\n                    document.getElementById('health-bar').style.width = `${healthPercent}%`;\n\n                    const webBtn = document.getElementById('web-button');\n                    const morphBtn = document.getElementById('morph-btn');\n\n                    if (partners.length > 0) {\n                        morphBtn.style.display = 'flex';\n                        morphBtn.textContent = player.morphedInto ? player.morphedInto.toUpperCase() : 'MORPH';\n                        if (player.morphCooldown <= 0) {\n                            morphBtn.classList.add('ready');\n                        } else {\n                            morphBtn.classList.remove('ready');\n                        }\n                    }\n\n                    if (player.morphedInto) {\n                        const pData = shopItems.find(i => i.id === player.morphedInto);\n                        webBtn.style.background = pData.color;\n                        if (assets.special_power_icon) {\n                            document.getElementById('web-icon').src = assets.special_power_icon.img.src;\n                        }\n                    } else {\n                        webBtn.style.background = 'linear-gradient(135deg, #ffffff, #cccccc)';\n                        if (assets.web_icon) {\n                            document.getElementById('web-icon').src = assets.web_icon.img.src;\n                        }\n                    }\n                }\n\n                const comboDisplay = document.getElementById('combo-display');\n                if (combo > 1) {\n                    comboDisplay.textContent = `${combo}x COMBO!`;\n                    comboDisplay.classList.add('active');\n                } else {\n                    comboDisplay.classList.remove('active');\n                }\n            }\n\n            // Show game parameters\n            lib.showGameParameters({\n                name: 'Game Settings',\n                params: {\n                    'Player Health': {\n                        key: 'playerHealth',\n                        type: 'slider',\n                        min: 50,\n                        max: 200,\n                        step: 10\n                    },\n                    'Enemy Spawn Rate': {\n                        key: 'enemySpawnRate',\n                        type: 'slider',\n                        min: 0.5,\n                        max: 3.0,\n                        step: 0.1\n                    },\n                    'Enemy Speed': {\n                        key: 'enemySpeed',\n                        type: 'slider',\n                        min: 50,\n                        max: 200,\n                        step: 10\n                    },\n                    'PowerUp Spawn Rate': {\n                        key: 'powerUpSpawnRate',\n                        type: 'slider',\n                        min: 2,\n                        max: 20,\n                        step: 1\n                    },\n                    'Wave Count': {\n                        key: 'waveCount',\n                        type: 'slider',\n                        min: 3,\n                        max: 10,\n                        step: 1\n                    },\n                    'Combo Time Window': {\n                        key: 'comboTimeWindow',\n                        type: 'slider',\n                        min: 1,\n                        max: 5,\n                        step: 0.5\n                    },\n                    'Player Color': {\n                        key: 'playerColor',\n                        type: 'color'\n                    },\n                    'Enemy Color': {\n                        key: 'enemyColor',\n                        type: 'color'\n                    },\n                    'Sky Gradient Top': {\n                        key: 'skyGradientTop',\n                        type: 'color'\n                    },\n                    'Sky Gradient Bottom': {\n                        key: 'skyGradientBottom',\n                        type: 'color'\n                    },\n                    'Building Color': {\n                        key: 'buildingColor',\n                        type: 'color'\n                    }\n                }\n            });\n\n            // Game loop variables\n            let enemySpawnTimer = 0;\n            let powerUpSpawnTimer = 0;\n\n            // Game loop\n            function gameLoop(timestamp) {\n                const dt = Math.min((timestamp - lastFrameTime) / 1000, 0.11);\n                lastFrameTime = timestamp;\n\n                // Clear canvas\n                ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n                // Draw background\n                const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);\n                gradient.addColorStop(0, config.skyGradientTop);\n                gradient.addColorStop(0.5, config.skyGradientBottom);\n                ctx.fillStyle = gradient;\n                ctx.fillRect(0, 0, canvas.width, canvas.height);\n\n                // Draw city skyline\n                if (assets.city_skyline_bg) {\n                    const img = assets.city_skyline_bg.img;\n                    const aspect = assets.city_skyline_bg.aspect_ratio;\n                    const drawWidth = canvas.width;\n                    const drawHeight = drawWidth / (aspect[0] / aspect[1]);\n                    ctx.drawImage(img, 0, 400, drawWidth, drawHeight);\n                }\n\n                // Draw ground\n                ctx.fillStyle = '#654321';\n                ctx.fillRect(0, GROUND_Y, canvas.width, canvas.height - GROUND_Y);\n\n                if (mode === 'edit') {\n                    // Edit mode - show setup\n                    ctx.fillStyle = 'white';\n                    ctx.font = '32px Roboto';\n                    ctx.textAlign = 'center';\n                    ctx.fillText('Edit Mode', canvas.width / 2, canvas.height / 2);\n                    ctx.font = '24px Roboto';\n                    ctx.fillText('Use the settings panel to customize the game', canvas.width / 2, canvas.height / 2 + 50);\n                } else {\n                    // Play mode - full game logic\n                    if (gameState === 'playing') {\n                        // Update combo timer\n                        if (combo > 0) {\n                            comboTimer -= dt;\n                            if (comboTimer <= 0) {\n                                combo = 0;\n                            }\n                        }\n\n                        // Handle input\n                        if (player) {\n                            let inputX = 0;\n                            let inputY = 0;\n\n                        // Combine both touch/UI and keyboard input\n                            if (movementState.left || keys['a'] || keys['arrowleft']) inputX = -1;\n                            if (movementState.right || keys['d'] || keys['arrowright']) inputX = 1;\n                            if (movementState.up || keys['w'] || keys['arrowup']) inputY = -1;\n                            if (movementState.down || keys['s'] || keys['arrowdown']) inputY = 1;\n\n                            if (player.isSwinging) {\n                                // Add force to swing\n                                player.swingAngularVelocity += inputX * dt * 2.0;\n                            } else {\n                                // Normal ground/air movement\n                                if (player.isGrounded) {\n                                    player.vx = inputX * 300;\n                                } else {\n                                    player.vx += inputX * dt * 500;\n                                    // Cap air speed\n                                    player.vx = Math.max(-400, Math.min(400, player.vx));\n                                }\n                            }\n\n                            player.update(dt, inputY);\n                            player.updateCooldowns(dt);\n                        }\n\n                        // Spawn enemies\n                        enemySpawnTimer += dt;\n                        if (enemySpawnTimer >= config.enemySpawnRate) {\n                            enemySpawnTimer = 0;\n                            spawnEnemy();\n                        }\n\n                        // Update enemies\n                        enemies = enemies.filter(enemy => {\n                            enemy.update(dt);\n                            return !enemy.defeated || enemy.deathTimer < 1.0;\n                        });\n\n                        // Update partners\n                        partners.forEach(partner => partner.update(dt));\n\n                        // Update projectiles\n                        projectiles = projectiles.filter(p => !p.update(dt));\n\n                        // Update power-ups\n                        powerUps = powerUps.filter(p => {\n                            p.update(dt);\n                            return !p.collected && p.age < 15.0; // Despawn after 15s\n                        });\n\n                        // Spawn power-ups\n                        powerUpSpawnTimer += dt;\n                        if (powerUpSpawnTimer >= config.powerUpSpawnRate) {\n                            powerUpSpawnTimer = 0;\n                            const type = Math.random() > 0.7 ? 'health' : 'score';\n                            powerUps.push(new PowerUp(\n                                Math.random() * (MAX_X - MIN_X) + MIN_X,\n                                Math.random() * 400 + 200, // Spawn in air\n                                type\n                            ));\n                        }\n\n                        // Check wave completion\n                        const activeEnemies = enemies.filter(e => !e.defeated).length;\n                        const currentWaveData = getCurrentWaveData();\n                        if (enemiesSpawnedThisWave >= currentWaveData.enemyCount && activeEnemies === 0) {\n                            waveRestTimer += dt;\n                            if (waveRestTimer >= 3.0) {\n                                currentWave++;\n                                playSound('wave_complete_sfx', 0.5);\n                                startWave();\n                            }\n                        }\n\n                        // Update particles\n                        particles = particles.filter(p => {\n                            p.age += dt;\n                            if (p.type === 'hit' || p.type === 'defeat') {\n                                p.x += p.vx * dt;\n                                p.y += p.vy * dt;\n                            }\n                            return p.age < p.lifetime;\n                        });\n\n                        // Update floating texts\n                        floatingTexts = floatingTexts.filter(t => {\n                            t.age += dt;\n                            t.y -= dt * 30;\n                            return t.age < t.lifetime;\n                        });\n\n                        updateUI();\n                    }\n\n                    // Draw game objects\n                    if (player) {\n                        // Draw swing line\n                        if (player.isSwinging) {\n                            ctx.strokeStyle = 'white';\n                            ctx.lineWidth = 3;\n                            ctx.beginPath();\n                            ctx.moveTo(player.swingAnchor.x, player.swingAnchor.y);\n                            ctx.lineTo(player.x, player.y - 40); // To player shoulder/chest\n                            ctx.stroke();\n                            \n                            // Web impact point\n                            ctx.fillStyle = 'white';\n                            ctx.beginPath();\n                            ctx.arc(player.swingAnchor.x, player.swingAnchor.y, 5, 0, Math.PI * 2);\n                            ctx.fill();\n                        }\n                        player.draw();\n                    }\n\n                    enemies.forEach(enemy => enemy.draw());\n                    partners.forEach(partner => partner.draw());\n                    projectiles.forEach(p => p.draw());\n                    powerUps.forEach(p => p.draw());\n\n                    // Draw particles\n                    particles.forEach(p => {\n                        const alpha = 1 - (p.age / p.lifetime);\n                        ctx.globalAlpha = alpha;\n\n                        if (p.type === 'web') {\n                            ctx.strokeStyle = 'white';\n                            ctx.lineWidth = 3;\n                            ctx.beginPath();\n                            ctx.moveTo(p.x1, p.y1);\n                            ctx.lineTo(p.x2, p.y2);\n                            ctx.stroke();\n                        } else if (p.type === 'hit') {\n                            ctx.fillStyle = 'white';\n                            ctx.beginPath();\n                            ctx.arc(p.x, p.y, 4, 0, Math.PI * 2);\n                            ctx.fill();\n                        } else if (p.type === 'defeat') {\n                            ctx.fillStyle = '#888888';\n                            ctx.beginPath();\n                            ctx.arc(p.x, p.y, 3, 0, Math.PI * 2);\n                            ctx.fill();\n                        }\n\n                        ctx.globalAlpha = 1;\n                    });\n\n                    // Draw floating texts\n                    floatingTexts.forEach(t => {\n                        const alpha = 1 - (t.age / t.lifetime);\n                        ctx.globalAlpha = alpha;\n                        ctx.fillStyle = t.color;\n                        ctx.font = 'bold 28px Roboto';\n                        ctx.textAlign = 'center';\n                        ctx.strokeStyle = 'black';\n                        ctx.lineWidth = 3;\n                        ctx.strokeText(t.text, t.x, t.y);\n                        ctx.fillText(t.text, t.x, t.y);\n                        ctx.globalAlpha = 1;\n                    });\n                }\n\n                requestAnimationFrame(gameLoop);\n            }\n\n            lastFrameTime = performance.now();\n            requestAnimationFrame(gameLoop);\n        }\n    </script>\n</body>\n</html>", "game_config": "{\n  \"playerHealth\": 200,\n  \"enemySpawnRate\": 1.5,\n  \"enemySpeed\": 80,\n  \"waveCount\": 5,\n  \"comboTimeWindow\": 5,\n  \"playerColor\": \"#FF0000\",\n  \"enemyColor\": \"#000000\",\n  \"skyGradientTop\": \"#87CEEB\",\n  \"skyGradientBottom\": \"#4A90E2\",\n  \"buildingColor\": \"#2C2C2C\",\n  \"waves\": [\n    {\n      \"enemyCount\": 3,\n      \"enemyType\": \"basic\"\n    },\n    {\n      \"enemyCount\": 5,\n      \"enemyType\": \"basic\"\n    },\n    {\n      \"enemyCount\": 4,\n      \"enemyType\": \"mixed\"\n    },\n    {\n      \"enemyCount\": 6,\n      \"enemyType\": \"mixed\"\n    },\n    {\n      \"enemyCount\": 8,\n      \"enemyType\": \"tough\"\n    }\n  ],\n  \"powerUpSpawnRate\": 10\n}", "game_design_document": "## Core Game Loop and Mechanics\n\n**Stick-Man Spider-Sling** is a side-scrolling action game where players control a stick-figure Spider-Man character fighting through waves of stick-figure villains in a minimalist urban environment.\n\n### Moment-to-Moment Gameplay\n- Player moves left/right across a fixed-screen arena\n- Enemies spawn from both sides and approach the player\n- Player can punch, kick, and perform a special web-attack to defeat enemies\n- Defeated enemies drop score points\n- Game gets progressively harder with more enemies and faster spawn rates\n\n### Progression and Engagement\n- **Score System**: Points awarded for each defeated enemy, with combo multipliers for consecutive hits\n- **Wave System**: Enemies come in waves with brief rest periods between waves\n- **Difficulty Scaling**: Each wave introduces more enemies or tougher enemy types\n- **Visual Feedback**: Hit effects, score popups, and satisfying stick-figure ragdoll physics when enemies are defeated\n\n### Win/Lose Conditions\n- **Win**: Survive all waves (start with 5 waves for first iteration)\n- **Lose**: Player health reaches zero\n- **Replay Value**: Try to beat high score, achieve perfect runs without taking damage\n\n### What Makes It Compelling\n- Satisfying stick-figure combat with exaggerated physics\n- Simple but skill-based timing mechanics\n- Quick sessions (2-3 minutes per playthrough)\n- Clear visual feedback and progression\n\n## Starting Scope\n\nFor the first iteration, we'll implement a **minimal but polished prototype**:\n\n**Include in First Version:**\n- Single fixed-screen arena with simple city skyline background\n- Player stick-figure character with 3 basic moves: punch, kick, web-attack\n- 2 enemy types: basic melee enemy and slightly tougher variant\n- 5 waves of increasing difficulty\n- Health bar for player\n- Score counter with combo system\n- Simple particle effects for hits\n- Touch controls (mobile) and keyboard controls (desktop)\n- Victory and game over screens with restart option\n\n**Save for Later Iterations:**\n- Web-slinging movement mechanics (too complex for first version)\n- Multiple levels/arenas\n- Boss fights\n- Power-ups and collectibles\n- Additional enemy types\n- Special moves beyond the basic three\n- Leaderboards or achievements\n\n## Game Perspective\n\n**Side-scrolling fixed-screen** - The game uses a classic 2D side view with the camera fixed on a single arena. The player moves left and right across the screen while enemies approach from both sides. This perspective is ideal for:\n- Clear visibility of all threats\n- Simple but engaging combat\n- Easy-to-understand spatial relationships\n- Mobile-friendly gameplay\n\n## Game World and Level Design\n\n### Arena Layout\nThe game takes place in a single fixed arena representing a city rooftop:\n\n**Dimensions**: 720px wide \u00d7 1280px tall (matching iframe)\n\n**Visual Layers** (back to front):\n1. **Sky Background** (top 400px): Simple gradient from light blue to darker blue\n2. **City Skyline** (400-600px): Minimalist stick-figure style buildings in silhouette\n3. **Rooftop Platform** (600-800px): The main play area - a flat surface where combat occurs\n4. **UI Layer** (overlay): Health bar (top-left), Score (top-right), Wave indicator (top-center)\n\n**Play Area**:\n- Ground level at y=800px\n- Player and enemies move along this horizontal line\n- Movement bounds: x=100px to x=620px (leaving margins for UI and visual breathing room)\n\n**Combat Zone**:\n- Enemies spawn off-screen (x=-50px or x=770px) and walk toward center\n- Player starts at center (x=360px)\n- Combat occurs when player and enemies overlap (within ~80px range)\n\n### Visual Style\n- **Stick Figures**: All characters are simple stick figures with circular heads and line limbs\n- **Minimalist City**: Buildings are simple rectangular shapes in silhouette\n- **Color Palette**: \n  - Sky: Light blue gradient\n  - Buildings: Dark gray/black silhouettes\n  - Player: Red stick figure (Spider-Man colors)\n  - Enemies: Black stick figures\n  - Effects: White/yellow for hits, red for damage\n- **Animation**: Simple but smooth - stick limbs rotate and move, ragdoll physics on defeat\n\n## Edit Mode Design\n\nEdit mode allows players to customize the game difficulty and visual style without breaking core mechanics.\n\n### Editable Properties\n\n**Gameplay Settings:**\n- `playerHealth` (50-200, default: 100) - Starting health\n- `enemySpawnRate` (0.5-3.0 seconds, default: 1.5) - Time between enemy spawns\n- `enemySpeed` (50-200, default: 100) - How fast enemies move toward player\n- `waveCount` (3-10, default: 5) - Number of waves to complete\n- `comboTimeWindow` (1-5 seconds, default: 2.5) - Time to maintain combo\n\n**Visual Customization:**\n- `playerColor` (color picker, default: \"#FF0000\") - Player stick figure color\n- `enemyColor` (color picker, default: \"#000000\") - Enemy stick figure color\n- `skyGradientTop` (color picker, default: \"#87CEEB\") - Top sky color\n- `skyGradientBottom` (color picker, default: \"#4A90E2\") - Bottom sky color\n- `buildingColor` (color picker, default: \"#2C2C2C\") - City silhouette color\n\n**Wave Configuration (Array):**\n```javascript\nwaves: [\n  { enemyCount: 3, enemyType: \"basic\" },\n  { enemyCount: 5, enemyType: \"basic\" },\n  { enemyCount: 4, enemyType: \"mixed\" },\n  { enemyCount: 6, enemyType: \"mixed\" },\n  { enemyCount: 8, enemyType: \"tough\" }\n]\n```\n\n### Constraints\n- Health values capped to prevent instant-death or invincibility\n- Spawn rates limited to maintain playability\n- Wave count limited to prevent excessively long games\n- Colors must have sufficient contrast for visibility\n\n### Edit Mode UI\n- Collapsible panels for each category (Gameplay, Visual, Waves)\n- Real-time preview of color changes\n- Sliders for numeric values with visible current value\n- \"Reset to Default\" button for each section\n- \"Test Changes\" button to immediately try modifications\n\n## Control Design\n\n### Mobile Controls (Touch)\n\n**Movement:**\n- Left side of screen (x < 360px): Virtual joystick for left/right movement\n  - Appears where finger touches\n  - Horizontal-only movement (vertical input ignored)\n  - Dead zone: 10px\n  - Max radius: 60px\n  - Base repositions if finger moves beyond max radius\n\n**Action Buttons (Right side, bottom area):**\n- Three circular buttons positioned in bottom-right (y > 900px):\n  - **Punch Button** (x: 520-600px, y: 1000-1080px): Red circle, \"P\" label\n  - **Kick Button** (x: 620-700px, y: 1000-1080px): Blue circle, \"K\" label\n  - **Web Button** (x: 570-650px, y: 1100-1180px): White circle, web icon\n- Buttons are 80px diameter (exceeds 96px minimum for accessibility)\n- Visual feedback: buttons scale down slightly when pressed\n\n**Touch Handling:**\n- Track touch IDs to prevent interference\n- Joystick area and button area don't overlap\n- Multiple simultaneous touches supported (move + attack)\n\n### Desktop Controls (Keyboard + Mouse)\n\n**Movement:**\n- `A` or `Left Arrow`: Move left\n- `D` or `Right Arrow`: Move right\n\n**Actions:**\n- `Space` or `Left Mouse Click`: Punch\n- `Shift` or `Right Mouse Click`: Kick\n- `E` or `Middle Mouse Click`: Web attack\n\n**Input Handling:**\n- Prevent default browser behavior for used keys\n- Convert keycodes to lowercase before storing state\n- Clear all input states on window blur\n- No pointer lock needed (not a 3D game)\n\n### Control Feedback\n- Player stick figure animates differently for each action\n- Button presses show visual feedback (scale, color change)\n- Haptic feedback on mobile (if supported) for hits\n- Audio cues for each action type\n\n## Win/Lose Conditions\n\n### Victory Condition\nPlayer wins by defeating all enemies in all waves:\n- Complete all configured waves (default: 5 waves)\n- Each wave must have all enemies defeated before next wave begins\n- 3-second rest period between waves\n- Victory screen shows: Final score, Accuracy %, Combo record, \"Play Again\" button\n\n### Lose Condition\nPlayer loses when health reaches zero:\n- Health depletes when hit by enemies\n- Each enemy hit deals damage (basic: 10, tough: 15)\n- Game over screen shows: Final score, Wave reached, \"Try Again\" button\n\n### Scoring System\n- **Base Points**: 10 points per basic enemy, 20 per tough enemy\n- **Combo Multiplier**: Consecutive hits within 2.5 seconds multiply score (2x, 3x, 4x, max 5x)\n- **Wave Bonus**: 50 points \u00d7 wave number for completing each wave\n- **Perfect Wave**: +100 bonus if no damage taken during wave\n\n## Combat Mechanics\n\n### Player Attacks\n\n**Punch (Quick Attack):**\n- Cooldown: 0.3 seconds\n- Range: 80px\n- Damage: 30\n- Animation: Quick forward jab (0.2s animation)\n- Best for: Fast, reliable damage\n\n**Kick (Strong Attack):**\n- Cooldown: 0.6 seconds\n- Range: 90px\n- Damage: 50\n- Animation: Leg extends forward (0.3s animation)\n- Knockback: Pushes enemy back 40px\n- Best for: Crowd control, creating space\n\n**Web Attack (Special):**\n- Cooldown: 2.0 seconds\n- Range: 150px\n- Damage: 40\n- Animation: Web shoots forward (0.4s animation)\n- Effect: Stuns enemy for 1 second\n- Best for: Stopping charging enemies, setting up combos\n\n### Enemy Behavior\n\n**Basic Enemy:**\n- Health: 60\n- Speed: 100px/s\n- Damage: 10\n- Behavior: Walks toward player, attacks when in range (60px)\n- Attack cooldown: 1.5 seconds\n\n**Tough Enemy:**\n- Health: 100\n- Speed: 80px/s\n- Damage: 15\n- Behavior: Same as basic but tankier and hits harder\n- Attack cooldown: 1.2 seconds\n- Visual: Slightly larger stick figure\n\n### Combat Flow\n1. Enemies spawn from left/right edges\n2. They walk toward player's position\n3. When in attack range, they stop and attack\n4. Player must time attacks to hit enemies before being hit\n5. Defeated enemies play ragdoll animation and fade out\n6. Score popup appears above defeated enemy\n\n## Physics and Animation\n\n### Stick Figure Animation\n- **Idle**: Slight bob up/down (0.5s cycle)\n- **Walk**: Legs alternate forward/back, arms swing opposite\n- **Punch**: Right arm extends forward, body leans slightly\n- **Kick**: Right leg extends, body leans back for balance\n- **Web**: Both arms extend forward, web line shoots out\n- **Hit Reaction**: Body recoils backward, brief red flash\n- **Death/Defeat**: Ragdoll physics - limbs go limp, body falls and rotates\n\n### Particle Effects\n- **Hit Effect**: White burst particles (5-8 particles) radiate from impact point\n- **Combo Effect**: Yellow star particles around player when combo increases\n- **Defeat Effect**: Small gray puff when enemy is defeated\n- **Web Effect**: White line from player to target, fades quickly\n\n### Visual Feedback\n- **Damage Numbers**: Float upward from hit location, fade out\n- **Score Popups**: \"+10\", \"+20\" etc. float up from defeated enemies\n- **Combo Display**: \"2x COMBO!\" appears near top-center when combo increases\n- **Screen Shake**: Subtle shake on heavy hits (kick, web attack)\n\n## Audio Design\n\n### Sound Effects Needed\n- **Punch SFX**: Quick whoosh sound\n- **Kick SFX**: Heavier impact sound\n- **Web SFX**: Thwip/shooting sound\n- **Enemy Hit**: Thud/impact\n- **Enemy Defeat**: Cartoon defeat sound\n- **Player Hit**: Oof/grunt\n- **Combo**: Ascending chime\n- **Wave Complete**: Victory jingle\n- **Game Over**: Descending tone\n\n### Music\n- **Background Music**: Upbeat, action-oriented loop with retro/arcade feel\n- **Victory Music**: Short triumphant fanfare\n- Volume controls in settings (music and SFX separate)\n\n## UI Design\n\n### HUD (Always Visible)\n**Top Bar (y: 0-80px):**\n- **Health Bar** (top-left, x: 20-200px, y: 20px):\n  - Red bar showing current/max health\n  - White border, fills from left to right\n  - Height: 30px\n  \n- **Score** (top-right, x: 520-700px, y: 20px):\n  - Large white numbers\n  - Font size: 32px\n  - Format: \"Score: 1234\"\n\n- **Wave Indicator** (top-center, x: 260-460px, y: 20px):\n  - \"Wave 3/5\"\n  - Font size: 24px\n  - White text with dark outline\n\n**Combo Display** (center-top, y: 100px):\n- Only visible when combo active\n- \"3x COMBO!\" in large yellow text\n- Pulses/scales slightly\n- Fades when combo breaks\n\n### Menu Screens\n\n**Title Screen:**\n- Game title at top (y: 200px)\n- \"Tap to Start\" / \"Press Space to Start\" (y: 640px, center)\n- Animated stick figure doing idle animation\n- Simple instructions: \"Defeat all enemies!\"\n\n**Victory Screen:**\n- \"VICTORY!\" text (y: 300px)\n- Final score (y: 500px)\n- Stats: Waves completed, Accuracy, Best combo (y: 600-700px)\n- \"Play Again\" button (y: 900px)\n\n**Game Over Screen:**\n- \"GAME OVER\" text (y: 300px)\n- Final score (y: 500px)\n- \"Wave Reached: X/5\" (y: 600px)\n- \"Try Again\" button (y: 900px)\n\n### Button Styling\n- Rounded rectangles with gradient\n- Hover/press states (scale down slightly)\n- Clear labels in readable font\n- Minimum 96px height for touch targets\n\n## Art Style and Visual Theme\n\n### Overall Aesthetic\n**Minimalist Stick-Figure Action** - The game embraces a clean, simple art style that focuses on readability and smooth animation over detailed graphics. Think classic stick-figure animations meets modern game polish.\n\n### Visual Style Guidelines\n\n**Characters:**\n- Pure stick figures: circular heads (20px diameter), line limbs (4px thick)\n- Simple but expressive through animation\n- Clear silhouettes for instant recognition\n- Smooth, fluid movement with slight cartoon exaggeration\n\n**Environment:**\n- Flat color backgrounds with simple gradients\n- Geometric building shapes (rectangles, triangles for roofs)\n- Minimal detail - focus on silhouettes\n- High contrast between foreground and background\n\n**Effects:**\n- Simple particle systems (circles, lines)\n- Bold, readable impact effects\n- Smooth fade-ins/fade-outs\n- No complex shaders or filters\n\n**Color Palette:**\n- **Primary**: Red (#FF0000) for player\n- **Secondary**: Black (#000000) for enemies\n- **Accents**: White (#FFFFFF) for effects, Yellow (#FFD700) for combos\n- **Background**: Blue gradient (#87CEEB to #4A90E2)\n- **UI**: White text with dark outlines for readability\n\n### Asset vs. Code Rendering\n\n**Render with Code (CSS/Canvas):**\n- Stick figure characters (simple lines and circles)\n- UI elements (health bars, buttons, text)\n- Particle effects (simple circles and lines)\n- Background gradients\n- Building silhouettes (simple rectangles)\n\n**Generate as Assets:**\n- Background city skyline (for visual interest and consistency)\n- UI button icons (web icon for special attack)\n- Title screen logo/text\n- Sound effects and music\n\nThis approach keeps the game lightweight and ensures consistent stick-figure style through code rendering, while using generated assets only where they add significant value.", "persistent_storage_config": null, "game_metadata": {"game_id": "01KTGY31Q8D7Y7SMNP2KCVP43V", "title": "Stick-Man Spider-Sling", "description": "Swing through a minimalist city as a stick-man Spider-Man, using web-slinging mechanics to dodge obstacles and fight cartoon villains!", "thumbnail_url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/thumbnail_6b217c13-336f-4988-a92f-21809db7e73c.webp", "created_at": 1780832417.0, "updated_at": 1781418239321.083, "art_style": null}, "task_plan": null}