{"asset_map": {"player_character_idle": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/player_character_idle_1deb8f59-20c7-497d-9acb-6009e6f3b9ee.webp", "type": "image", "prompt": "Generate a full-body, top-down view of a cute stylized 2D cartoon chef character. The character is chibi-like, wearing a white chef's hat and a clean apron. Front-facing but slightly angled for a top-down perspective, idle standing pose. Bright, warm colors, clean thick outlines, professional mobile game asset, transparent background.", "description": "The main chef character in an idle pose, seen from a top-down perspective.", "subtype": "character", "aspect_ratio": [799, 1439], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "player_character_walk_1": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/player_character_walk_1_927b57ec-3359-4f73-aa2b-0600236614e9.webp", "type": "image", "prompt": "Generate the same cute chibi chef character from 'player_character_idle' in a walking animation frame. Top-down view, one leg forward, arms swinging slightly. Maintain the same white chef's hat and apron. Bright cartoon style, clean lines, transparent background.", "description": "The chef character in the first frame of a walking animation.", "subtype": "character", "aspect_ratio": [694, 1248], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "player_character_walk_2": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/player_character_walk_2_49142094-6e56-44cf-a901-5db6620e817c.webp", "type": "image", "prompt": "Generate the same cute chibi chef character from 'player_character_idle' in the second frame of a walking animation. Top-down view, the opposite leg forward from image 'player_character_walk_1'. Maintain consistent design, colors, and chef outfit. Bright cartoon style, transparent background.", "description": "The chef character in the second frame of a walking animation.", "subtype": "character", "aspect_ratio": [691, 1245], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "burger_machine": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/burger_machine_5cdafa6e-fab4-4b23-b0c0-997e43af4f4b.webp", "type": "image", "prompt": "Generate a stylized 2D cartoon burger grill machine. Top-down perspective showing a flat grill surface with sizzling patties and a small preparation counter. Compact street-food design, vibrant reds and metallic grays, polished game-ready asset, transparent background.", "description": "A compact burger grill station used to produce food items.", "subtype": "object", "aspect_ratio": [827, 920], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "customer_counter": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/customer_counter_519efa64-8233-4f2c-aa41-5e0076bafbbd.webp", "type": "image", "prompt": "Generate a simple, clean 2D cartoon customer counter for a food stall. Rectangular wooden or metal counter with a clear service surface. Top-down perspective, consistent with the burger machine style. Bright cartoon visuals, transparent background.", "description": "The serving counter where customers wait for their orders.", "subtype": "object", "aspect_ratio": [1106, 810], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "burger_item": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/burger_item_f2046da8-e429-4361-80c3-297058932a2d.webp", "type": "image", "prompt": "Generate an appetizing, stylized 2D cartoon burger. Top-down view showing the bun, lettuce, and patty. Clean, simple shapes suitable for stacking visually in a game. Bright colors, thick outlines, transparent background.", "description": "A single stylized burger item that can be stacked.", "subtype": "object", "aspect_ratio": [933, 862], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "regular_customer": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/regular_customer_b161ba32-1aff-4a10-b1f9-2ffb63464781.webp", "type": "image", "prompt": "Generate a cute, stylized 2D cartoon customer, chibi-style. Wearing casual everyday clothes (like a t-shirt and jeans). Top-down perspective, facing downwards as if looking at a counter. Friendly expression, bright colors, transparent background.", "description": "A casual customer character waiting to be served.", "subtype": "character", "aspect_ratio": [718, 1307], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "coin_pile": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/coin_pile_7ab10478-1c3e-483a-96d9-2f47352cbdbf.webp", "type": "image", "prompt": "Generate a small pile of shiny, stylized 2D cartoon gold coins. Top-down perspective, looking like they are lying on the ground. High contrast, bright yellow and orange tones, clean outlines, transparent background.", "description": "A small pile of gold coins collected from customers.", "subtype": "object", "aspect_ratio": [1036, 819], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "bill_pile": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/bill_pile_81043488-18c4-42ab-a5ca-3db68321d65e.webp", "type": "image", "prompt": "Generate a small pile of stylized 2D cartoon paper money bills. Green and blue color palette, top-down perspective, resting on the floor. Simple, recognizable currency shape, transparent background.", "description": "A small stack of paper money bills.", "subtype": "object", "aspect_ratio": [972, 819], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "background_food_cart": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/background_food_cart_c3fcc855-40d5-4dcc-9be7-1b0b310854b1.webp", "type": "image", "prompt": "Generate a 9:16 tall image of a simple 2D cartoon street food environment. Top-down view of a clean paved sidewalk or asphalt area. Include subtle details like a lamppost on the edge and a distant building silhouette. The center area should be open and clear for gameplay objects. Bright, inviting, sun-drenched atmosphere.", "description": "The main environment background showing the street area.", "subtype": "background", "aspect_ratio": [1024, 1536], "compression_suggestion": "original", "duration_secs": null, "loop": null, "animation": null}, "sfx_collect_food": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/sfx_collect_food_6dde687a-0f0d-4746-a5c2-5a3bf7571d49.mp3", "type": "audio", "prompt": "A short, satisfying 'pop' sound effect, cartoonish and organic, slightly bouncy, high-pitched and upbeat, suitable for frequent collection in a mobile game.", "description": "A short, cheerful 'pop' sound effect when a burger is added to the player's stack.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 0.44, "loop": false, "animation": null}, "sfx_serve_customer": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/sfx_serve_customer_3735ec3a-f88c-45e6-8e31-f220246b817a.mp3", "type": "audio", "prompt": "A gentle, positive musical chime, light synth bells, friendly and rewarding, clear audio tail, conveys a sense of task completion and customer satisfaction.", "description": "A rewarding chime sound played when food is handed over to a customer.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 1.85, "loop": false, "animation": null}, "sfx_collect_money": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/sfx_collect_money_287fbe2f-f0c7-4276-a7a3-0ece639286c4.mp3", "type": "audio", "prompt": "A crisp, satisfying coin jingle combined with a subtle vintage cash register 'cha-ching', metallic but bright, rewarding and clear, optimized for repetitive pickup feedback.", "description": "A crisp cash register or coin jingle sound when the player picks up money.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 0.93, "loop": false, "animation": null}, "sfx_upgrade": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/sfx_upgrade_4c6d22ee-cf02-473c-904b-7ea709b1d7c4.mp3", "type": "audio", "prompt": "A triumphant and magical short sparkle fanfare, rising pitch, shimmering textures, exciting and celebratory, high-quality audio feedback for character or machine progression.", "description": "A triumphant sparkle or short fanfare for menu upgrades.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 2.83, "loop": false, "animation": null}, "music_background_loop": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/music_background_loop_004fcabb-aba2-47df-9ec2-7247d4a3f3d2.mp3", "type": "audio", "prompt": "Upbeat lo-fi hip hop mixed with light jazzy elements, 100 BPM, walking bassline, sunny electric piano chords, light shaker percussion, relaxed but bustling atmosphere, non-distracting, seamless loop, cheerful and inviting for a tycoon game.", "description": "Main cheerful background music for the Street Food Empire gameplay.", "subtype": "music", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 30, "loop": true, "animation": null}, "fries_machine": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/fries_machine_bfb6ce42-3124-46ea-b2f9-a9783b9d4304.webp", "type": "image", "prompt": "Generate a 2D cartoon-style fries deep fryer machine for a street food game, seen from a top-down slightly angled perspective. The machine should have bright colors, clean lines, and a polished game-ready look. It features a silver metallic body with a basket of golden fries being submerged in oil. Match the artistic style, line weight, and shading of the burger_machine. Professional quality, transparent background.", "description": "A 2D cartoon-style fries deep fryer machine that matches the existing burger machine station.", "subtype": "object", "aspect_ratio": [897, 1006], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "fries_item": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/fries_item_e8eb40b4-e35a-4d90-acea-c6d06e880bb8.webp", "type": "image", "prompt": "Generate a 2D cartoon-style small carton of French fries. Bright red carton with golden yellow fries sticking out. High-quality game asset, clean lines, bold colors. Top-down slightly angled perspective to match the style of burger_item. Transparent background, professional mobile game UI quality.", "description": "A 2D cartoon-style carton of French fries, used as a stackable food item.", "subtype": "object", "aspect_ratio": [693, 815], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "soda_machine": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/soda_machine_a184713b-8279-46df-a8fe-b3939ea71b48.webp", "type": "image", "prompt": "Generate a 2D cartoon-style soda fountain machine for a street food game, featuring modern dispenser nozzles. Top-down slightly angled perspective. Use bright, clean lines and a polished aesthetic that matches the visual style and color palette of the burger_machine. High resolution, professional game asset, transparent background.", "description": "A 2D cartoon-style soda fountain machine with dispenser nozzles for the street food cart.", "subtype": "object", "aspect_ratio": [895, 1003], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "soda_item": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/soda_item_6f884462-1b20-4dc4-a77c-370a7853ec84.webp", "type": "image", "prompt": "Generate a 2D cartoon-style soda cup with a straw for a street food game. Bright colors, clean outlines, high-quality game asset. Top-down or slightly angled perspective to match burger_item and fries_item. Transparent background, professional quality.", "description": "A single stylized soda cup item that can be stacked or carried, matching the existing food items.", "subtype": "object", "aspect_ratio": [544, 1019], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "worker_character_idle": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/worker_character_idle_96ee8e82-46af-477e-bceb-4b3945f14234.webp", "type": "image", "prompt": "Generate a full-body, top-down view illustration of a staff worker character for a street food game. The character should have a similar 2D cartoon art style to the player_character_idle but wearing a bright green apron and matching hat. Standing illustration, front-facing from a top-down perspective, clean bold lines, vibrant colors, professional game-ready asset, transparent background.", "description": "A staff worker character in an idle pose, visually distinguished from the player by a different colored uniform.", "subtype": "character", "aspect_ratio": [686, 1237], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "vip_car": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/vip_car_dc24a406-a1bf-4b2d-9701-7aa9b0d2f1d2.webp", "type": "image", "prompt": "Generate a high-end luxury limousine for a street food game, top-down view, sleek purple body with gold chrome accents, bright and colorful cartoon style, polished and clean game-ready asset, professional quality, transparent background.", "description": "A luxury VIP vehicle that arrives at the food cart, styled as a sleek purple limousine from a top-down perspective.", "subtype": "object", "aspect_ratio": [344, 983], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "vip_customer": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/vip_customer_f2bd02a9-fbe9-4c1e-9bae-fabf61545033.webp", "type": "image", "prompt": "Generate a full-body, top-down view of a VIP customer character for a 2D street food game. The character should have a friendly and wealthy appearance, wearing a sleek black fancy suit, a tall top hat, and sparkly gold accessories. Professional cartoon style, clean lines, polished game-ready asset. Match the visual style and top-down perspective of 'regular_customer'. High resolution, transparent background.", "description": "A wealthy VIP customer wearing a fancy suit and top hat, seen from a top-down perspective.", "subtype": "character", "aspect_ratio": [665, 1248], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}}, "game_code": "<!DOCTYPE html>\n<html>\n<head>\n    <title>Street Food Empire</title>\n    <style>\n        /* CSS Reset */\n        body, html {\n            margin: 0;\n            padding: 0;\n            width: 100%;\n            height: 100%;\n            overflow: hidden;\n            touch-action: none; /* Disable default touch actions like scrolling */\n            -ms-touch-action: none;\n            font-family: 'Poppins', sans-serif; /* Default font */\n            color: #333;\n        }\n\n        /* Custom Properties */\n        :root {\n            /* Animation Timings */\n            --transition-fast: 150ms;\n            --transition-normal: 300ms;\n            --transition-slow: 500ms;\n\n            /* Easing Functions */\n            --ease-out: cubic-bezier(0.0, 0, 0.2, 1);\n            --ease-bounce: cubic-bezier(0.68, -0.6, 0.32, 1.6);\n\n            /* Spacing Scale (8px grid) */\n            --space-xs: 8px;\n            --space-sm: 16px;\n            --space-md: 24px;\n            --space-lg: 32px;\n            --space-xl: 48px;\n\n            /* Colors */\n            --color-primary: #FFD700; /* Gold */\n            --color-secondary: #FF6347; /* Tomato */\n            --color-accent: #4CAF50; /* Green */\n            --color-background: #F0F8FF; /* AliceBlue */\n            --color-text-dark: #333;\n            --color-text-light: #FFF;\n            --color-ui-panel: rgba(255, 255, 255, 0.9);\n            --color-ui-border: #CCC;\n            --color-button-bg: var(--color-accent);\n            --color-button-hover: #5cb85c;\n            --color-button-active: #449d44;\n            --color-button-disabled: #b0b0b0;\n        }\n\n        /* Font Imports */\n        @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap');\n        @import url('https://fonts.googleapis.com/css2?family=Chewy&display=swap');\n\n        #game-world {\n            position: relative;\n            width: 100%;\n            height: 100%;\n            background-color: var(--color-background);\n            display: flex;\n            justify-content: center;\n            align-items: center;\n        }\n\n        #game-canvas {\n            display: block;\n            background-color: transparent; /* Background handled by game-world div */\n        }\n\n        /* UI Overlay for HUD and Controls */\n        .ui-overlay {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            pointer-events: none; /* Allow clicks to pass through by default */\n            display: flex;\n            flex-direction: column;\n            justify-content: space-between;\n            padding: var(--space-sm);\n            box-sizing: border-box;\n        }\n\n        .ui-overlay > * {\n            pointer-events: auto; /* Re-enable pointer events for specific UI elements */\n        }\n\n        /* Top HUD */\n        .hud-top {\n            display: flex;\n            justify-content: space-between;\n            align-items: center;\n            width: 100%;\n            padding: var(--space-xs) var(--space-sm);\n            box-sizing: border-box;\n        }\n\n        .money-display {\n            background-color: var(--color-ui-panel);\n            border-radius: var(--space-md);\n            padding: var(--space-xs) var(--space-sm);\n            display: flex;\n            align-items: center;\n            font-size: 28px; /* HUD Elements */\n            font-weight: 600;\n            color: var(--color-text-dark);\n            box-shadow: 0 4px 8px rgba(0,0,0,0.1);\n            text-shadow: 1px 1px 2px rgba(255,255,255,0.5);\n            min-width: 180px;\n            justify-content: center;\n        }\n\n        .money-icon {\n            width: 32px;\n            height: 32px;\n            margin-right: var(--space-xs);\n            vertical-align: middle;\n        }\n\n        /* Upgrade Panel */\n        .upgrade-panel {\n            position: absolute;\n            bottom: var(--space-lg);\n            right: var(--space-sm);\n            background-color: var(--color-ui-panel);\n            border-radius: var(--space-md);\n            padding: var(--space-sm);\n            box-shadow: 0 8px 16px rgba(0,0,0,0.2);\n            display: flex;\n            flex-direction: column;\n            gap: var(--space-sm);\n            width: 500px;\n            max-height: 80vh;\n            pointer-events: auto;\n            transform: translateX(120%); /* Start off-screen */\n            transition: transform var(--transition-normal) var(--ease-out);\n            overflow: hidden;\n        }\n\n        .upgrade-list {\n            overflow-y: auto;\n            display: flex;\n            flex-direction: column;\n            gap: var(--space-sm);\n            padding-right: 5px;\n        }\n\n        .upgrade-list::-webkit-scrollbar {\n            width: 8px;\n        }\n\n        .upgrade-list::-webkit-scrollbar-thumb {\n            background: var(--color-ui-border);\n            border-radius: 4px;\n        }\n\n        .upgrade-panel.open {\n            transform: translateX(0);\n        }\n\n        .upgrade-panel h3 {\n            font-family: 'Chewy', cursive;\n            font-size: 32px; /* Menus/Buttons */\n            color: var(--color-secondary);\n            margin: 0 0 var(--space-xs) 0;\n            text-align: center;\n        }\n\n        .upgrade-item {\n            display: flex;\n            align-items: center;\n            gap: var(--space-xs);\n            background-color: #f9f9f9;\n            border-radius: var(--space-sm);\n            padding: var(--space-xs);\n            box-shadow: inset 0 1px 3px rgba(0,0,0,0.05);\n        }\n\n        .upgrade-item-icon {\n            width: 48px;\n            height: 48px;\n            background-color: var(--color-primary);\n            border-radius: var(--space-xs);\n            display: flex;\n            justify-content: center;\n            align-items: center;\n            font-size: 24px;\n            font-weight: bold;\n            color: var(--color-text-light);\n        }\n\n        .upgrade-item-info {\n            flex-grow: 1;\n        }\n\n        .upgrade-item-info h4 {\n            margin: 0;\n            font-size: 22px; /* Menus/Buttons */\n            color: var(--color-text-dark);\n        }\n\n        .upgrade-item-info p {\n            margin: 0;\n            font-size: 18px; /* Captions/Subtext */\n            color: #666;\n        }\n\n        .upgrade-button {\n            background-color: var(--color-button-bg);\n            color: var(--color-text-light);\n            border: none;\n            border-radius: var(--space-sm);\n            padding: var(--space-xs) var(--space-sm);\n            font-size: 22px; /* Menus/Buttons */\n            font-weight: 600;\n            cursor: pointer;\n            transition: background-color var(--transition-fast) var(--ease-out), transform var(--transition-fast) var(--ease-out);\n            min-width: 96px; /* Touch Targets */\n            min-height: 48px; /* Half of touch target for smaller buttons */\n            display: flex;\n            justify-content: center;\n            align-items: center;\n        }\n\n        .upgrade-button:hover:not(:disabled) {\n            background-color: var(--color-button-hover);\n            transform: translateY(-2px);\n        }\n\n        .upgrade-button:active:not(:disabled) {\n            background-color: var(--color-button-active);\n            transform: translateY(0);\n        }\n\n        .upgrade-button:disabled {\n            background-color: var(--color-button-disabled);\n            cursor: not-allowed;\n        }\n\n        /* Toggle Button for Upgrade Panel */\n        .toggle-upgrade-button {\n            position: absolute;\n            bottom: var(--space-lg);\n            right: var(--space-sm);\n            background-color: var(--color-secondary);\n            color: var(--color-text-light);\n            border: none;\n            border-radius: 50%;\n            width: 96px; /* Touch Targets */\n            height: 96px; /* Touch Targets */\n            font-size: 40px;\n            display: flex;\n            justify-content: center;\n            align-items: center;\n            box-shadow: 0 8px 16px rgba(0,0,0,0.2);\n            cursor: pointer;\n            transition: background-color var(--transition-fast) var(--ease-out), transform var(--transition-fast) var(--ease-out);\n            z-index: 100; /* Ensure it's above the panel when closed */\n        }\n\n        .toggle-upgrade-button:hover:not(:disabled) {\n            background-color: #e05038;\n            transform: translateY(-4px);\n        }\n\n        .toggle-upgrade-button:active:not(:disabled) {\n            background-color: #c04028;\n            transform: translateY(0);\n        }\n\n        .toggle-upgrade-button.open {\n            right: calc(var(--space-sm) + 300px + var(--space-sm)); /* Position next to open panel */\n            transform: translateX(0);\n        }\n\n        /* Virtual Joystick */\n        #joystick-container {\n            position: absolute;\n            bottom: var(--space-lg);\n            left: var(--space-lg);\n            width: 180px; /* Base size */\n            height: 180px; /* Base size */\n            pointer-events: none; /* Let touches pass through to canvas */\n            display: none; /* Hidden by default, shown on touch */\n            z-index: 200;\n        }\n\n        #joystick-base {\n            position: absolute;\n            width: 160px;\n            height: 160px;\n            background-color: rgba(0, 0, 0, 0.3);\n            border-radius: 50%;\n            display: flex;\n            justify-content: center;\n            align-items: center;\n            opacity: 0.8;\n        }\n\n        #joystick-knob {\n            position: absolute;\n            width: 80px;\n            height: 80px;\n            background-color: rgba(255, 255, 255, 0.7);\n            border-radius: 50%;\n            box-shadow: 0 0 10px rgba(255, 255, 255, 0.5);\n        }\n\n        /* Floating Text */\n        .floating-text {\n            position: absolute;\n            font-size: 28px; /* HUD Elements */\n            font-weight: bold;\n            color: var(--color-primary);\n            text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;\n            animation: floatUpFadeOut 1.5s forwards;\n            pointer-events: none;\n            white-space: nowrap;\n            z-index: 10;\n        }\n\n        @keyframes floatUpFadeOut {\n            0% {\n                transform: translateY(0) scale(1);\n                opacity: 1;\n            }\n            100% {\n                transform: translateY(-80px) scale(1.2);\n                opacity: 0;\n            }\n        }\n\n        /* Offline Earnings Modal */\n        #offline-earnings-modal {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            background-color: rgba(0, 0, 0, 0.7);\n            display: flex;\n            justify-content: center;\n            align-items: center;\n            z-index: 1000;\n            pointer-events: auto;\n            opacity: 0;\n            visibility: hidden;\n            transition: opacity var(--transition-normal) var(--ease-out), visibility var(--transition-normal);\n        }\n\n        #offline-earnings-modal.show {\n            opacity: 1;\n            visibility: visible;\n        }\n\n        .modal-content {\n            background-color: var(--color-ui-panel);\n            border-radius: var(--space-lg);\n            padding: var(--space-xl);\n            box-shadow: 0 16px 32px rgba(0, 0, 0, 0.3);\n            text-align: center;\n            max-width: 600px;\n            width: 90%;\n            display: flex;\n            flex-direction: column;\n            gap: var(--space-md);\n        }\n\n        .modal-content h2 {\n            font-family: 'Chewy', cursive;\n            font-size: 40px; /* Titles/Screen Headers */\n            color: var(--color-secondary);\n            margin-bottom: var(--space-sm);\n        }\n\n        .modal-content p {\n            font-size: 24px; /* Dialogue/Lore */\n            color: var(--color-text-dark);\n            margin-bottom: var(--space-md);\n        }\n\n        .modal-buttons {\n            display: flex;\n            flex-direction: column;\n            gap: var(--space-sm);\n        }\n\n        .modal-button {\n            background-color: var(--color-button-bg);\n            color: var(--color-text-light);\n            border: none;\n            border-radius: var(--space-md);\n            padding: var(--space-sm) var(--space-lg);\n            font-size: 28px; /* Menus/Buttons */\n            font-weight: 600;\n            cursor: pointer;\n            transition: background-color var(--transition-fast) var(--ease-out), transform var(--transition-fast) var(--ease-out);\n            min-height: 96px; /* Touch Targets */\n        }\n\n        .modal-button.primary {\n            background-color: var(--color-primary);\n            color: var(--color-text-dark);\n        }\n\n        .modal-button:hover:not(:disabled) {\n            transform: translateY(-2px);\n            filter: brightness(1.1);\n        }\n\n        .modal-button:active:not(:disabled) {\n            transform: translateY(0);\n            filter: brightness(0.9);\n        }\n\n        .modal-button:disabled {\n            background-color: var(--color-button-disabled);\n            cursor: not-allowed;\n        }\n\n        /* Edit Mode specific styles */\n        .edit-mode-overlay {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            pointer-events: none;\n            z-index: 500;\n        }\n\n        .edit-mode-overlay .draggable-handle {\n            position: absolute;\n            width: 40px;\n            height: 40px;\n            background-color: rgba(0, 120, 255, 0.6);\n            border: 2px solid rgba(0, 120, 255, 0.9);\n            border-radius: 8px;\n            cursor: grab;\n            pointer-events: auto;\n            display: flex;\n            justify-content: center;\n            align-items: center;\n            color: white;\n            font-size: 24px;\n            font-weight: bold;\n            text-shadow: 1px 1px 2px rgba(0,0,0,0.5);\n        }\n\n        .edit-mode-overlay .draggable-handle.active {\n            cursor: grabbing;\n            background-color: rgba(0, 120, 255, 0.8);\n        }\n\n        .edit-mode-overlay .interaction-zone-visual {\n            position: absolute;\n            background-color: rgba(255, 0, 0, 0.2);\n            border: 2px dashed red;\n            pointer-events: none;\n            box-sizing: border-box;\n        }\n\n        /* Reduced motion preference */\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                scroll-behavior: auto !important;\n            }\n        }\n    </style>\n</head>\n<body>\n    <div id=\"game-world\">\n        <canvas id=\"game-canvas\"></canvas>\n\n        <!-- UI Overlay -->\n        <div class=\"ui-overlay\">\n            <!-- Top HUD -->\n            <div class=\"hud-top\">\n                <div class=\"money-display\">\n                    <img src=\"\" id=\"money-icon\" alt=\"Money\" class=\"money-icon\">\n                    <span id=\"money-amount\">0</span>\n                </div>\n            </div>\n\n            <!-- Upgrade Panel -->\n            <div id=\"upgrade-panel\" class=\"upgrade-panel\">\n                <h3>Upgrades</h3>\n                <div id=\"upgrade-list\" class=\"upgrade-list\">\n                    <!-- Upgrades will be dynamically populated -->\n                </div>\n            </div>\n\n            <!-- Toggle Button for Upgrade Panel -->\n            <button id=\"toggle-upgrade-panel\" class=\"toggle-upgrade-button\">\u2b06\ufe0f</button>\n\n            <!-- Virtual Joystick (Mobile Only) -->\n            <div id=\"joystick-container\">\n                <div id=\"joystick-base\"></div>\n                <div id=\"joystick-knob\"></div>\n            </div>\n        </div>\n\n        <!-- Offline Earnings Modal -->\n        <div id=\"offline-earnings-modal\">\n            <div class=\"modal-content\">\n                <h2>Welcome Back!</h2>\n                <p>While you were away, your business earned:</p>\n                <p style=\"font-size: 36px; font-weight: bold; color: var(--color-accent);\" id=\"offline-earnings-amount\">$0</p>\n                <div class=\"modal-buttons\">\n                    <button id=\"claim-offline-earnings\" class=\"modal-button primary\">Claim Earnings</button>\n                    <!-- <button id=\"claim-double-earnings\" class=\"modal-button\">Watch Ad for 2x Earnings</button> -->\n                </div>\n            </div>\n        </div>\n\n        <!-- Edit Mode Overlay -->\n        <div id=\"edit-mode-overlay\" class=\"edit-mode-overlay\">\n            <!-- Draggable handles and interaction zone visuals will be added here dynamically -->\n        </div>\n    </div>\n\n    <script>\n        // Game Overview:\n        // \"Street Food Empire\" is a 2D mobile idle tycoon game where players build and manage a food business.\n        // The core loop involves collecting food from machines, serving customers, collecting money, and upgrading.\n        // This initial scope focuses on a single \"Small Food Cart\" with a Burger Stand, a player character,\n        // basic customers, and core upgrade systems for player speed, carry capacity, and burger production.\n        // The game features a top-down perspective, virtual joystick controls (or WASD/Arrows for desktop),\n        // bright cartoon visuals, and satisfying sound effects. There are no traditional win/lose conditions;\n        // the goal is continuous progression and expansion.\n\n        // Global game state and constants\n        const GAME_WIDTH = 720;\n        const GAME_HEIGHT = 1280;\n        const ASSET_SCALE = 0.8; // General scaling for game objects\n        const PLAYER_SIZE = 96; // Player avatar minimum size\n        const INTERACTION_ZONE_RADIUS = 80; // Radius for automatic interactions\n        const MONEY_COLLECTION_RADIUS = 60; // Radius for money collection\n        const MAX_DELTA_TIME = 0.11; // Cap delta time to prevent large updates\n\n        const assetCache = {};\n        const audioContext = new (window.AudioContext || window.webkitAudioContext)();\n        const soundBuffers = {};\n\n        let canvas, ctx;\n        let lastTimestamp = 0;\n        let gameMode = 'play';\n\n        // Runtime game state (not stored in gameConfig)\n        let player = {};\n        let burgerMachine = {};\n        let machines = [];\n        let workers = [];\n        let customerCounter = {};\n        let customers = [];\n        let moneyPiles = [];\n        let currentMoney = 0;\n        let lastCustomerSpawnTime = 0;\n        let lastVIPSpawnTime = Date.now(); // Added for VIP timing\n        let lastOfflineCheckTime = Date.now();\n        let joystick = {\n            active: false,\n            startX: 0, startY: 0,\n            currentX: 0, currentY: 0,\n            baseElement: null, knobElement: null,\n            maxRadius: 80, deadZone: 10\n        };\n        let keys = {};\n        let upgradePanelOpen = false;\n\n        const UPGRADE_DATA = {\n            'playerSpeed': { icon: '\ud83c\udfc3', key: 'player.speedLevel', baseCostKey: 'player.baseSpeedUpgradeCost', multKey: 'player.speedUpgradeCostMultiplier', name: 'Player Speed' },\n            'playerCapacity': { icon: '\ud83c\udf92', key: 'player.capacityLevel', baseCostKey: 'player.baseCapacityUpgradeCost', multKey: 'player.capacityUpgradeCostMultiplier', name: 'Carry Capacity' },\n            'burgerProduction': { icon: '\ud83c\udf54', key: 'burgerMachine.productionLevel', baseCostKey: 'burgerMachine.baseProductionUpgradeCost', multKey: 'burgerMachine.productionUpgradeCostMultiplier', name: 'Burger Speed' },\n            'burgerStock': { icon: '\ud83d\udce6', key: 'upgrades.burgerMaxStock.level', baseCostKey: 'upgrades.burgerMaxStock.baseCost', multKey: 'upgrades.burgerMaxStock.costMultiplier', name: 'Burger Stock' },\n            'friesUnlock': { icon: '\ud83c\udf5f', key: 'upgrades.friesLevel.level', baseCostKey: 'upgrades.friesLevel.baseCost', multKey: 'upgrades.friesLevel.costMultiplier', name: 'Unlock Fries' },\n            'friesProduction': { icon: '\u26a1', key: 'upgrades.friesProduction.level', baseCostKey: 'upgrades.friesProduction.baseCost', multKey: 'upgrades.friesProduction.costMultiplier', name: 'Fries Speed' },\n            'sodaUnlock': { icon: '\ud83e\udd64', key: 'upgrades.sodaLevel.level', baseCostKey: 'upgrades.sodaLevel.baseCost', multKey: 'upgrades.sodaLevel.costMultiplier', name: 'Unlock Soda' },\n            'sodaProduction': { icon: '\ud83e\udd64', key: 'upgrades.sodaProduction.level', baseCostKey: 'upgrades.sodaProduction.baseCost', multKey: 'upgrades.sodaProduction.costMultiplier', name: 'Soda Speed' },\n            'customerMax': { icon: '\ud83d\udc65', key: 'upgrades.customerMax.level', baseCostKey: 'upgrades.customerMax.baseCost', multKey: 'upgrades.customerMax.costMultiplier', name: 'Max Customers' },\n            'customerSpawn': { icon: '\ud83d\udd52', key: 'upgrades.customerSpawn.level', baseCostKey: 'upgrades.customerSpawn.baseCost', multKey: 'upgrades.customerSpawn.costMultiplier', name: 'Spawn Rate' },\n            'customerPay': { icon: '\ud83d\udcb0', key: 'upgrades.customerPay.level', baseCostKey: 'upgrades.customerPay.baseCost', multKey: 'upgrades.customerPay.costMultiplier', name: 'Profit+' },\n            'serverCount': { icon: '\ud83d\udc68\u200d\ud83c\udf73', key: 'upgrades.serverCount.level', baseCostKey: 'upgrades.serverCount.baseCost', multKey: 'upgrades.serverCount.costMultiplier', name: 'Hire Waiter' },\n            'serverSpeed': { icon: '\ud83d\udc5f', key: 'upgrades.serverSpeed.level', baseCostKey: 'upgrades.serverSpeed.baseCost', multKey: 'upgrades.serverSpeed.costMultiplier', name: 'Waiter Speed' },\n            'collectorCount': { icon: '\ud83e\udd11', key: 'upgrades.cashCollectorCount.level', baseCostKey: 'upgrades.cashCollectorCount.baseCost', multKey: 'upgrades.cashCollectorCount.costMultiplier', name: 'Hire Cashier' },\n            'collectorSpeed': { icon: '\ud83e\uddf2', key: 'upgrades.cashCollectorSpeed.level', baseCostKey: 'upgrades.cashCollectorSpeed.baseCost', multKey: 'upgrades.cashCollectorSpeed.costMultiplier', name: 'Cashier Speed' },\n            'chefCount': { icon: '\ud83c\udf73', key: 'upgrades.foodMakerCount.level', baseCostKey: 'upgrades.foodMakerCount.baseCost', multKey: 'upgrades.foodMakerCount.costMultiplier', name: 'Hire Chef' },\n            'chefSpeed': { icon: '\ud83d\udd2a', key: 'upgrades.foodMakerSpeed.level', baseCostKey: 'upgrades.foodMakerSpeed.baseCost', multKey: 'upgrades.foodMakerSpeed.costMultiplier', name: 'Chef Speed' },\n            'attracterCount': { icon: '\ud83d\udce3', key: 'upgrades.attracterCount.level', baseCostKey: 'upgrades.attracterCount.baseCost', multKey: 'upgrades.attracterCount.costMultiplier', name: 'Hire Attracter' },\n            'attracterPower': { icon: '\ud83d\udce2', key: 'upgrades.attracterPower.level', baseCostKey: 'upgrades.attracterPower.baseCost', multKey: 'upgrades.attracterPower.costMultiplier', name: 'Attracter Range' },\n            'magnetRange': { icon: '\ud83e\uddf2', key: 'upgrades.magnetRange.level', baseCostKey: 'upgrades.magnetRange.baseCost', multKey: 'upgrades.magnetRange.costMultiplier', name: 'Magnet Range' },\n            'offlineEarnings': { icon: '\ud83d\udca4', key: 'upgrades.offlineEarnings.level', baseCostKey: 'upgrades.offlineEarnings.baseCost', multKey: 'upgrades.offlineEarnings.costMultiplier', name: 'Offline Profit' },\n            'patience': { icon: '\u23f3', key: 'upgrades.patienceLevel.level', baseCostKey: 'upgrades.patienceLevel.baseCost', multKey: 'upgrades.patienceLevel.costMultiplier', name: 'Patience' },\n            'tipChance': { icon: '\ud83c\udfb2', key: 'upgrades.tipChance.level', baseCostKey: 'upgrades.tipChance.baseCost', multKey: 'upgrades.tipChance.costMultiplier', name: 'Tip Chance' },\n            'tipValue': { icon: '\ud83d\udc8e', key: 'upgrades.tipValue.level', baseCostKey: 'upgrades.tipValue.baseCost', multKey: 'upgrades.tipValue.costMultiplier', name: 'Tip Value' },\n            'vipChance': { icon: '\ud83c\udf1f', key: 'upgrades.vipChance.level', baseCostKey: 'upgrades.vipChance.baseCost', multKey: 'upgrades.vipChance.costMultiplier', name: 'VIP Chance' },\n            'globalIncome': { icon: '\ud83c\udf0d', key: 'upgrades.globalIncome.level', baseCostKey: 'upgrades.globalIncome.baseCost', multKey: 'upgrades.globalIncome.costMultiplier', name: 'Global Profit' }\n        };\n\n        // --- Asset Management ---\n        async function preloadAssets(assetIds) {\n            const promises = assetIds.map(async id => {\n                const assetInfo = lib.getAsset(id);\n                if (!assetInfo) {\n                    console.warn(`Asset with ID ${id} not found.`);\n                    return;\n                }\n\n                if (assetInfo.type === 'image') {\n                    return new Promise((resolve, reject) => {\n                        const img = new Image();\n                        img.onload = () => {\n                            assetCache[id] = img;\n                            resolve();\n                        };\n                        img.onerror = () => {\n                            console.error(`Failed to load image: ${assetInfo.url}`);\n                            reject(new Error(`Failed to load image: ${assetInfo.url}`));\n                        };\n                        img.src = assetInfo.url;\n                    });\n                } else if (assetInfo.type === 'audio') {\n                    return fetch(assetInfo.url)\n                        .then(response => response.arrayBuffer())\n                        .then(arrayBuffer => audioContext.decodeAudioData(arrayBuffer))\n                        .then(audioBuffer => {\n                            soundBuffers[id] = audioBuffer;\n                        })\n                        .catch(e => console.error(`Failed to load audio: ${assetInfo.url}`, e));\n                }\n            });\n            await Promise.all(promises);\n            console.log(\"All assets preloaded.\");\n        }\n\n        function playSound(id, loop = false, volume = 1.0) {\n            const buffer = soundBuffers[id];\n            if (buffer) {\n                const source = audioContext.createBufferSource();\n                source.buffer = buffer;\n                source.loop = loop;\n\n                const gainNode = audioContext.createGain();\n                gainNode.gain.value = volume;\n                source.connect(gainNode);\n                gainNode.connect(audioContext.destination);\n\n                source.start(0);\n                return source; // Return source to allow stopping looped sounds\n            }\n            return null;\n        }\n\n        // --- Drawing Utilities ---\n        function drawImage(id, x, y, width, height, rotation = 0, flipX = false) {\n            const img = assetCache[id];\n            if (!img) {\n                // console.warn(`Image asset ${id} not found in cache.`);\n                return;\n            }\n\n            ctx.save();\n            ctx.translate(x + width / 2, y + height / 2);\n            if (rotation !== 0) {\n                ctx.rotate(rotation);\n            }\n            if (flipX) {\n                ctx.scale(-1, 1);\n            }\n            ctx.drawImage(img, -width / 2, -height / 2, width, height);\n            ctx.restore();\n        }\n\n        function drawRotatedImage(id, x, y, width, height, angle) {\n            const img = assetCache[id];\n            if (!img) return;\n\n            ctx.save();\n            ctx.translate(x + width / 2, y + height / 2);\n            ctx.rotate(angle);\n            ctx.drawImage(img, -width / 2, -height / 2, width, height);\n            ctx.restore();\n        }\n\n        function drawText(text, x, y, color = 'white', fontSize = 24, align = 'center', font = 'Poppins', stroke = false, strokeColor = 'black') {\n            ctx.font = `${fontSize}px ${font}`;\n            ctx.fillStyle = color;\n            ctx.textAlign = align;\n            ctx.textBaseline = 'middle';\n            if (stroke) {\n                ctx.strokeStyle = strokeColor;\n                ctx.lineWidth = 4;\n                ctx.strokeText(text, x, y);\n            }\n            ctx.fillText(text, x, y);\n        }\n\n        // --- Game Object Classes (Simplified for initial scope) ---\n        class Player {\n            constructor(x, y, speed, carryCapacity) {\n                this.x = x;\n                this.y = y;\n                this.width = PLAYER_SIZE * (lib.getAsset('player_character_idle')?.aspect_ratio?.[0] / lib.getAsset('player_character_idle')?.aspect_ratio?.[1] || 1);\n                this.height = PLAYER_SIZE;\n                this.speed = speed;\n                this.carryCapacity = carryCapacity;\n                this.foodCarried = 0;\n                this.directionX = 0;\n                this.directionY = 0;\n                this.animationFrame = 0;\n                this.animationTimer = 0;\n                this.animationSpeed = 0.15; // seconds per frame\n                this.facingLeft = false;\n                this.isMoving = false;\n            }\n\n            update(deltaTime) {\n                const prevX = this.x;\n                const prevY = this.y;\n\n                if (this.directionX !== 0 || this.directionY !== 0) {\n                    const normalizedSpeed = this.speed * deltaTime;\n                    this.x += this.directionX * normalizedSpeed;\n                    this.y += this.directionY * normalizedSpeed;\n\n                    // Clamp player to canvas bounds\n                    this.x = Math.max(0, Math.min(canvas.width - this.width, this.x));\n                    this.y = Math.max(0, Math.min(canvas.height - this.height, this.y));\n\n                    this.isMoving = true;\n                    this.animationTimer += deltaTime;\n                    if (this.animationTimer >= this.animationSpeed) {\n                        this.animationFrame = (this.animationFrame + 1) % 2; // 2 frames for walk cycle\n                        this.animationTimer = 0;\n                    }\n\n                    if (this.directionX < 0) {\n                        this.facingLeft = true;\n                    } else if (this.directionX > 0) {\n                        this.facingLeft = false;\n                    }\n                } else {\n                    this.isMoving = false;\n                    this.animationFrame = 0; // Reset to idle frame\n                    this.animationTimer = 0;\n                }\n            }\n\n            draw() {\n                const assetId = this.isMoving ? (this.animationFrame === 0 ? 'player_character_walk_1' : 'player_character_walk_2') : 'player_character_idle';\n                drawImage(assetId, this.x, this.y, this.width, this.height, 0, this.facingLeft);\n\n                // Draw carried food\n                for (let i = 0; i < this.foodCarried; i++) {\n                    const foodHeight = 40; // Fixed height for stacked food\n                    const foodWidth = foodHeight * (lib.getAsset('burger_item')?.aspect_ratio?.[0] / lib.getAsset('burger_item')?.aspect_ratio?.[1] || 1);\n                    drawImage('burger_item', this.x + (this.width - foodWidth) / 2, this.y - foodHeight * (i + 1), foodWidth, foodHeight);\n                }\n            }\n\n            getBounds() {\n                return { x: this.x, y: this.y, width: this.width, height: this.height };\n            }\n\n            getCenter() {\n                return { x: this.x + this.width / 2, y: this.y + this.height / 2 };\n            }\n        }\n        class Customer {\n            constructor(x, y, moneyValue) {\n                this.x = x;\n                this.y = y;\n                this.width = PLAYER_SIZE * (lib.getAsset('regular_customer')?.aspect_ratio?.[0] / lib.getAsset('regular_customer')?.aspect_ratio?.[1] || 1);\n                this.height = PLAYER_SIZE;\n                this.moneyValue = moneyValue;\n                this.served = false;\n                this.despawnTimer = 0;\n                this.despawnTime = 0.5; // Quick vanish/despawn after being served\n                this.moveSpeed = 300;\n                this.targetX = x;\n                this.targetY = y;\n            }\n\n            update(deltaTime) {\n                if (this.served) {\n                    this.despawnTimer += deltaTime;\n                } else {\n                    // Smoothly move towards target position in line\n                    const dx = this.targetX - this.x;\n                    const dy = this.targetY - this.y;\n                    const distance = Math.sqrt(dx * dx + dy * dy);\n                    if (distance > 5) {\n                        this.x += (dx / distance) * this.moveSpeed * deltaTime;\n                        this.y += (dy / distance) * this.moveSpeed * deltaTime;\n                    } else {\n                        this.x = this.targetX;\n                        this.y = this.targetY;\n                    }\n                }\n            }\n\n            draw() {\n                if (!this.served) {\n                    const assetId = this.isVIP ? 'vip_customer' : 'regular_customer';\n                    const drawWidth = this.isVIP ? this.width * 1.2 : this.width;\n                    const drawHeight = this.isVIP ? this.height * 1.2 : this.height;\n                    drawImage(assetId, this.x - drawWidth / 2, this.y - drawHeight / 2, drawWidth, drawHeight);\n                    if (this.isVIP) {\n                        drawText('VIP', this.x, this.y - drawHeight / 2 - 20, 'gold', 24, 'center', 'Chewy', true, 'black');\n                    }\n                }\n            }\n\n            getBounds() {\n                return { x: this.x - this.width / 2, y: this.y - this.height / 2, width: this.width, height: this.height };\n            }\n        }\n\n        class VIPCar {\n            constructor() {\n                this.width = 250;\n                this.height = 120;\n                this.x = -this.width;\n                this.y = 1100; // Park at the bottom area\n                this.targetX = 150;\n                this.speed = 200;\n                this.state = 'entering'; // entering, waiting, leaving\n                this.waitTimer = 0;\n                this.waitTime = 2.0;\n                this.spawnedVIP = false;\n            }\n\n            update(deltaTime) {\n                if (this.state === 'entering') {\n                    if (this.x < this.targetX) {\n                        this.x += this.speed * deltaTime;\n                    } else {\n                        this.state = 'waiting';\n                    }\n                } else if (this.state === 'waiting') {\n                    this.waitTimer += deltaTime;\n                    if (!this.spawnedVIP && this.waitTimer > 0.5) {\n                        spawnVIP(this.x + this.width / 2, this.y);\n                        this.spawnedVIP = true;\n                    }\n                    if (this.waitTimer >= this.waitTime) {\n                        this.state = 'leaving';\n                    }\n                } else if (this.state === 'leaving') {\n                    this.x += this.speed * deltaTime;\n                    if (this.x > canvas.width + 100) {\n                        this.finished = true;\n                    }\n                }\n            }\n\n            draw() {\n                const asset = lib.getAsset('vip_car');\n                if (asset) {\n                    drawImage('vip_car', this.x, this.y, this.width, this.height);\n                } else {\n                    ctx.fillStyle = 'purple';\n                    ctx.fillRect(this.x, this.y, this.width, this.height);\n                }\n            }\n        }\n\n        let vipCars = [];\n\n        function spawnVIP(x, y) {\n            const customerValue = (window.gameConfig.customer.baseMoneyPerCustomer + (window.gameConfig.upgrades.customerPay.level * 10)) * 5;\n            const customer = new Customer(x, y, customerValue);\n            customer.isVIP = true;\n            customers.push(customer);\n            showFloatingText('VIP ARRIVED!', x, y - 50);\n        }\n\n        class MoneyPile {\n            constructor(x, y, value) {\n                this.x = x;\n                this.y = y;\n                this.value = value;\n                this.width = 60 * (lib.getAsset('coin_pile')?.aspect_ratio?.[0] / lib.getAsset('coin_pile')?.aspect_ratio?.[1] || 1);\n                this.height = 60;\n                this.collected = false;\n            }\n\n            draw() {\n                if (!this.collected) {\n                    const assetId = this.value >= 50 ? 'bill_pile' : 'coin_pile';\n                    drawImage(assetId, this.x - this.width / 2, this.y - this.height / 2, this.width, this.height);\n                }\n            }\n\n            getBounds() {\n                return { x: this.x - this.width / 2, y: this.y - this.height / 2, width: this.width, height: this.height };\n            }\n        }\n        class FoodMachine {\n            constructor(id, type, x, y, initialProductionTime, itemAsset) {\n                this.id = id;\n                this.type = type;\n                this.x = x;\n                this.y = y;\n                this.width = 150 * ASSET_SCALE * (lib.getAsset(id)?.aspect_ratio?.[0] / lib.getAsset(id)?.aspect_ratio?.[1] || 1);\n                this.height = 150 * ASSET_SCALE;\n                this.initialProductionTime = initialProductionTime;\n                this.productionTime = initialProductionTime;\n                this.productionTimer = 0;\n                this.foodAvailable = 0;\n                this.maxFood = 5;\n                this.itemAsset = itemAsset;\n                this.interactionZone = {\n                    x: this.x - INTERACTION_ZONE_RADIUS,\n                    y: this.y - INTERACTION_ZONE_RADIUS,\n                    width: this.width + INTERACTION_ZONE_RADIUS * 2,\n                    height: this.height + INTERACTION_ZONE_RADIUS * 2\n                };\n            }\n\n            update(deltaTime) {\n                if (this.foodAvailable < this.maxFood) {\n                    this.productionTimer += deltaTime;\n                    if (this.productionTimer >= this.productionTime) {\n                        this.foodAvailable++;\n                        this.productionTimer = 0;\n                    }\n                }\n            }\n\n            draw() {\n                drawImage(this.id, this.x, this.y, this.width, this.height);\n                if (this.foodAvailable > 0) {\n                    const itemWidth = 40;\n                    const itemHeight = 40;\n                    drawImage(this.itemAsset, this.x + this.width / 2 - itemWidth / 2, this.y - 20, itemWidth, itemHeight);\n                    drawText(`${this.foodAvailable}`, this.x + this.width / 2 + 30, this.y - 15, 'white', 24, 'left', 'Chewy', true, 'black');\n                }\n            }\n        }\n\n        class Staff {\n            constructor(type, x, y) {\n                this.type = type;\n                this.x = x;\n                this.y = y;\n                this.width = PLAYER_SIZE * 0.8;\n                this.height = PLAYER_SIZE * 0.8;\n                this.speed = 150;\n                this.target = null;\n                this.foodCarried = 0;\n                this.carryCapacity = 1;\n                this.facingLeft = false;\n                this.isMoving = false;\n                this.animationFrame = 0;\n                this.animationTimer = 0;\n            }\n\n            update(deltaTime) {\n                // Decision logic based on type\n                if (this.type === 'server') {\n                    this.updateServer(deltaTime);\n                } else if (this.type === 'collector') {\n                    this.updateCollector(deltaTime);\n                } else if (this.type === 'maker') {\n                    this.updateMaker(deltaTime);\n                } else if (this.type === 'attracter') {\n                    this.updateAttracter(deltaTime);\n                }\n\n                // Separation logic to prevent overlapping\n                workers.forEach(other => {\n                    if (other === this) return;\n                    const dx = this.x - other.x;\n                    const dy = this.y - other.y;\n                    const distSq = dx * dx + dy * dy;\n                    const minDist = 60; // Increased separation distance\n                    if (distSq < minDist * minDist) {\n                        const dist = Math.sqrt(distSq) || 1;\n                        const force = (minDist - dist) / minDist;\n                        // Stronger separation force to avoid overlapping\n                        this.x += (dx / dist) * force * 150 * deltaTime;\n                        this.y += (dy / dist) * force * 150 * deltaTime;\n                    }\n                });\n\n                // Movement\n                if (this.target) {\n                    const dx = this.target.x - this.x;\n                    const dy = this.target.y - this.y;\n                    const dist = Math.sqrt(dx * dx + dy * dy);\n                    if (dist > 10) {\n                        this.x += (dx / dist) * this.speed * deltaTime;\n                        this.y += (dy / dist) * this.speed * deltaTime;\n                        this.isMoving = true;\n                        this.facingLeft = dx < 0;\n                        \n                        this.animationTimer += deltaTime;\n                        if (this.animationTimer >= 0.15) {\n                            this.animationFrame = (this.animationFrame + 1) % 2;\n                            this.animationTimer = 0;\n                        }\n                    } else {\n                        this.isMoving = false;\n                        this.target = null;\n                    }\n                } else {\n                    this.isMoving = false;\n                }\n            }\n\n            updateServer(deltaTime) {\n                if (this.foodCarried < this.carryCapacity) {\n                    // Find a machine with food, prefer machines that haven't been visited recently by other servers\n                    // Simplified: Use worker index to cycle machines\n                    const serverIndex = workers.filter(w => w.type === 'server').indexOf(this);\n                    const machineIdx = (Math.floor(Date.now() / 5000) + serverIndex) % machines.length;\n                    const source = machines[machineIdx].foodAvailable > 0 ? machines[machineIdx] : machines.find(m => m.foodAvailable > 0);\n                    \n                    if (source) {\n                        this.target = { x: source.x + source.width / 2, y: source.y + source.height / 2 };\n                        if (Math.sqrt(Math.pow(this.x - this.target.x, 2) + Math.pow(this.y - this.target.y, 2)) < 30) {\n                            source.foodAvailable--;\n                            this.foodCarried++;\n                            this.target = null;\n                        }\n                    } else {\n                        // Roam to different random spots based on index\n                        if (!this.target && Math.random() < 0.02) {\n                            const offset = this.workerIndex * 30;\n                            this.target = { x: 100 + Math.random() * 500, y: 300 + Math.random() * 800 };\n                        }\n                    }\n                } else {\n                    // Go to counter\n                    this.target = { x: customerCounter.x + customerCounter.width / 2, y: customerCounter.y + customerCounter.height / 2 };\n                    if (Math.sqrt(Math.pow(this.x - this.target.x, 2) + Math.pow(this.y - this.target.y, 2)) < 50) {\n                        const waitingCustomer = customers.find(c => !c.served);\n                        if (waitingCustomer) {\n                            waitingCustomer.served = true;\n                            this.foodCarried--;\n                            const moneyPile = new MoneyPile(customerCounter.moneySpawnPoint.x, customerCounter.moneySpawnPoint.y, waitingCustomer.moneyValue);\n                            moneyPiles.push(moneyPile);\n                        }\n                        this.target = null;\n                    }\n                }\n            }\n\n            updateCollector(deltaTime) {\n                if (moneyPiles.length > 0) {\n                    // Collectors divide the work based on their index\n                    const collectorIndex = workers.filter(w => w.type === 'collector').indexOf(this);\n                    const targetPile = moneyPiles[collectorIndex % moneyPiles.length];\n                    \n                    this.target = { x: targetPile.x, y: targetPile.y };\n                    if (Math.sqrt(Math.pow(this.x - this.target.x, 2) + Math.pow(this.y - this.target.y, 2)) < 30) {\n                        collectMoney(targetPile);\n                        this.target = null;\n                    }\n                } else {\n                    if (!this.target && Math.random() < 0.01) {\n                        this.target = { x: 100 + Math.random() * 500, y: 300 + Math.random() * 800 };\n                    }\n                }\n            }\n\n            updateMaker(deltaTime) {\n                if (machines.length === 0) return;\n                // Distribute makers across machines\n                const makerIndex = workers.filter(w => w.type === 'maker').indexOf(this);\n                const machine = machines[makerIndex % machines.length];\n                \n                if (machine) {\n                    this.target = { x: machine.x - 40, y: machine.y + machine.height / 2 };\n                    const dist = Math.sqrt(Math.pow(this.x - this.target.x, 2) + Math.pow(this.y - this.target.y, 2));\n                    if (dist < 30) {\n                        machine.productionTimer += deltaTime * 0.8; // Boost machine production\n                        this.target = null; // Stay here\n                    }\n                }\n            }\n\n            updateAttracter(deltaTime) {\n                // Different attracters can stand at different promotional spots\n                const attrIndex = workers.filter(w => w.type === 'attracter').indexOf(this);\n                this.target = { x: 600 - (attrIndex * 40), y: 1100 - (attrIndex * 20) };\n            }\n\n            draw() {\n                const assetId = 'worker_character_idle';\n                drawImage(assetId, this.x - this.width / 2, this.y - this.height / 2, this.width, this.height, 0, this.facingLeft);\n                \n                // Draw carried food\n                for (let i = 0; i < this.foodCarried; i++) {\n                    drawImage('burger_item', this.x - 20, this.y - 40 - (i * 20), 40, 40);\n                }\n            }\n        }\n\n        class CustomerCounter {\n            constructor(x, y) {\n                this.x = x;\n                this.y = y;\n                this.width = 180 * ASSET_SCALE * (lib.getAsset('customer_counter')?.aspect_ratio?.[0] / lib.getAsset('customer_counter')?.aspect_ratio?.[1] || 1);\n                this.height = 180 * ASSET_SCALE;\n                this.interactionZone = {\n                    x: this.x - INTERACTION_ZONE_RADIUS,\n                    y: this.y - INTERACTION_ZONE_RADIUS,\n                    width: this.width + INTERACTION_ZONE_RADIUS * 2,\n                    height: this.height + INTERACTION_ZONE_RADIUS * 2\n                };\n                this.customerSpawnPoint = { x: this.x + this.width / 2, y: this.y + this.height + 20 };\n                this.moneySpawnPoint = { x: this.x + this.width / 2, y: this.y + this.height + 80 };\n            }\n\n            draw() {\n                drawImage('customer_counter', this.x, this.y, this.width, this.height);\n            }\n\n            getBounds() {\n                return { x: this.x, y: this.y, width: this.width, height: this.height };\n            }\n        }\n\n\n        // --- Collision Detection ---\n        function checkCollision(rect1, rect2) {\n            return rect1.x < rect2.x + rect2.width &&\n                   rect1.x + rect1.width > rect2.x &&\n                   rect1.y < rect2.y + rect2.height &&\n                   rect1.y + rect1.height > rect2.y;\n        }\n\n        function checkCircleCollision(circle1, circle2) {\n            const dx = circle1.x - circle2.x;\n            const dy = circle1.y - circle2.y;\n            const distance = Math.sqrt(dx * dx + dy * dy);\n            return distance < circle1.radius + circle2.radius;\n        }\n\n        // --- Game Logic ---\n        function initializeGame() {\n            // Initialize player\n            player = new Player(\n                window.gameConfig.player.startX,\n                window.gameConfig.player.startY,\n                window.gameConfig.player.initialSpeed,\n                window.gameConfig.player.initialCarryCapacity\n            );\n            player.speed = window.gameConfig.player.initialSpeed * (1 + window.gameConfig.player.speedLevel * 0.1);\n            player.carryCapacity = window.gameConfig.player.initialCarryCapacity + window.gameConfig.player.capacityLevel;\n\n            // Initialize machines\n            machines = [];\n            \n            // Burger\n            const burgerM = new FoodMachine(\n                'burger_machine', 'burger',\n                window.gameConfig.burgerMachine.x,\n                window.gameConfig.burgerMachine.y,\n                window.gameConfig.burgerMachine.initialProductionTime / (1 + window.gameConfig.burgerMachine.productionLevel * 0.15),\n                'burger_item'\n            );\n            burgerM.maxFood = 5 + (window.gameConfig.upgrades.burgerMaxStock.level * 2);\n            machines.push(burgerM);\n            burgerMachine = burgerM;\n\n            // Fries\n            if (window.gameConfig.upgrades.friesLevel.level > 0) {\n                const friesM = new FoodMachine(\n                    'fries_machine', 'fries',\n                    window.gameConfig.machines.fries.x,\n                    window.gameConfig.machines.fries.y,\n                    window.gameConfig.machines.fries.initialProductionTime / (1 + window.gameConfig.upgrades.friesProduction.level * 0.15),\n                    'fries_item'\n                );\n                friesM.maxFood = window.gameConfig.machines.fries.maxStock + (window.gameConfig.upgrades.friesStock.level * 2);\n                machines.push(friesM);\n            }\n\n            // Soda\n            if (window.gameConfig.upgrades.sodaLevel.level > 0) {\n                const sodaM = new FoodMachine(\n                    'soda_machine', 'soda',\n                    window.gameConfig.machines.soda.x,\n                    window.gameConfig.machines.soda.y,\n                    window.gameConfig.machines.soda.initialProductionTime / (1 + window.gameConfig.upgrades.sodaProduction.level * 0.15),\n                    'soda_item'\n                );\n                sodaM.maxFood = window.gameConfig.machines.soda.maxStock + (window.gameConfig.upgrades.sodaStock.level * 2);\n                machines.push(sodaM);\n            }\n\n            // Initialize Workers\n            workers = [];\n            let totalWorkerCount = 0;\n\n            const serverLvl = window.gameConfig.upgrades.serverCount.level;\n            for (let i = 0; i < serverLvl; i++) {\n                const w = new Staff('server', 400, 800);\n                w.workerIndex = totalWorkerCount++;\n                w.speed = 150 + window.gameConfig.upgrades.serverSpeed.level * 20;\n                w.carryCapacity = 1 + window.gameConfig.upgrades.serverCapacity.level;\n                workers.push(w);\n            }\n            const collectLvl = window.gameConfig.upgrades.cashCollectorCount.level;\n            for (let i = 0; i < collectLvl; i++) {\n                const w = new Staff('collector', 400, 800);\n                w.workerIndex = totalWorkerCount++;\n                w.speed = 150 + window.gameConfig.upgrades.cashCollectorSpeed.level * 25;\n                workers.push(w);\n            }\n            const chefLvl = window.gameConfig.upgrades.foodMakerCount.level;\n            for (let i = 0; i < chefLvl; i++) {\n                const w = new Staff('maker', 100, 400);\n                w.workerIndex = totalWorkerCount++;\n                workers.push(w);\n            }\n            const attrLvl = window.gameConfig.upgrades.attracterCount.level;\n            for (let i = 0; i < attrLvl; i++) {\n                const w = new Staff('attracter', 600, 1100);\n                w.workerIndex = totalWorkerCount++;\n                workers.push(w);\n            }\n\n            // Initialize customer counter\n            customerCounter = new CustomerCounter(\n                window.gameConfig.customerCounter.x,\n                window.gameConfig.customerCounter.y\n            );\n\n            currentMoney = window.gameConfig.player.money;\n            lastCustomerSpawnTime = Date.now();\n            lastVIPSpawnTime = Date.now(); // Reset VIP timer\n            customers = [];\n            moneyPiles = [];\n            vipCars = [];\n\n            // Update UI\n            updateMoneyDisplay();\n            updateUpgradeButtons();\n        }\n\n        function spawnCustomer() {\n            const max = 5 + (window.gameConfig.upgrades.customerMax.level * 2);\n            if (customers.length < max) {\n                let baseValue = window.gameConfig.customer.baseMoneyPerCustomer;\n                \n                // Add value for unlocked machines (Soda is more than Fries)\n                if (window.gameConfig.upgrades.friesLevel.level > 0) {\n                    baseValue += 40; // Fries add value\n                }\n                if (window.gameConfig.upgrades.sodaLevel.level > 0) {\n                    baseValue += 75; // Soda adds even more value than fries\n                }\n\n                // Global Profit+ upgrade (customerPay)\n                baseValue += (window.gameConfig.upgrades.customerPay.level * 25); // Increased from 10 to 25\n\n                // Empire Profits upgrade (globalIncome) provides a massive multiplier (2x, 3x, etc.)\n                const multiplier = 1 + window.gameConfig.upgrades.globalIncome.level;\n                \n                const customerValue = Math.round(baseValue * multiplier);\n                \n                const customer = new Customer(\n                    customerCounter.customerSpawnPoint.x,\n                    canvas.height + 100,\n                    customerValue\n                );\n                customers.push(customer);\n            }\n        }\n\n        function collectFood() {\n            machines.forEach(m => {\n                if (player.foodCarried < player.carryCapacity && m.foodAvailable > 0) {\n                    const playerCenter = player.getCenter();\n                    const dist = Math.sqrt(Math.pow(playerCenter.x - (m.x + m.width/2), 2) + Math.pow(playerCenter.y - (m.y + m.height/2), 2));\n                    const magnet = 80 + (window.gameConfig.upgrades.magnetRange.level * 20);\n                    if (dist < magnet) {\n                        m.foodAvailable--;\n                        player.foodCarried++;\n                        playSound('sfx_collect_food');\n                    }\n                }\n            });\n        }\n\n        function serveCustomer() {\n            const waitingCustomer = customers.find(c => !c.served);\n            if (waitingCustomer && player.foodCarried > 0) {\n                const distToCounter = Math.sqrt(Math.pow(waitingCustomer.x - customerCounter.customerSpawnPoint.x, 2) + Math.pow(waitingCustomer.y - customerCounter.customerSpawnPoint.y, 2));\n                if (distToCounter < 20) {\n                    player.foodCarried--;\n                    waitingCustomer.served = true;\n                    playSound('sfx_serve_customer');\n\n                    let val = waitingCustomer.moneyValue;\n                    if (Math.random() < (window.gameConfig.upgrades.tipChance.level * 0.05)) {\n                        val += (window.gameConfig.upgrades.tipValue.level * 5);\n                        showFloatingText('TIP!', waitingCustomer.x, waitingCustomer.y - 40);\n                    }\n\n                    const moneyPile = new MoneyPile(\n                        customerCounter.moneySpawnPoint.x + (Math.random() - 0.5) * 60,\n                        customerCounter.moneySpawnPoint.y + (Math.random() - 0.5) * 60,\n                        val\n                    );\n                    moneyPiles.push(moneyPile);\n                }\n            }\n        }\n\n        function collectMoney(moneyPile) {\n            currentMoney += moneyPile.value;\n            window.gameConfig.player.money = currentMoney; // Update gameConfig for persistence\n            moneyPile.collected = true;\n            playSound('sfx_collect_money');\n            showFloatingText(`+$${moneyPile.value}`, moneyPile.x, moneyPile.y);\n            updateMoneyDisplay();\n            updateUpgradeButtons();\n        }\n\n        function showFloatingText(text, x, y) {\n            const floatingText = document.createElement('div');\n            floatingText.className = 'floating-text';\n            floatingText.textContent = text;\n            floatingText.style.left = `${x}px`;\n            floatingText.style.top = `${y}px`;\n            document.getElementById('game-world').appendChild(floatingText);\n\n            floatingText.addEventListener('animationend', () => {\n                floatingText.remove();\n            });\n        }\n\n        function calculateUpgradeCost(baseCost, level, multiplier) {\n            return Math.round(baseCost * Math.pow(multiplier, level));\n        }\n\n        function updateMoneyDisplay() {\n            document.getElementById('money-amount').textContent = currentMoney.toLocaleString();\n        }\n\n        function updateUpgradeButtons() {\n            const list = document.getElementById('upgrade-list');\n            list.innerHTML = '';\n\n            for (const [key, data] of Object.entries(UPGRADE_DATA)) {\n                const level = getNestedProperty(window.gameConfig, data.key);\n                const baseCost = getNestedProperty(window.gameConfig, data.baseCostKey);\n                const multiplier = getNestedProperty(window.gameConfig, data.multKey);\n                const cost = calculateUpgradeCost(baseCost, level, multiplier);\n\n                const item = document.createElement('div');\n                item.className = 'upgrade-item';\n                item.innerHTML = `\n                    <div class=\"upgrade-item-icon\">${data.icon}</div>\n                    <div class=\"upgrade-item-info\">\n                        <h4>${data.name}</h4>\n                        <p>Level ${level + 1}</p>\n                    </div>\n                    <button class=\"upgrade-button\" ${currentMoney < cost ? 'disabled' : ''}>$${cost.toLocaleString()}</button>\n                `;\n                item.querySelector('button').onclick = () => applyUpgrade(key);\n                list.appendChild(item);\n            }\n        }\n\n        function applyUpgrade(type) {\n            const data = UPGRADE_DATA[type];\n            if (!data) return;\n\n            const currentLevel = getNestedProperty(window.gameConfig, data.key);\n            const baseCost = getNestedProperty(window.gameConfig, data.baseCostKey);\n            const multiplier = getNestedProperty(window.gameConfig, data.multKey);\n            const cost = calculateUpgradeCost(baseCost, currentLevel, multiplier);\n\n            if (currentMoney >= cost) {\n                currentMoney -= cost;\n                window.gameConfig.player.money = currentMoney;\n                setNestedProperty(window.gameConfig, data.key, currentLevel + 1);\n                playSound('sfx_upgrade');\n                initializeGame();\n                updateUpgradeButtons();\n            }\n        }\n\n        // Helper for nested property access\n        function getNestedProperty(obj, path) {\n            return path.split('.').reduce((acc, part) => acc && acc[part], obj);\n        }\n\n        function setNestedProperty(obj, path, value) {\n            const parts = path.split('.');\n            const lastPart = parts.pop();\n            const target = parts.reduce((acc, part) => acc[part] = acc[part] || {}, obj);\n            target[lastPart] = value;\n        }\n\n        // --- Offline Earnings ---\n        async function checkOfflineEarnings() {\n            const savedState = await lib.getUserGameState();\n            if (savedState && savedState.state && savedState.state.lastPlayedTime) {\n                const lastPlayedTime = savedState.state.lastPlayedTime;\n                const now = Date.now();\n                const timeAwaySeconds = (now - lastPlayedTime) / 1000;\n\n                if (timeAwaySeconds > 60) { // Only show if away for more than 1 minute\n                    const activeEarningsPerSecond = calculateActiveEarningsPerSecond();\n                    const offlineEarnings = activeEarningsPerSecond * timeAwaySeconds * window.gameConfig.offlineEarnings.offlineEarningsRate;\n\n                    if (offlineEarnings > 0) {\n                        document.getElementById('offline-earnings-amount').textContent = `$${Math.round(offlineEarnings).toLocaleString()}`;\n                        document.getElementById('offline-earnings-modal').classList.add('show');\n\n                        document.getElementById('claim-offline-earnings').onclick = () => {\n                            currentMoney += Math.round(offlineEarnings);\n                            window.gameConfig.player.money = currentMoney;\n                            updateMoneyDisplay();\n                            updateUpgradeButtons();\n                            document.getElementById('offline-earnings-modal').classList.remove('show');\n                        };\n                        // Optional: Add logic for 'claim-double-earnings' if rewarded ads are implemented\n                    }\n                }\n            }\n            // Update lastPlayedTime in saved state\n            await lib.saveUserGameState({ lastPlayedTime: Date.now() });\n        }\n\n        function calculateActiveEarningsPerSecond() {\n            // This is a simplified estimation for the initial scope.\n            // In a full game, this would involve average customer service time, customer value, etc.\n            const customerValue = window.gameConfig.customer.baseMoneyPerCustomer;\n            const customerInterval = window.gameConfig.customer.customerSpawnInterval;\n            return customerValue / customerInterval;\n        }\n\n        // --- Edit Mode Functions ---\n        let selectedObject = null;\n        let dragOffsetX, dragOffsetY;\n        let isDragging = false;\n\n        function setupEditMode() {\n            const editOverlay = document.getElementById('edit-mode-overlay');\n            editOverlay.innerHTML = ''; // Clear previous handles\n\n            // Create draggable handles for game objects\n            function createDraggableHandle(obj, key, label) {\n                const handle = document.createElement('div');\n                handle.className = 'draggable-handle';\n                handle.textContent = label;\n                handle.style.left = `${obj.x + obj.width / 2 - 20}px`;\n                handle.style.top = `${obj.y + obj.height / 2 - 20}px`;\n                editOverlay.appendChild(handle);\n\n                handle.onpointerdown = (e) => {\n                    e.stopPropagation();\n                    isDragging = true;\n                    selectedObject = { obj, key };\n                    dragOffsetX = e.clientX - handle.getBoundingClientRect().left;\n                    dragOffsetY = e.clientY - handle.getBoundingClientRect().top;\n                    handle.classList.add('active');\n                };\n            }\n\n            createDraggableHandle(player, 'player', 'P');\n            createDraggableHandle(burgerMachine, 'burgerMachine', 'B');\n            createDraggableHandle(customerCounter, 'customerCounter', 'C');\n\n            // Visual for interaction zones\n            function createInteractionZoneVisual(zone, label) {\n                const zoneVisual = document.createElement('div');\n                zoneVisual.className = 'interaction-zone-visual';\n                zoneVisual.style.left = `${zone.x}px`;\n                zoneVisual.style.top = `${zone.y}px`;\n                zoneVisual.style.width = `${zone.width}px`;\n                zoneVisual.style.height = `${zone.height}px`;\n                editOverlay.appendChild(zoneVisual);\n            }\n\n            createInteractionZoneVisual(burgerMachine.interactionZone, 'Burger Zone');\n            createInteractionZoneVisual(customerCounter.interactionZone, 'Customer Zone');\n\n            editOverlay.onpointermove = (e) => {\n                if (isDragging && selectedObject) {\n                    let newX = e.clientX - dragOffsetX;\n                    let newY = e.clientY - dragOffsetY;\n\n                    // Clamp to canvas bounds\n                    newX = Math.max(0, Math.min(canvas.width - selectedObject.obj.width, newX));\n                    newY = Math.max(0, Math.min(canvas.height - selectedObject.obj.height, newY));\n\n                    selectedObject.obj.x = newX;\n                    selectedObject.obj.y = newY;\n\n                    // Update gameConfig\n                    window.gameConfig[selectedObject.key].x = newX;\n                    window.gameConfig[selectedObject.key].y = newY;\n\n                    // Update handle position\n                    const handle = editOverlay.querySelector('.draggable-handle.active');\n                    if (handle) {\n                        handle.style.left = `${newX + selectedObject.obj.width / 2 - 20}px`;\n                        handle.style.top = `${newY + selectedObject.obj.height / 2 - 20}px`;\n                    }\n\n                    // Re-calculate interaction zones for visuals\n                    if (selectedObject.key === 'burgerMachine') {\n                        burgerMachine.interactionZone = {\n                            x: burgerMachine.x - INTERACTION_ZONE_RADIUS,\n                            y: burgerMachine.y - INTERACTION_ZONE_RADIUS,\n                            width: burgerMachine.width + INTERACTION_ZONE_RADIUS * 2,\n                            height: burgerMachine.height + INTERACTION_ZONE_RADIUS * 2\n                        };\n                    } else if (selectedObject.key === 'customerCounter') {\n                        customerCounter.interactionZone = {\n                            x: customerCounter.x - INTERACTION_ZONE_RADIUS,\n                            y: customerCounter.y - INTERACTION_ZONE_RADIUS,\n                            width: customerCounter.width + INTERACTION_ZONE_RADIUS * 2,\n                            height: customerCounter.height + INTERACTION_ZONE_RADIUS * 2\n                        };\n                        customerCounter.customerSpawnPoint = { x: customerCounter.x + customerCounter.width / 2, y: customerCounter.y + customerCounter.height + 20 };\n                        customerCounter.moneySpawnPoint = { x: customerCounter.x + customerCounter.width / 2, y: customerCounter.y + customerCounter.height + 80 };\n                    }\n                    // Re-draw interaction zone visuals\n                    editOverlay.querySelectorAll('.interaction-zone-visual').forEach(el => el.remove());\n                    createInteractionZoneVisual(burgerMachine.interactionZone, 'Burger Zone');\n                    createInteractionZoneVisual(customerCounter.interactionZone, 'Customer Zone');\n                }\n            };\n\n            editOverlay.onpointerup = () => {\n                isDragging = false;\n                selectedObject = null;\n                editOverlay.querySelectorAll('.draggable-handle').forEach(h => h.classList.remove('active'));\n            };\n        }\n\n        // --- Input Handling ---\n        function setupInputListeners() {\n            // Keyboard for Desktop\n            window.addEventListener('keydown', (e) => {\n                keys[e.key.toLowerCase()] = true;\n            });\n            window.addEventListener('keyup', (e) => {\n                keys[e.key.toLowerCase()] = false;\n            });\n\n            // Touch/Mouse for Joystick\n            const joystickContainer = document.getElementById('joystick-container');\n            const joystickBase = document.getElementById('joystick-base');\n            const joystickKnob = document.getElementById('joystick-knob');\n            joystick.baseElement = joystickBase;\n            joystick.knobElement = joystickKnob;\n\n            function getTouchPos(e) {\n                const rect = canvas.getBoundingClientRect();\n                return {\n                    x: e.clientX - rect.left,\n                    y: e.clientY - rect.top\n                };\n            }\n\n            function startJoystick(e) {\n                if (gameMode === 'edit') return;\n                e.preventDefault(); // Prevent scrolling/zooming\n                joystickContainer.style.display = 'block';\n                joystick.active = true;\n\n                const clientX = e.touches ? e.touches[0].clientX : e.clientX;\n                const clientY = e.touches ? e.touches[0].clientY : e.clientY;\n\n                // Position joystick container relative to touch start\n                const containerRect = joystickContainer.getBoundingClientRect();\n                const newContainerX = clientX - containerRect.width / 2;\n                const newContainerY = clientY - containerRect.height / 2;\n\n                // Clamp joystick container to screen bounds\n                joystickContainer.style.left = `${Math.max(0, Math.min(canvas.width - containerRect.width, newContainerX))}px`;\n                joystickContainer.style.top = `${Math.max(0, Math.min(canvas.height - containerRect.height, newContainerY))}px`;\n\n                // Set joystick base and knob to center of container\n                joystick.startX = clientX;\n                joystick.startY = clientY;\n                joystick.currentX = clientX;\n                joystick.currentY = clientY;\n\n                joystickBase.style.left = `${joystickContainer.offsetWidth / 2 - joystickBase.offsetWidth / 2}px`;\n                joystickBase.style.top = `${joystickContainer.offsetHeight / 2 - joystickBase.offsetHeight / 2}px`;\n                joystickKnob.style.left = `${joystickContainer.offsetWidth / 2 - joystickKnob.offsetWidth / 2}px`;\n                joystickKnob.style.top = `${joystickContainer.offsetHeight / 2 - joystickKnob.offsetHeight / 2}px`;\n\n                moveJoystick(e); // Initial move to set knob position\n            }\n\n            function moveJoystick(e) {\n                if (!joystick.active || gameMode === 'edit') return;\n                e.preventDefault();\n\n                const clientX = e.touches ? e.touches[0].clientX : e.clientX;\n                const clientY = e.touches ? e.touches[0].clientY : e.clientY;\n\n                let dx = clientX - joystick.startX;\n                let dy = clientY - joystick.startY;\n                const distance = Math.sqrt(dx * dx + dy * dy);\n\n                if (distance > joystick.maxRadius) {\n                    const angle = Math.atan2(dy, dx);\n                    dx = Math.cos(angle) * joystick.maxRadius;\n                    dy = Math.sin(angle) * joystick.maxRadius;\n\n                    // Move joystick base if finger moves beyond max radius\n                    joystick.startX = clientX - dx;\n                    joystick.startY = clientY - dy;\n\n                    joystickContainer.style.left = `${joystick.startX - joystickContainer.offsetWidth / 2}px`;\n                    joystickContainer.style.top = `${joystick.startY - joystickContainer.offsetHeight / 2}px`;\n                }\n\n                joystick.currentX = joystick.startX + dx;\n                joystick.currentY = joystick.startY + dy;\n\n                joystickKnob.style.left = `${joystick.currentX - joystickContainer.offsetLeft - joystickKnob.offsetWidth / 2}px`;\n                joystickKnob.style.top = `${joystick.currentY - joystickContainer.offsetTop - joystickKnob.offsetHeight / 2}px`;\n\n                // Calculate player direction\n                if (distance > joystick.deadZone) {\n                    player.directionX = dx / joystick.maxRadius;\n                    player.directionY = dy / joystick.maxRadius;\n                } else {\n                    player.directionX = 0;\n                    player.directionY = 0;\n                }\n            }\n\n            function stopJoystick() {\n                joystick.active = false;\n                joystickContainer.style.display = 'none';\n                player.directionX = 0;\n                player.directionY = 0;\n            }\n\n            canvas.addEventListener('touchstart', startJoystick, { passive: false });\n            canvas.addEventListener('touchmove', moveJoystick, { passive: false });\n            canvas.addEventListener('touchend', stopJoystick);\n            canvas.addEventListener('touchcancel', stopJoystick);\n\n            // Mouse events for desktop joystick simulation (if needed, but keyboard is primary)\n            // For now, only keyboard for desktop.\n        }\n\n        function updatePlayerDirectionFromKeyboard() {\n            player.directionX = 0;\n            player.directionY = 0;\n\n            if (keys['a'] || keys['arrowleft']) player.directionX = -1;\n            if (keys['d'] || keys['arrowright']) player.directionX = 1;\n            if (keys['w'] || keys['arrowup']) player.directionY = -1;\n            if (keys['s'] || keys['arrowdown']) player.directionY = 1;\n\n            // Normalize diagonal movement\n            if (player.directionX !== 0 && player.directionY !== 0) {\n                const length = Math.sqrt(player.directionX * player.directionX + player.directionY * player.directionY);\n                player.directionX /= length;\n                player.directionY /= length;\n            }\n        }\n\n        // --- Main Game Loop ---\n        function gameLoop(timestamp) {\n            const deltaTime = Math.min(MAX_DELTA_TIME, (timestamp - lastTimestamp) / 1000);\n            lastTimestamp = timestamp;\n\n            // Clear canvas\n            ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n            // Draw background\n            const backgroundAsset = assetCache['background_food_cart'];\n            if (backgroundAsset) {\n                const bgAspectRatio = backgroundAsset.width / backgroundAsset.height;\n                let bgWidth = canvas.width;\n                let bgHeight = canvas.width / bgAspectRatio;\n\n                if (bgHeight < canvas.height) { // If background is too short, scale by height\n                    bgHeight = canvas.height;\n                    bgWidth = canvas.height * bgAspectRatio;\n                }\n\n                const bgX = (canvas.width - bgWidth) / 2;\n                const bgY = (canvas.height - bgHeight) / 2;\n                ctx.drawImage(backgroundAsset, bgX, bgY, bgWidth, bgHeight);\n            }\n\n            if (gameMode === 'edit') {\n                // Edit mode: Disable gameplay, enable object dragging\n                // Objects are drawn by their handles in the overlay, but we still draw the base objects for context\n                player.draw();\n                burgerMachine.draw();\n                customerCounter.draw();\n            } else {\n                // Play mode: Full game logic, physics, controls\n                if (!joystick.active) {\n                    updatePlayerDirectionFromKeyboard();\n                }\n                player.update(deltaTime);\n                machines.forEach(m => m.update(deltaTime));\n                workers.forEach(w => w.update(deltaTime));\n                vipCars.forEach(car => car.update(deltaTime));\n                vipCars = vipCars.filter(car => !car.finished);\n\n                // Customer spawning\n                let spawnInterval = 3.5; // Spawn 1 customer every 3.5 seconds to achieve 2 every 7 seconds\n                const hasAttracter = workers.some(w => w.type === 'attracter');\n                if (hasAttracter) spawnInterval *= 0.7;\n\n                if (Date.now() - lastCustomerSpawnTime > spawnInterval * 1000) {\n                    spawnCustomer();\n                    lastCustomerSpawnTime = Date.now();\n                }\n\n                // VIP Spawning every 80 seconds\n                if (Date.now() - lastVIPSpawnTime > 80000) {\n                    vipCars.push(new VIPCar());\n                    lastVIPSpawnTime = Date.now();\n                }\n\n                // Update customer line positions\n                let lineIndex = 0;\n                customers.forEach(customer => {\n                    if (!customer.served) {\n                        customer.targetX = customerCounter.customerSpawnPoint.x;\n                        customer.targetY = customerCounter.customerSpawnPoint.y + lineIndex * (PLAYER_SIZE + 10);\n                        \n                        // Add some horizontal variance for \"working differently\" look\n                        if (customer.isVIP) {\n                             customer.targetX += (Math.sin(Date.now() / 1000) * 10);\n                        }\n                        \n                        lineIndex++;\n                    }\n                });\n\n                customers.forEach(customer => customer.update(deltaTime));\n                customers = customers.filter(customer => !customer.served || customer.despawnTimer < customer.despawnTime);\n                moneyPiles = moneyPiles.filter(pile => !pile.collected);\n\n                // Interactions\n                const playerBounds = player.getBounds();\n                const playerCenter = player.getCenter();\n\n                // Food interaction\n                collectFood();\n\n                // Customer Counter interaction\n                if (checkCollision(playerBounds, customerCounter.interactionZone)) {\n                    serveCustomer();\n                }\n\n                // Money collection\n                moneyPiles.forEach(pile => {\n                    const dist = Math.sqrt(Math.pow(playerCenter.x - pile.x, 2) + Math.pow(playerCenter.y - pile.y, 2));\n                    const magnet = MONEY_COLLECTION_RADIUS + (window.gameConfig.upgrades.magnetRange.level * 20);\n                    if (dist < magnet) {\n                        collectMoney(pile);\n                    }\n                });\n\n                // Draw game objects\n                machines.forEach(m => m.draw());\n                customerCounter.draw();\n                vipCars.forEach(car => car.draw()); // Draw VIP car\n                customers.forEach(customer => customer.draw());\n                moneyPiles.forEach(pile => pile.draw());\n                workers.forEach(w => w.draw());\n                player.draw();\n            }\n\n            requestAnimationFrame(gameLoop);\n        }\n\n        // --- Run Function (Entry Point) ---\n        async function run(mode) {\n            gameMode = mode;\n            const gameWorld = document.getElementById('game-world');\n            canvas = document.getElementById('game-canvas');\n            ctx = canvas.getContext('2d');\n\n            // Initialize display and resize handling\n            function resizeCanvas() {\n                canvas.width = GAME_WIDTH;\n                canvas.height = GAME_HEIGHT;\n                // Center canvas in game-world if game-world is larger\n                const scale = Math.min(gameWorld.clientWidth / GAME_WIDTH, gameWorld.clientHeight / GAME_HEIGHT);\n                canvas.style.width = `${GAME_WIDTH * scale}px`;\n                canvas.style.height = `${GAME_HEIGHT * scale}px`;\n                canvas.style.left = `${(gameWorld.clientWidth - GAME_WIDTH * scale) / 2}px`;\n                canvas.style.top = `${(gameWorld.clientHeight - GAME_HEIGHT * scale) / 2}px`;\n            }\n            resizeCanvas();\n            window.addEventListener('resize', resizeCanvas);\n\n            // Preload assets\n            const assetIds = [\n                'player_character_idle', 'player_character_walk_1', 'player_character_walk_2',\n                'burger_machine', 'customer_counter', 'burger_item', 'regular_customer',\n                'coin_pile', 'bill_pile', 'background_food_cart',\n                'sfx_collect_food', 'sfx_serve_customer', 'sfx_collect_money', 'sfx_upgrade',\n                'music_background_loop', 'fries_machine', 'fries_item', 'soda_machine', 'soda_item',\n                'worker_character_idle', 'vip_car', 'vip_customer'\n            ];\n            await preloadAssets(assetIds);\n\n            // Set money icon\n            const moneyIcon = document.getElementById('money-icon');\n            const coinPileAsset = lib.getAsset('coin_pile');\n            if (coinPileAsset) {\n                moneyIcon.src = coinPileAsset.url;\n            }\n\n            // Setup UI elements\n            const upgradePanel = document.getElementById('upgrade-panel');\n            const toggleUpgradePanelBtn = document.getElementById('toggle-upgrade-panel');\n\n            document.getElementById('toggle-upgrade-panel').onclick = () => {\n                upgradePanelOpen = !upgradePanelOpen;\n                upgradePanel.classList.toggle('open', upgradePanelOpen);\n                toggleUpgradePanelBtn.classList.toggle('open', upgradePanelOpen);\n                toggleUpgradePanelBtn.textContent = upgradePanelOpen ? '\u2b07\ufe0f' : '\u2b06\ufe0f';\n                if (upgradePanelOpen) updateUpgradeButtons();\n            };\n\n            // Show game parameters in both modes\n            lib.showGameParameters({\n                name: 'Game Settings',\n                params: {\n                    'Player Initial Speed': {\n                        key: 'player.initialSpeed', type: 'slider', min: 100, max: 300, step: 10,\n                        onChange: (value) => { window.gameConfig.player.initialSpeed = value; initializeGame(); }\n                    },\n                    'Player Carry Capacity': {\n                        key: 'player.initialCarryCapacity', type: 'slider', min: 1, max: 5, step: 1,\n                        onChange: (value) => { window.gameConfig.player.initialCarryCapacity = value; initializeGame(); }\n                    },\n                    'Speed Upgrade Cost Multiplier': {\n                        key: 'player.speedUpgradeCostMultiplier', type: 'slider', min: 1.2, max: 2.0, step: 0.05,\n                        onChange: (value) => { window.gameConfig.player.speedUpgradeCostMultiplier = value; updateUpgradeButtons(); }\n                    },\n                    'Capacity Upgrade Cost Multiplier': {\n                        key: 'player.capacityUpgradeCostMultiplier', type: 'slider', min: 1.2, max: 2.0, step: 0.05,\n                        onChange: (value) => { window.gameConfig.player.capacityUpgradeCostMultiplier = value; updateUpgradeButtons(); }\n                    },\n                    'Burger Production Time': {\n                        key: 'burgerMachine.initialProductionTime', type: 'slider', min: 2, max: 5, step: 0.1,\n                        onChange: (value) => { window.gameConfig.burgerMachine.initialProductionTime = value; initializeGame(); }\n                    },\n                    'Production Upgrade Cost Multiplier': {\n                        key: 'burgerMachine.productionUpgradeCostMultiplier', type: 'slider', min: 1.2, max: 2.0, step: 0.05,\n                        onChange: (value) => { window.gameConfig.burgerMachine.productionUpgradeCostMultiplier = value; updateUpgradeButtons(); }\n                    },\n                    'Customer Spawn Interval': {\n                        key: 'customer.customerSpawnInterval', type: 'slider', min: 3, max: 10, step: 0.5,\n                        onChange: (value) => { window.gameConfig.customer.customerSpawnInterval = value; }\n                    },\n                    'Base Money Per Customer': {\n                        key: 'customer.baseMoneyPerCustomer', type: 'slider', min: 10, max: 100, step: 5,\n                        onChange: (value) => { window.gameConfig.customer.baseMoneyPerCustomer = value; }\n                    },\n                    'Offline Earnings Rate': {\n                        key: 'offlineEarnings.offlineEarningsRate', type: 'slider', min: 0.1, max: 0.5, step: 0.05,\n                        onChange: (value) => { window.gameConfig.offlineEarnings.offlineEarningsRate = value; }\n                    }\n                }\n            });\n\n            // Initialize game state from config\n            initializeGame();\n\n            // Setup input listeners\n            setupInputListeners();\n\n            // Play background music\n            const musicSource = playSound('music_background_loop', true, 0.3); // Loop music, lower volume\n\n            if (mode === 'edit') {\n                document.getElementById('joystick-container').style.display = 'none';\n                document.getElementById('upgrade-panel').style.display = 'none';\n                document.getElementById('toggle-upgrade-panel').style.display = 'none';\n                document.getElementById('offline-earnings-modal').style.display = 'none';\n                document.getElementById('edit-mode-overlay').style.display = 'block';\n                setupEditMode();\n            } else {\n                document.getElementById('edit-mode-overlay').style.display = 'none';\n                document.getElementById('upgrade-panel').style.display = 'flex';\n                document.getElementById('toggle-upgrade-panel').style.display = 'flex';\n                // Show joystick container on mobile if needed, but startJoystick handles it\n                document.getElementById('joystick-container').style.display = 'none';\n\n                // Check for offline earnings only in play mode\n                await checkOfflineEarnings();\n            }\n\n            // Start game loop\n            requestAnimationFrame(gameLoop);\n        }\n    </script>\n</body>\n</html>", "game_config": "{\n  \"player\": {\n    \"x\": 300,\n    \"y\": 800,\n    \"startX\": 300,\n    \"startY\": 800,\n    \"initialSpeed\": 250,\n    \"initialCarryCapacity\": 2,\n    \"speedLevel\": 0,\n    \"capacityLevel\": 0,\n    \"baseSpeedUpgradeCost\": 100,\n    \"speedUpgradeCostMultiplier\": 1.5,\n    \"baseCapacityUpgradeCost\": 150,\n    \"capacityUpgradeCostMultiplier\": 1.6,\n    \"money\": 0\n  },\n  \"burgerMachine\": {\n    \"x\": 100,\n    \"y\": 400,\n    \"initialProductionTime\": 3,\n    \"productionLevel\": 0,\n    \"baseProductionUpgradeCost\": 120,\n    \"productionUpgradeCostMultiplier\": 1.4\n  },\n  \"customerCounter\": {\n    \"x\": 450,\n    \"y\": 400\n  },\n  \"customer\": {\n    \"customerSpawnInterval\": 5,\n    \"baseMoneyPerCustomer\": 85\n  },\n  \"offlineEarnings\": {\n    \"offlineEarningsRate\": 0.2\n  },\n  \"upgrades\": {\n    \"patienceLevel\": {\n      \"costMultiplier\": 1.4,\n      \"baseCost\": 300,\n      \"level\": 0,\n      \"name\": \"Customer Patience\"\n    },\n    \"friesProduction\": {\n      \"costMultiplier\": 1.4,\n      \"baseCost\": 300,\n      \"level\": 0,\n      \"name\": \"Fries Speed\"\n    },\n    \"customerMax\": {\n      \"name\": \"Max Customers\",\n      \"costMultiplier\": 1.7,\n      \"baseCost\": 300,\n      \"level\": 0\n    },\n    \"sodaLevel\": {\n      \"costMultiplier\": 2,\n      \"baseCost\": 1000,\n      \"level\": 0,\n      \"name\": \"Soda Machine (New)\"\n    },\n    \"burgerMaxStock\": {\n      \"costMultiplier\": 1.4,\n      \"baseCost\": 200,\n      \"level\": 0,\n      \"name\": \"Burger Stock Cap\"\n    },\n    \"magnetRange\": {\n      \"level\": 0,\n      \"costMultiplier\": 1.6,\n      \"baseCost\": 350,\n      \"name\": \"Magnet Range\"\n    },\n    \"cashCollectorCount\": {\n      \"level\": 0,\n      \"costMultiplier\": 2,\n      \"baseCost\": 800,\n      \"name\": \"Hire Cashier Staff\"\n    },\n    \"attracterCount\": {\n      \"costMultiplier\": 2.2,\n      \"baseCost\": 1000,\n      \"level\": 0,\n      \"name\": \"Hire Attracter\"\n    },\n    \"customerPay\": {\n      \"name\": \"Food Price\",\n      \"costMultiplier\": 1.6,\n      \"baseCost\": 400,\n      \"level\": 0\n    },\n    \"sodaProduction\": {\n      \"name\": \"Soda Speed\",\n      \"level\": 0,\n      \"costMultiplier\": 1.5,\n      \"baseCost\": 400\n    },\n    \"friesStock\": {\n      \"name\": \"Fries Stock Cap\",\n      \"costMultiplier\": 1.5,\n      \"baseCost\": 250,\n      \"level\": 0\n    },\n    \"serverSpeed\": {\n      \"name\": \"Waiter Speed\",\n      \"level\": 0,\n      \"costMultiplier\": 1.7,\n      \"baseCost\": 500\n    },\n    \"foodMakerSpeed\": {\n      \"name\": \"Chef Efficiency\",\n      \"level\": 0,\n      \"costMultiplier\": 1.7,\n      \"baseCost\": 600\n    },\n    \"serverCount\": {\n      \"costMultiplier\": 2.5,\n      \"baseCost\": 1500,\n      \"level\": 0,\n      \"name\": \"Hire Waiter Staff\"\n    },\n    \"offlineEarnings\": {\n      \"costMultiplier\": 1.8,\n      \"baseCost\": 500,\n      \"level\": 0,\n      \"name\": \"Offline Profits\"\n    },\n    \"customerSpawn\": {\n      \"name\": \"Spawn Speed\",\n      \"costMultiplier\": 1.5,\n      \"baseCost\": 250,\n      \"level\": 0\n    },\n    \"cashCollectorSpeed\": {\n      \"level\": 0,\n      \"costMultiplier\": 1.5,\n      \"baseCost\": 400,\n      \"name\": \"Cashier Speed\"\n    },\n    \"globalIncome\": {\n      \"costMultiplier\": 3,\n      \"baseCost\": 2000,\n      \"level\": 0,\n      \"name\": \"Empire Profits\"\n    },\n    \"serverCapacity\": {\n      \"costMultiplier\": 1.6,\n      \"baseCost\": 400,\n      \"level\": 0,\n      \"name\": \"Server Capacity\"\n    },\n    \"attracterPower\": {\n      \"costMultiplier\": 1.6,\n      \"baseCost\": 500,\n      \"level\": 0,\n      \"name\": \"Attracter Power\"\n    },\n    \"vipChance\": {\n      \"name\": \"VIP Arrival Rate\",\n      \"costMultiplier\": 2,\n      \"baseCost\": 1500,\n      \"level\": 0\n    },\n    \"foodMakerCount\": {\n      \"name\": \"Hire Chef Staff\",\n      \"level\": 0,\n      \"costMultiplier\": 2.5,\n      \"baseCost\": 1200\n    },\n    \"tipChance\": {\n      \"level\": 0,\n      \"costMultiplier\": 1.6,\n      \"baseCost\": 400,\n      \"name\": \"Tip Chance\"\n    },\n    \"tipValue\": {\n      \"name\": \"Tip Amount\",\n      \"level\": 0,\n      \"costMultiplier\": 1.5,\n      \"baseCost\": 300\n    },\n    \"sodaStock\": {\n      \"name\": \"Soda Stock Cap\",\n      \"level\": 0,\n      \"costMultiplier\": 1.6,\n      \"baseCost\": 300\n    },\n    \"friesLevel\": {\n      \"costMultiplier\": 1.8,\n      \"baseCost\": 500,\n      \"level\": 0,\n      \"name\": \"Fries Machine (New)\"\n    }\n  },\n  \"machines\": {\n    \"fries\": {\n      \"level\": 0,\n      \"initialProductionTime\": 4,\n      \"maxStock\": 3,\n      \"active\": false,\n      \"y\": 600,\n      \"x\": 100\n    },\n    \"soda\": {\n      \"x\": 100,\n      \"active\": false,\n      \"y\": 800,\n      \"maxStock\": 2,\n      \"initialProductionTime\": 5,\n      \"level\": 0\n    }\n  }\n}", "game_design_document": "## Core Game Loop and Mechanics\n\n\"Street Food Empire\" is a highly addictive 2D mobile idle tycoon game where players build and manage a food business from a small burger stand to a massive culinary empire. The core gameplay revolves around a simple, satisfying loop:\n\n1.  **Produce Food:** The player character (or later, staff) walks to a food machine (e.g., Burger Machine) to automatically collect food. Food items stack visually above the character's head.\n2.  **Serve Customers:** The character walks near a customer at a counter to automatically serve them the collected food.\n3.  **Collect Money:** Served customers leave piles of money (coins/bills) on the ground. The character walks over these piles to automatically collect them.\n4.  **Expand & Upgrade:** Collected money is automatically added to the player's balance. Players use this money to unlock new food machines, hire staff, upgrade existing machines and staff, and expand into new restaurant areas.\n5.  **Repeat:** The cycle continues, leading to exponential growth and automation.\n\nThe game emphasizes constant visual feedback, satisfying sound effects, and frequent unlocks to create an \"easy to learn, impossible to put down\" experience.\n\n**Win Condition:** There is no explicit \"win\" condition in the traditional sense. The game is designed for continuous progression and expansion, with the ultimate goal being to build the largest, most automated food empire possible. Players can set personal goals, such as unlocking all restaurant stages or fully upgrading all machines and staff.\n\n**Lose Condition:** There is no explicit \"lose\" condition. The game is designed to be low-stress and always progressing. Even if the player leaves, offline earnings ensure they return to a growing business.\n\n## Starting Scope\n\nThe initial implementation will focus on the very first stage of the game: the **Small Food Cart** with a single **Burger Stand**. This will allow for a polished and complete core loop before expanding.\n\n**Initial Features:**\n\n*   **Player Character:** Controllable via virtual joystick.\n*   **Burger Machine:** Produces burgers.\n*   **Customer Counter:** Where customers appear and are served.\n*   **Cash Collection Area:** Where money piles appear after customers are served.\n*   **Basic Customer:** A single type of regular customer.\n*   **Money System:** Collecting money, displaying balance.\n*   **Upgrade System (Limited):**\n    *   Upgrade Burger Machine production speed.\n    *   Upgrade Player Character's walking speed.\n    *   Upgrade Player Character's carry capacity.\n*   **Basic UI:** Money display, upgrade buttons for the initial features.\n*   **Offline Earnings:** A simple calculation for earnings while away.\n*   **Art Style:** Implement the colorful cartoon art style for all initial assets.\n*   **Sound Effects:** Satisfying sounds for food collection, serving, and money collection.\n\n**Exclusions for Starting Scope (to be added in future iterations):**\n\n*   Additional food machines (Fries, Soda, etc.)\n*   Staff system (Food Collectors, Servers, Managers)\n*   Multiple restaurant stages (Street Stall, Food Truck, etc.)\n*   Different customer types (Families, VIPs)\n*   Advanced upgrade types (Customer Patience, Tip Bonuses)\n*   Rewarded Ads integration\n*   Celebration animations for unlocks (basic visual feedback will suffice initially)\n\n## Game Perspective\n\n**Top-down:** The game will use a top-down perspective, allowing players to clearly see the entire restaurant layout, character movement, machines, customers, and money piles. This perspective is ideal for managing a tycoon-style game and fits well with the virtual joystick control scheme.\n\n## Game World and Level Design\n\nThe initial game world will be a single, fixed-screen area representing the \"Small Food Cart\" stage.\n\n**Layout:**\n\n*   The screen will be a rectangular area (720x1280 pixels, portrait).\n*   The **Burger Machine** will be positioned on the left side of the screen.\n*   The **Customer Counter** will be positioned on the right side of the screen.\n*   A clear path will exist between the Burger Machine and the Customer Counter for the player character to traverse.\n*   The **Cash Collection Area** will be directly in front of the Customer Counter, where money piles will spawn.\n*   A small, simple background representing a street food cart environment (e.g., a simple street texture, a few decorative elements like a trash can or lamppost in the background).\n\n**Interaction Zones:**\n\n*   **Burger Machine Zone:** When the player character enters this zone, they automatically start collecting burgers.\n*   **Customer Counter Zone:** When the player character enters this zone with food, they automatically serve customers.\n*   **Cash Collection Zone:** When the player character enters this zone, they automatically collect any money piles present.\n\n## Edit Mode Design\n\nFor the initial scope, Edit Mode will focus on modifying core game parameters to allow players to customize their experience.\n\n**Editable Properties:**\n\n*   **Player Character:**\n    *   `initialSpeed`: Starting walking speed (e.g., 100-300 pixels/second).\n    *   `initialCarryCapacity`: Starting number of food items the player can carry (e.g., 1-5).\n    *   `speedUpgradeCostMultiplier`: How much the cost of speed upgrades increases (e.g., 1.2-2.0).\n    *   `capacityUpgradeCostMultiplier`: How much the cost of capacity upgrades increases (e.g., 1.2-2.0).\n*   **Burger Machine:**\n    *   `initialProductionTime`: Time to produce one burger (e.g., 2-5 seconds).\n    *   `productionUpgradeCostMultiplier`: How much the cost of production upgrades increases (e.g., 1.2-2.0).\n*   **Customer System:**\n    *   `customerSpawnInterval`: Time between new customers appearing (e.g., 3-10 seconds).\n    *   `baseMoneyPerCustomer`: Amount of money a regular customer pays (e.g., 10-50).\n*   **Offline Earnings:**\n    *   `offlineEarningsRate`: Percentage of active earnings generated offline per minute (e.g., 0.1-0.5).\n\n**Constraints:**\n\n*   All numerical values will have reasonable minimum and maximum limits to prevent game-breaking configurations (e.g., speed cannot be 0, costs cannot be negative).\n*   Cost multipliers should always be greater than 1 to ensure progressive scaling.\n\n## Control Design\n\n**Single Virtual Joystick:**\n\n*   **Mobile (Touch):** A single virtual joystick will appear on the left side of the screen where the player first touches.\n    *   The joystick will have a `base position` (where the touch began), a `max radius` (e.g., 80 pixels), and a `dead zone` (e.g., 10 pixels).\n    *   If the finger moves farther than the `max radius`, the joystick base MUST be moved so that the finger is always at the `max radius` from the base. This ensures the knob stays under the finger while allowing full range of motion.\n    *   The joystick will output an analog vector (`-1` to `+1` for X and Y) representing the player's desired movement direction.\n*   **Desktop (Keyboard/Mouse):**\n    *   **Movement:** WASD or Arrow Keys will control the player character's movement.\n        *   `W` or `Up Arrow`: Move Up\n        *   `A` or `Left Arrow`: Move Left\n        *   `S` or `Down Arrow`: Move Down\n        *   `D` or `Right Arrow`: Move Right\n    *   **Interaction:** All interactions (collecting food, serving, collecting money) are automatic when the character is within the respective interaction zones. No mouse clicks or additional key presses are required for core gameplay.\n\n**Interaction Logic:**\n\n*   The player character automatically performs actions when their collision box overlaps with the interaction zones of machines, customers, or money piles.\n*   **Food Collection:** If the player is near a Burger Machine and has less than their `carryCapacity`, they will automatically collect a burger when available.\n*   **Serving Customers:** If the player is near a Customer Counter, has food, and a customer is waiting, they will automatically serve the customer.\n*   **Money Collection:** If the player is near a money pile, they will automatically collect it.\n\n**Simplicity:** The control scheme is designed to be extremely simple and intuitive, allowing for one-handed play on mobile and minimal input on desktop, aligning with the \"easy to learn\" philosophy.\n\n## Win/Lose Conditions\n\nAs an idle tycoon game, there are no traditional win or lose conditions. The game is designed for continuous progression and growth. Players set their own goals, such as unlocking all restaurant stages, fully automating their business, or achieving a certain level of income. The game is designed to be relaxing and always moving forward.\n\n## Art Style and Visual Theme\n\n**Bright Cartoon Visuals:** The game will feature a vibrant, cheerful, and appealing cartoon art style.\n\n*   **Characters:** Cute, stylized, and slightly chibi-like character designs for the player and customers. Smooth 2D animations for walking, collecting, and serving. The player character will visually stack food items above their head.\n*   **Environment:** Clean, simple, and colorful backgrounds for each restaurant stage. The initial \"Small Food Cart\" will have a charming, slightly rustic street vendor feel.\n*   **Machines:** Distinctive, easily recognizable designs for each food machine (e.g., a classic burger grill, a soda dispenser). Machines should show subtle visual improvements or animations when upgraded (e.g., sparkling, faster-moving parts).\n*   **Food Items:** Appetizing, stylized representations of food (burgers, fries, soda).\n*   **Money:** Visually appealing piles of coins and bills that scatter on the ground and are collected with a satisfying \"pop\" animation. Floating income numbers will appear above collected money.\n*   **UI:** Clean, mobile-friendly interface with large, clear buttons and text. Icons for upgrades should be intuitive.\n*   **Color Palette:** Predominantly warm and inviting colors (reds, yellows, oranges) with complementary greens and blues to create a lively atmosphere.\n\n## Dynamic Text\n\n`lib.llm()` will not be used in the initial scope. All text (UI labels, upgrade descriptions, offline earnings messages) will be static and pre-defined.", "persistent_storage_config": null, "game_metadata": {"game_id": "01KV8CW6QZ7AJHADFW32904KPC", "title": "Street Food Empire \u2013 Idle Run Tycoon", "description": "Become the ultimate street food mogul in this addictive 2D idle tycoon game! Use a simple virtual joystick to control your character, automatically collecting food, serving customers, and grabbing cash to expand your culinary empire.", "thumbnail_url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/thumbnail_5761cc7e-2804-4e45-bee5-c5791b2a3f19.webp", "created_at": 1781619658, "updated_at": 1781694478548.1467, "art_style": null}, "task_plan": null}