{"asset_map": {"shopper_casual_male": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/shopper_casual_male_5bd6925e-0d9c-42d1-89bd-7988c75a34b3.webp", "type": "image", "prompt": "Generate a full-body, front-facing cartoon illustration of a friendly male shopper wearing a t-shirt and jeans. Use a clean art style with flat colors and subtle shading. The character should have a neutral expression and a standing posture suitable for a mobile game. Transparent background, high resolution, game-ready asset.", "description": "Casual male shopper character for the security checkpoint", "subtype": "character", "aspect_ratio": [509, 1319], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "shopper_casual_female": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/shopper_casual_female_1732bd36-0b4c-4e37-b17e-2f0a01a2d548.webp", "type": "image", "prompt": "Generate a full-body, front-facing cartoon illustration of a friendly female shopper wearing a blouse and pants. Match the art style, clean lines, and flat shading of shopper_casual_male. Neutral expression, standing posture. Transparent background, high resolution, professional mobile game character design.", "description": "Casual female shopper character for the security checkpoint", "subtype": "character", "aspect_ratio": [470, 1240], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "shopper_business_male": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/shopper_business_male_0bea553f-61ef-400c-ba0a-e036611e7008.webp", "type": "image", "prompt": "Generate a full-body, front-facing cartoon illustration of a male shopper in a professional suit and tie. Maintain the exact same art style and proportions as shopper_casual_male and shopper_casual_female. Friendly but professional neutral expression. Transparent background, game-ready sprite.", "description": "Business male shopper character in a suit", "subtype": "character", "aspect_ratio": [481, 1243], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "shopper_teen": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/shopper_teen_9adbffb2-d4eb-432b-a1c9-5f172e383bfc.webp", "type": "image", "prompt": "Generate a full-body, front-facing cartoon illustration of a teenage shopper wearing a hoodie and jeans. Match the clean flat-color illustration style of shopper_casual_male. Neutral expression, standing upright. Transparent background, polished mobile game asset.", "description": "Teenage shopper character in a hoodie", "subtype": "character", "aspect_ratio": [544, 1248], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "item_smartphone": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/item_smartphone_7579de55-a499-45f3-b9b4-2a11defee09d.webp", "type": "image", "prompt": "Create a simple, stylized flat icon of a modern smartphone viewed from the front. Clean rectangular design with a visible screen, minimalist aesthetic. Matches a cartoon mobile game style. Transparent background, high quality icon.", "description": "SAFE item: A modern smartphone icon", "subtype": "object", "aspect_ratio": [521, 865], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "item_keys": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/item_keys_6ca4e2b3-24f3-4180-8b07-aab79c9b103a.webp", "type": "image", "prompt": "Create a stylized flat icon of a keyring with 2-3 keys. Simple, recognizable silhouette with subtle shading. Consistent with the smartphone icon style. Transparent background, clean vector-like cartoon style.", "description": "SAFE item: A keyring with keys", "subtype": "object", "aspect_ratio": [606, 776], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "item_wallet": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/item_wallet_d3ead367-9fdf-4ac5-af00-3c256bf202fe.webp", "type": "image", "prompt": "Create a stylized flat icon of a closed bi-fold wallet viewed from a slight angle. Clean silhouette, subtle shading, cartoon aesthetic. Match the style of item_smartphone. Transparent background.", "description": "SAFE item: A bifold wallet icon", "subtype": "object", "aspect_ratio": [772, 647], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "item_knife": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/item_knife_5b9681d2-7369-420a-b1dd-3b1e0e89aca1.webp", "type": "image", "prompt": "Create a stylized icon of a pocket knife with the blade visible. Recognizable as contraband weapon but in a non-graphic cartoon style. Slightly more angular lines than safe items, flat colors. Transparent background.", "description": "CONTRABAND item: A stylized pocket knife", "subtype": "object", "aspect_ratio": [874, 853], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "item_drugs": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/item_drugs_e3a2260e-6b59-48d0-9e06-aec5fed0e0ee.webp", "type": "image", "prompt": "Create a stylized icon of a small plastic baggie with pills or powder inside. Suggestive of contraband without being explicit. Clean cartoon style, flat colors, transparent background.", "description": "CONTRABAND item: A small bag of suspicious contents", "subtype": "object", "aspect_ratio": [474, 535], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "item_gun": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/item_gun_334c48f6-4668-4d0b-b7e3-990d9a8daa11.webp", "type": "image", "prompt": "Create a stylized profile view of a handgun. Simplified, cartoon-style firearm, clearly recognizable as a weapon but not realistic. Angular silhouette, flat shading, transparent background.", "description": "CONTRABAND item: A stylized handgun", "subtype": "object", "aspect_ratio": [803, 503], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "scanner_frame": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/scanner_frame_ad2f26cd-9521-4f9d-8de5-a3a1b41a0443.webp", "type": "image", "prompt": "Generate a modern, high-tech security scanner archway viewed from the front. Sleek metallic design with indicator lights and technical panels. The center of the archway must be empty/transparent to allow characters to walk through. Metallic textures, clean lines, professional security tech aesthetic. Transparent background.", "description": "High-tech security scanner archway frame", "subtype": "ui", "aspect_ratio": [707, 906], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "background_checkpoint": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/background_checkpoint_751ea4f4-3d4c-4987-bdbe-5500912dbea3.webp", "type": "image", "prompt": "Generate a 9:16 tall image of a modern, clean mall security checkpoint. Professional atmosphere with muted grays and blues. Include subtle floor tiles, wall panels, and minimalist security signage. Perspective should be a front-on view of the security zone. No people or moving objects, clean and polished background illustration.", "description": "Modern mall security checkpoint environment", "subtype": "background", "aspect_ratio": [1024, 1536], "compression_suggestion": "original", "duration_secs": null, "loop": null, "animation": null}, "sfx_scanner_rotate": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/sfx_scanner_rotate_9c4e816c-25c6-4e72-9fc1-24cbe79dd9b8.mp3", "type": "audio", "prompt": "Short, high-tech electronic 'whoosh' with a subtle 'beep-sweep' tail. Clean, crisp, futuristic UI sound for a digital scanner. Fast attack, quick decay, 0.5 seconds, suggests advanced technology cycling through modes.", "description": "High-tech electronic whoosh for switching scanner filter modes.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 1.04, "loop": false, "animation": null}, "sfx_item_detected_safe": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/sfx_item_detected_safe_d8fdf89b-6f51-4153-9aa5-15cb5ebbd790.mp3", "type": "audio", "prompt": "Pleasant, high-pitched electronic 'ping' or 'ding'. Reassuring and positive tone, clean sine-wave quality. Soft attack, short decay, suitable for a successful scan result in a security app.", "description": "Positive electronic ping for safe item detection.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 2.0, "loop": false, "animation": null}, "sfx_item_detected_contraband": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/sfx_item_detected_contraband_0f8b28e6-a7e1-47cd-9fc3-cf4307298551.mp3", "type": "audio", "prompt": "Electronic alert 'buzz' or double-pulse warning beep. Urgent but not distorted. Mid-range frequency, clear digital texture, suggests a 'danger' or 'contraband detected' notification for a UI.", "description": "Urgent electronic alert for contraband detection.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 1.72, "loop": false, "animation": null}, "sfx_decision_correct": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/sfx_decision_correct_25e6971c-c9c6-44b1-9848-51b805b9bc12.mp3", "type": "audio", "prompt": "Satisfying success sound, modern electronic 'cha-ching' combined with a bright melodic chime. Uplifting, rewarding, and encouraging. High-fidelity, clear resonance, roughly 0.8 seconds long.", "description": "Satisfying success chime for a correct player judgment.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 1.29, "loop": false, "animation": null}, "sfx_decision_wrong": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/sfx_decision_wrong_f7c17afb-51e1-4f1a-82cd-ca03ffdcb12a.mp3", "type": "audio", "prompt": "Negative digital buzzer or 'error' sound. Two-tone descending sequence, slightly dissonant but clean. Communicates a mistake without being harsh or annoying. Electronic 'wrong answer' feedback.", "description": "Negative buzzer sound for an incorrect player judgment.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 0.99, "loop": false, "animation": null}, "sfx_game_over": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/sfx_game_over_5f9b2018-a215-4212-878c-0f29690e3fd2.mp3", "type": "audio", "prompt": "Descending electronic synth tones, 'power down' effect. Low-frequency sweep ending in a soft digital thud. Clearly signals finality and the end of a session, tech-themed arcade style.", "description": "Finality sound effect for the game over screen.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 2.94, "loop": false, "animation": null}, "music_gameplay_loop": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/music_gameplay_loop_05bd92c7-fb68-4c95-a79b-82740807f5a5.mp3", "type": "audio", "prompt": "Upbeat, moderately-paced synthwave track, 120 BPM. Clean electronic drums, steady driving bassline, and high-tech pluck melodies. Modern 'mall security' or 'high-tech checkpoint' atmosphere. Professional, focused, and rhythmic. Instrumental only, seamless loop, consistent energy level for arcade gameplay.", "description": "Main gameplay theme music with a focused, upbeat electronic vibe.", "subtype": "music", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 30.0, "loop": true, "animation": null}, "background_mall_entrance": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/background_mall_entrance_246ed3db-1f30-4593-aa95-3e77a2c1d0ae.webp", "type": "image", "prompt": "Generate a 9:16 tall image of a modern, slightly futuristic mall entrance seen from an interior perspective. The scene features large glass doors, polished reflective floors, and subtle neon lighting accents. Clean, professional quality, slightly stylized aesthetic matching the modern mall setting. The view should be looking out toward the entrance, creating an open and bright atmosphere suitable for a game background.", "description": "A modern, slightly futuristic mall entrance background for the security checkpoint screen.", "subtype": "background", "aspect_ratio": [1024, 1536], "compression_suggestion": "original", "duration_secs": null, "loop": null, "animation": null}, "sfx_alarm_siren": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/sfx_alarm_siren_0e74f054-c173-4a61-8a65-a02dcdf8645c.mp3", "type": "audio", "prompt": "High-pitched repeating electronic mall security alarm. Urgent rhythmic wail with a sharp sawtooth wave texture. Clear, loud, and authoritative emergency signal. Professional security system sound, non-musical, clean digital audio, designed to loop seamlessly.", "description": "Urgent mall security alarm siren for emergency or security breach signaling.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 3.0, "loop": true, "animation": null}, "item_water_bottle": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/item_water_bottle_0adf96e0-9dfe-410a-9211-a590482a82e2.webp", "type": "image", "prompt": "Generate a professional game icon of a simple, clear plastic water bottle with a blue cap. Clean flat icon style with a clear silhouette, high-quality 2D digital art, transparent background. Match the visual style and perspective of item_smartphone and item_keys.", "description": "SAFE item: A clean, clear plastic water bottle icon for the security scanner.", "subtype": "object", "aspect_ratio": [414, 985], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "item_sunglasses": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/item_sunglasses_63e1e4cb-a807-467b-846e-8da95ed2607f.webp", "type": "image", "prompt": "Generate a flat icon of classic aviator sunglasses for a mall security scanner game. Use a clean, simple silhouette with dark lenses and a thin frame. Professional game-ready asset, flat design style, high resolution, transparent background. Ensure the visual style matches the modern, polished look of item_smartphone and item_water_bottle.", "description": "A pair of sunglasses icon for the mall security scanner, categorized as a safe item.", "subtype": "object", "aspect_ratio": [693, 284], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "item_headphones": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/item_headphones_efccaa22-8db9-4e49-a0ff-9bc299bf9290.webp", "type": "image", "prompt": "Generate a flat icon of a pair of classic over-ear headphones with a headband. Design it with a simple, clear silhouette and a clean aesthetic suitable for a game HUD. Use a professional, modern style with a transparent background to match the existing game items like 'item_smartphone' and 'item_sunglasses'. High resolution, 2D vector-like quality.", "description": "A pair of over-ear headphones icon for the security scanner.", "subtype": "object", "aspect_ratio": [650, 610], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "item_explosives": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/item_explosives_9585212f-dcb0-42f6-9839-a01c62f96d5c.webp", "type": "image", "prompt": "Generate a high-quality, professional game icon of a classic cartoon bomb. The bomb should be a round black sphere with a sparkling lit fuse. Style it as a clean, flat icon with a simple silhouette and bold colors, ensuring it matches the aesthetic of the existing game items like 'item_knife' and 'item_gun'. High resolution, detailed textures, transparent background.", "description": "CONTRABAND item: A classic cartoon bomb with a lit fuse icon", "subtype": "object", "aspect_ratio": [562, 696], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "item_brass_knuckles": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/item_brass_knuckles_78e10462-a4e5-4e88-9c13-7f4509bed9bd.webp", "type": "image", "prompt": "Generate a flat icon of classic metallic brass knuckles. Use a metallic gold or brass color palette. The design should be a clean, simple silhouette with a professional game-ready look. Transparent background, high contrast, matching the flat icon style of existing items like item_knife and item_gun.", "description": "Contraband brass knuckles weapon icon for the security scanner", "subtype": "object", "aspect_ratio": [1031, 515], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "item_book": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/item_book_af4398eb-3612-4803-9c7a-7d10536a5809.webp", "type": "image", "prompt": "Generate a clean, flat icon of a simple closed hardcover book, viewed from a slight 3/4 angle. Use a warm brown and red color palette. Professional game UI style with a clear, bold silhouette and polished finish. High resolution, transparent background, matching the clean aesthetic of item_smartphone and item_wallet.", "description": "A simple closed hardcover book icon used as a safe item in the security scanner.", "subtype": "object", "aspect_ratio": [1024, 792], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "sfx_contraband_alert": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/sfx_contraband_alert_ec9f68e8-d7b9-451e-82b2-f410a012d3c7.mp3", "type": "audio", "prompt": "A sharp, aggressive electronic sting sound effect for a high-tech security scanner. Features a harsh digital alarm blip with a rapid descending pitch warble. Urgent and tense threat-detection alert, 120bpm rhythmic intensity, high-frequency digital distortion, one-shot with a clean sudden cutoff. No background noise, non-musical, sci-fi interface style.", "description": "Sharp electronic alarm sting for detecting dangerous contraband items.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 2.0, "loop": false, "animation": null}, "sfx_contraband_alert_v2": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/sfx_contraband_alert_v2_181ee1c2-6d9c-4f4d-a7c4-eec66c668c82.mp3", "type": "audio", "prompt": "Harsh, deep electronic buzzer sound effect. An aggressive 'BZZZT!' klaxon with a distorted, biting texture. The sound should feel urgent, industrial, and threatening, signaling a serious security breach. High-frequency buzz combined with a low-end mechanical growl. Short decay, high impact, designed for immediate player feedback in a high-stakes security context.", "description": "An aggressive and jarring electronic buzzer used to signal the detection of highly dangerous contraband.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 1.16, "loop": false, "animation": null}, "shopper_punk_female": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/shopper_punk_female_7cc4394a-ba6b-4bc1-9962-c242c6d4408f.webp", "type": "image", "prompt": "Generate a full-body, front-facing illustration of a punk female shopper character. She features colorful dyed hair, a black leather jacket with silver studs, ripped denim jeans, and heavy combat boots. Clean, polished cartoon art style consistent with a modern game aesthetic. The character should be standing on a transparent background, high resolution, detailed professional quality, matching the style of shopper_casual_female.", "description": "A punk female shopper character with colorful hair and a leather jacket for the security checkpoint.", "subtype": "character", "aspect_ratio": [515, 1248], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "shopper_elderly_male": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/shopper_elderly_male_9b4b2f25-9ab2-4147-aff8-653b2de45703.webp", "type": "image", "prompt": "Generate a full-body picture of an elderly male shopper standing illustration. He is wearing a classic sweater vest, trousers, and a flat cap, and is holding a walking cane. Use a clean, polished, stylized cartoon art style. Front-facing character with a neutral expression. Ensure the art style, line work, and coloring match the existing characters 'shopper_casual_male' and 'shopper_punk_female'. High resolution, professional quality, transparent background.", "description": "An elderly male shopper character with a cane, matching the game's cartoon style.", "subtype": "character", "aspect_ratio": [502, 1241], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "shopper_tourist_female": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/shopper_tourist_female_50419388-8eb2-4fab-bdae-36a8ee4b85e0.webp", "type": "image", "prompt": "Generate a full-body, front-facing illustration of a female tourist shopper. She is wearing a large sun hat, sunglasses, a bright floral-patterned shirt, and casual shorts, carrying a large tote bag. Use a clean, stylized cartoon art style consistent with a modern game. High resolution, professional quality, transparent background.", "description": "A female tourist shopper character with a floral shirt and sun hat for the security checkpoint.", "subtype": "character", "aspect_ratio": [535, 1246], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "shopper_athlete_female": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/shopper_athlete_female_f9f947e1-3f90-4af9-b7b0-ff77549e4e21.webp", "type": "image", "prompt": "Generate a full-body, front-facing illustration of a female athlete shopper. She is wearing a modern sports top, yoga leggings, and sleek running shoes, and is carrying a gym bag over her shoulder. The style must be a clean, polished, stylized cartoon art style with bold outlines, matching the aesthetic of shopper_casual_female and shopper_punk_female. Transparent background, high resolution, professional game asset.", "description": "A full-body female athlete shopper in fitness gear for the security checkpoint.", "subtype": "character", "aspect_ratio": [483, 1246], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "shopper_delivery_person": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/shopper_delivery_person_f2aa8062-1822-4df9-a079-4b225311b5a9.webp", "type": "image", "prompt": "Generate a full-body character illustration of a delivery person for a mall security game. The character is wearing a professional delivery uniform with a cap and holding a clipboard. Use a clean, vibrant cartoon art style consistent with a modern mobile game. Front-view, standing pose, neutral but friendly expression. High resolution, professional quality, transparent background.", "description": "A delivery person shopper character wearing a uniform and carrying a clipboard for the security checkpoint.", "subtype": "character", "aspect_ratio": [602, 1238], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "shopper_goth_male": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/shopper_goth_male_8f932260-300d-44af-a4ca-7d508afd0e6a.webp", "type": "image", "prompt": "Generate a full-body, front-facing illustration of a goth male shopper character. He is wearing stylized black clothing, heavy combat boots, and silver chain accessories. Maintain a clean, polished cartoon style consistent with a modern mobile game. Standing pose, neutral expression, high resolution, professional quality, transparent background.", "description": "A goth male shopper character in black attire for the security checkpoint", "subtype": "character", "aspect_ratio": [559, 1243], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "sfx_contraband_alert_v3": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/sfx_contraband_alert_v3_ffdcbaf4-5b23-456e-8f36-631a0b74f42d.mp3", "type": "audio", "prompt": "A sharp, high-tech electronic alert chime. Sounds like a clean digital sonar ping or a sophisticated UI notification. High-pitched, crystalline, and pleasant, avoiding any harsh buzzing or distortion. Short, snappy attack with a smooth 0.5s decay. Modern sci-fi aesthetic, similar to a premium smartphone alert or a sleek futuristic terminal signal.", "description": "A professional, high-tech digital notification chime used for highlighting contraband items during scanning.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 0.76, "loop": false, "animation": null}, "icon_badge": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/icon_badge_65711a50-4ca6-44a6-8c83-361cf895b683.webp", "type": "image", "prompt": "Generate a high-tech golden security guard badge icon, shield-shaped with a centered five-pointed star. Polished metallic gold finish with sharp edges and subtle futuristic glowing accents. Professional UI icon style, clean lines, high resolution, game-ready asset, front-view, transparent background.", "description": "A polished golden security guard badge icon with a futuristic shield design.", "subtype": "ui", "aspect_ratio": [744, 843], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}}, "game_code": "<!DOCTYPE html>\n<html>\n<head>\n    <title>Mall Cop Scanner</title>\n    <link href=\"https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Inter:wght@400;600;700&display=swap\" rel=\"stylesheet\">\n    <style>\n        :root {\n            --transition-fast: 150ms;\n            --transition-normal: 300ms;\n            --transition-slow: 500ms;\n            --ease-out: cubic-bezier(0.0, 0, 0.2, 1);\n            --ease-bounce: cubic-bezier(0.68, -0.6, 0.32, 1.6);\n            --space-xs: 8px;\n            --space-sm: 16px;\n            --space-md: 24px;\n            --space-lg: 32px;\n            --space-xl: 48px;\n        }\n\n        body, html {\n            margin: 0;\n            padding: 0;\n            width: 100%;\n            height: 100%;\n            overflow: hidden;\n            touch-action: none;\n            -ms-touch-action: none;\n            font-family: 'Inter', sans-serif;\n            background: #1a1a2e;\n        }\n\n        #game-world {\n            position: relative;\n            width: 100%;\n            height: 100%;\n            overflow: hidden;\n        }\n\n        canvas {\n            display: block;\n            width: 100%;\n            height: 100%;\n        }\n    </style>\n</head>\n<body>\n    <div id=\"game-world\">\n        <canvas id=\"game-canvas\"></canvas>\n    </div>\n\n    <script>\n    /*\n     * MALL COP SCANNER\n     * A fast-paced arcade game where you operate a multi-layered scanner\n     * to detect contraband on shoppers entering a mall.\n     * Tap to rotate filters, identify items, then ALLOW or DENY.\n     * 3 mistakes = game over.\n     */\n    async function run(mode) {\n        const canvas = document.getElementById('game-canvas');\n        const ctx = canvas.getContext('2d');\n        const W = 720, H = 1280;\n\n        canvas.width = W;\n        canvas.height = H;\n\n        const config = window.gameConfig;\n\n        // Audio context\n        let audioCtx = null;\n        const audioBuffers = {};\n\n        function getAudioContext() {\n            if (!audioCtx) {\n                audioCtx = new (window.AudioContext || window.webkitAudioContext)();\n            }\n            return audioCtx;\n        }\n\n        function playSound(id, volume = 0.5) {\n            try {\n                const actx = getAudioContext();\n                if (actx.state === 'suspended') actx.resume();\n                const buffer = audioBuffers[id];\n                if (!buffer) return;\n                const source = actx.createBufferSource();\n                source.buffer = buffer;\n                const gain = actx.createGain();\n                gain.gain.value = volume;\n                source.connect(gain);\n                gain.connect(actx.destination);\n                if (id === 'music_gameplay_loop') {\n                    source.loop = true;\n                    musicSource = source;\n                    musicGain = gain;\n                }\n                source.start(0);\n            } catch(e) {}\n        }\n\n        let musicSource = null;\n        let musicGain = null;\n\n        function stopMusic() {\n            if (musicSource) {\n                try { musicSource.stop(); } catch(e) {}\n                musicSource = null;\n            }\n        }\n\n        // Asset loading\n        const imageCache = {};\n\n        async function loadImage(id) {\n            const asset = lib.getAsset(id);\n            if (!asset || asset.type !== 'image') return null;\n            return new Promise((resolve) => {\n                const img = new Image();\n                img.crossOrigin = 'anonymous';\n                img.onload = () => { imageCache[id] = img; resolve(img); };\n                img.onerror = () => resolve(null);\n                img.src = asset.url;\n            });\n        }\n\n        async function loadAudio(id) {\n            const asset = lib.getAsset(id);\n            if (!asset || asset.type !== 'audio') return;\n            try {\n                const actx = getAudioContext();\n                const response = await fetch(asset.url);\n                const arrayBuffer = await response.arrayBuffer();\n                const buffer = await actx.decodeAudioData(arrayBuffer);\n                audioBuffers[id] = buffer;\n            } catch(e) {}\n        }\n\n        // Preload all assets\n        const imageIds = ['shopper_casual_male', 'shopper_casual_female', 'shopper_business_male', 'shopper_teen',\n                         'shopper_punk_female', 'shopper_elderly_male', 'shopper_tourist_female', 'shopper_athlete_female',\n                         'shopper_delivery_person', 'shopper_goth_male',\n                         'item_smartphone', 'item_keys', 'item_wallet', 'item_knife', 'item_drugs', 'item_gun',\n                         'item_water_bottle', 'item_sunglasses', 'item_headphones', 'item_book',\n                         'item_explosives', 'item_brass_knuckles',\n                         'scanner_frame', 'background_mall_entrance', 'icon_badge'];\n        const audioIds = ['sfx_scanner_rotate', 'sfx_item_detected_safe', 'sfx_item_detected_contraband',\n                         'sfx_contraband_alert_v3',\n                         'sfx_decision_correct', 'sfx_decision_wrong', 'sfx_game_over', 'music_gameplay_loop', 'sfx_alarm_siren'];\n\n        await Promise.all([\n            ...imageIds.map(id => loadImage(id)),\n            ...audioIds.map(id => loadAudio(id))\n        ]);\n\n        // Game constants\n        const FILTERS = [\n            { name: 'X-RAY', color: 'rgba(0, 150, 255, 0.25)', reveals: ['item_knife', 'item_gun', 'item_keys', 'item_brass_knuckles', 'item_headphones'] },\n            { name: 'THERMAL', color: 'rgba(255, 100, 0, 0.25)', reveals: ['item_drugs', 'item_smartphone', 'item_explosives', 'item_water_bottle'] },\n            { name: 'ELECTRO-MAG', color: 'rgba(180, 0, 255, 0.25)', reveals: ['item_gun', 'item_smartphone', 'item_keys', 'item_sunglasses', 'item_brass_knuckles'] }\n        ];\n\n        const SAFE_ITEMS = ['item_smartphone', 'item_keys', 'item_wallet', 'item_water_bottle', 'item_sunglasses', 'item_headphones', 'item_book'];\n        const CONTRABAND_ITEMS = ['item_knife', 'item_drugs', 'item_gun', 'item_explosives', 'item_brass_knuckles'];\n        const SHOPPER_IDS = ['shopper_casual_male', 'shopper_casual_female', 'shopper_business_male', 'shopper_teen',\n                            'shopper_punk_female', 'shopper_elderly_male', 'shopper_tourist_female', 'shopper_athlete_female',\n                            'shopper_delivery_person', 'shopper_goth_male'];\n\n        const ITEM_POSITIONS = [\n            { x: -40, y: 30 },\n            { x: 30, y: 50 },\n            { x: -30, y: -20 },\n            { x: 20, y: -40 },\n            { x: 0, y: 60 }\n        ];\n\n        // Show game parameters\n        lib.showGameParameters({\n            name: 'Mall Cop Scanner',\n            params: {\n                'Starting Speed': { key: 'settings.startingSpeed', type: 'slider', min: 0.5, max: 2, step: 0.1 },\n                'Lives': { key: 'settings.lives', type: 'slider', min: 1, max: 5, step: 1 },\n                'Max Combo': { key: 'settings.maxCombo', type: 'slider', min: 2, max: 10, step: 0.5 },\n                'Items Per Shopper': { key: 'settings.itemsPerShopper', type: 'slider', min: 1, max: 5, step: 1 }\n            }\n        });\n\n        // Runtime state\n        let gameState = 'title'; // title, playing, gameover\n        let score = 0;\n        let lives = config.settings.lives;\n        let combo = 0;\n        let comboMultiplier = 1;\n        let currentFilter = 0;\n        let shopperCount = 0;\n        let lastFilterTap = 0;\n        let dangerTimer = 0;\n        let sirenSource = null;\n        let tutorialStep = 0; // 0: None, 1: Filter, 2: Decision\n        let tutorialSeen = false;\n        let fontsLoaded = false;\n        let leaderboardEntries = [];\n        let userRank = null;\n\n        function playSiren() {\n            try {\n                const actx = getAudioContext();\n                const buffer = audioBuffers['sfx_alarm_siren'];\n                if (!buffer) return;\n                sirenSource = actx.createBufferSource();\n                sirenSource.buffer = buffer;\n                sirenSource.loop = true;\n                const gain = actx.createGain();\n                gain.gain.value = 0.6;\n                sirenSource.connect(gain);\n                gain.connect(actx.destination);\n                sirenSource.start(0);\n            } catch(e) {}\n        }\n\n        function stopSiren() {\n            if (sirenSource) {\n                try { sirenSource.stop(); } catch(e) {}\n                sirenSource = null;\n            }\n        }\n\n        // Current shopper\n        let currentShopper = null;\n        let shopperY = 760; // Fixed final Y position\n        let shopperScale = 0.1; // Initial scale for zoom-in effect\n        let shopperOpacity = 0; // Initial opacity for fade-in effect\n        let shopperZoomSpeed = 0;\n        let shopperTimer = 0;\n        let shopperMaxTime = 5000;\n        let decisionMade = false;\n        let transitionTimer = 0;\n        let feedbackText = '';\n        let feedbackColor = '';\n        let feedbackTimer = 0;\n        let scanLineOffset = 0;\n        let scanBarProgress = 0;\n        let scanBarDirection = 1;\n\n        // Button states\n        let allowBtnPressed = false;\n        let denyBtnPressed = false;\n        let filterBtnPressed = false;\n\n        // Edit mode state\n        let editSelectedFilter = -1;\n        let editDragging = false;\n\n        function generateShopper() {\n            const shopperId = SHOPPER_IDS[Math.floor(Math.random() * SHOPPER_IDS.length)];\n            const numItems = Math.min(config.settings.itemsPerShopper, 1 + Math.floor(shopperCount / 5));\n            const actualItems = Math.max(1, Math.min(numItems, 3 + Math.floor(shopperCount / 8)));\n\n            let items = [];\n            const hasContraband = (shopperCount === 0 && !tutorialSeen) ? true : (Math.random() < 0.55);\n\n            if (hasContraband) {\n                const cItem = CONTRABAND_ITEMS[Math.floor(Math.random() * CONTRABAND_ITEMS.length)];\n                items.push({ id: cItem, contraband: true, pos: ITEM_POSITIONS[0] });\n                for (let i = 1; i < actualItems; i++) {\n                    const sItem = SAFE_ITEMS[Math.floor(Math.random() * SAFE_ITEMS.length)];\n                    items.push({ id: sItem, contraband: false, pos: ITEM_POSITIONS[i] });\n                }\n            } else {\n                for (let i = 0; i < actualItems; i++) {\n                    const sItem = SAFE_ITEMS[Math.floor(Math.random() * SAFE_ITEMS.length)];\n                    items.push({ id: sItem, contraband: false, pos: ITEM_POSITIONS[i] });\n                }\n            }\n\n            return { shopperId, items, hasContraband };\n        }\n\n        function spawnShopper() {\n            currentShopper = generateShopper();\n            shopperY = 760; // Fixed final Y position\n            shopperScale = 0.1; // Start small\n            shopperOpacity = 0; // Start transparent\n            shopperZoomSpeed = 1.8; // Shopper zooms in from 0.1 to 1 scale in 0.5 seconds\n            shopperMaxTime = 7000; // 7 seconds\n            shopperTimer = 0;\n            decisionMade = false;\n            transitionTimer = 500;\n            currentFilter = 0;\n        }\n\n        function makeDecision(allow) {\n            if (tutorialStep > 0) {\n                if (tutorialStep === 2) {\n                    if (allow) return; // Force DENY in tutorial\n                    tutorialStep = 3;\n                } else if (tutorialStep === 1) {\n                    // Don't allow skipping the scan tutorial by clicking decision buttons\n                    return;\n                }\n            }\n            if (decisionMade || !currentShopper || transitionTimer > 0 || gameState === 'danger') return;\n            decisionMade = true;\n\n            const correct = allow ? !currentShopper.hasContraband : currentShopper.hasContraband;\n\n            if (correct) {\n                combo++;\n                comboMultiplier = Math.min(config.settings.maxCombo, 1 + combo * 0.5);\n                score += Math.round(100 * comboMultiplier);\n                feedbackText = '+' + Math.round(100 * comboMultiplier);\n                feedbackColor = '#00ff88';\n                playSound('sfx_decision_correct', 0.6);\n            } else {\n                combo = 0;\n                comboMultiplier = 1;\n\n                if (allow && currentShopper.hasContraband) {\n                    // DANGER CUTSCENE: Allowed contraband\n                    gameState = 'danger';\n                    dangerTimer = 3500; // 3.5 seconds of alarm\n                    playSiren();\n                    return; // Don't proceed to next shopper yet\n                } else {\n                    lives--;\n                    feedbackText = 'WRONG!';\n                    feedbackColor = '#ff4444';\n                    playSound('sfx_decision_wrong', 0.6);\n\n                    if (lives <= 0) {\n                    endGame();\n                    return;\n                }\n            }\n        }\n\n        feedbackTimer = 1200;\n        shopperCount++;\n\n        setTimeout(() => {\n            if (gameState === 'playing' && tutorialStep === 0) spawnShopper();\n        }, 800);\n    }\n\n    function endGame() {\n        gameState = 'gameover';\n        stopMusic();\n        playSound('sfx_game_over', 0.7);\n        lib.addPlayerScoreToLeaderboard(score).catch(error => {\n            lib.log(`Failed to submit score to leaderboard: ${error.message}`);\n        });\n    }\n\n        function rotateFilter() {\n            if (tutorialStep === 1) {\n                tutorialStep = 2;\n            }\n            const now = Date.now();\n            if (now - lastFilterTap < 200) return;\n            lastFilterTap = now;\n            currentFilter = (currentFilter + 1) % FILTERS.length;\n            // playSound('sfx_scanner_rotate', 0.4); // Sound removed per user request\n\n            // Check item detection\n            if (currentShopper) {\n                const filter = FILTERS[currentFilter];\n                for (const item of currentShopper.items) {\n                    if (filter.reveals.includes(item.id)) {\n                        if (item.contraband) {\n                            playSound('sfx_contraband_alert_v3', 0.3);\n                        } else {\n                            playSound('sfx_item_detected_safe', 0.3);\n                        }\n                        break;\n                    }\n                }\n            }\n        }\n\n        // Drawing helpers\n        function drawImageContain(img, x, y, maxW, maxH) {\n            if (!img) return;\n            const scale = Math.min(maxW / img.width, maxH / img.height);\n            const w = img.width * scale;\n            const h = img.height * scale;\n            ctx.drawImage(img, x - w/2, y - h/2, w, h);\n        }\n\n        function drawRoundRect(x, y, w, h, r) {\n            ctx.beginPath();\n            ctx.moveTo(x + r, y);\n            ctx.lineTo(x + w - r, y);\n            ctx.quadraticCurveTo(x + w, y, x + w, y + r);\n            ctx.lineTo(x + w, y + h - r);\n            ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);\n            ctx.lineTo(x + r, y + h);\n            ctx.quadraticCurveTo(x, y + h, x, y + h - r);\n            ctx.lineTo(x, y + r);\n            ctx.quadraticCurveTo(x, y, x + r, y);\n            ctx.closePath();\n        }\n\n        // Input handling\n        const ALLOW_BTN = { x: 380, y: 1120, w: 300, h: 120 };\n        const DENY_BTN = { x: 40, y: 1120, w: 300, h: 120 };\n        const FILTER_AREA = { x: 60, y: 960, w: 600, h: 120 };\n        const SCANNER_AREA = { x: 190, y: 400, w: 340, h: 450 };\n        const LEADERBOARD_ICON_AREA = { x: 24, y: 140, w: 80, h: 80 };\n\n        function hitTest(px, py, rect) {\n            return px >= rect.x && px <= rect.x + rect.w && py >= rect.y && py <= rect.y + rect.h;\n        }\n\n        function handleInput(px, py, isDown) {\n            if (mode === 'edit') return;\n\n            if (gameState === 'title' && isDown && fontsLoaded) {\n                if (hitTest(px, py, { x: 210, y: 850, w: 300, h: 96 })) { // START button area\n                    startGame();\n                }\n                return;\n            }\n\n        if (gameState === 'gameover' && isDown) {\n            if (hitTest(px, py, { x: 210, y: 820, w: 300, h: 96 })) { // RESTART button area\n                startGame();\n            }\n            return;\n        }\n\n        if (gameState === 'leaderboard' && isDown) {\n            if (hitTest(px, py, { x: 210, y: H - 150, w: 300, h: 96 })) { // BACK button\n                gameState = 'playing';\n            }\n            return;\n        }\n\n        if (gameState !== 'playing') return;\n\n        if (isDown) {\n            if (hitTest(px, py, LEADERBOARD_ICON_AREA)) {\n                showLeaderboard();\n                return;\n            }\n                if (tutorialStep === 3) {\n                    tutorialStep = 0;\n                    tutorialSeen = true;\n                    spawnShopper();\n                    return;\n                }\n                if (hitTest(px, py, ALLOW_BTN)) {\n                    allowBtnPressed = true;\n                    makeDecision(true);\n                } else if (hitTest(px, py, DENY_BTN)) {\n                    denyBtnPressed = true;\n                    makeDecision(false);\n                } else if (hitTest(px, py, FILTER_AREA) || hitTest(px, py, SCANNER_AREA)) {\n                    filterBtnPressed = true;\n                    rotateFilter();\n                }\n            } else {\n                allowBtnPressed = false;\n                denyBtnPressed = false;\n                filterBtnPressed = false;\n            }\n        }\n\n        async function startGame() {\n            gameState = 'playing';\n            score = 0;\n            lives = config.settings.lives;\n            combo = 0;\n            comboMultiplier = 1;\n            shopperCount = 0;\n            spawnShopper();\n            playSound('music_gameplay_loop', 0.3);\n            \n            // Initial rank fetch\n            try {\n                const response = await lib.getTopNEntriesFromLeaderboard(1);\n                userRank = response.userRank;\n            } catch (e) {}\n        }\n\n        canvas.addEventListener('pointerdown', (e) => {\n            const rect = canvas.getBoundingClientRect();\n            const px = (e.clientX - rect.left) * (W / rect.width);\n            const py = (e.clientY - rect.top) * (H / rect.height);\n            handleInput(px, py, true);\n        });\n\n        canvas.addEventListener('pointerup', (e) => {\n            const rect = canvas.getBoundingClientRect();\n            const px = (e.clientX - rect.left) * (W / rect.width);\n            const py = (e.clientY - rect.top) * (H / rect.height);\n            handleInput(px, py, false);\n        });\n\n        // Keyboard\n        document.addEventListener('keydown', (e) => {\n            if (mode === 'edit') return;\n            if (e.code === 'Space') { e.preventDefault(); rotateFilter(); }\n            if (e.code === 'KeyA') makeDecision(true);\n            if (e.code === 'KeyD') makeDecision(false);\n            if (e.code === 'Enter' && gameState === 'title' && fontsLoaded) {\n                startGame();\n            }\n            if (e.code === 'KeyR' && gameState === 'gameover') startGame();\n            if (e.code === 'KeyL' && gameState === 'playing') showLeaderboard(); // 'L' for Leaderboard\n            if (e.code === 'Escape' && gameState === 'leaderboard') gameState = 'playing'; // Escape to close leaderboard\n        });\n\n        // Main render\n        let lastTime = performance.now();\n\n        function gameLoop(timestamp) {\n            const dt = Math.min((timestamp - lastTime) / 1000, 0.11);\n            lastTime = timestamp;\n\n            // Update\n            if (gameState === 'playing' && !decisionMade && currentShopper) {\n                transitionTimer = Math.max(0, transitionTimer - dt * 1000);\n\n                if (transitionTimer <= 0) {\n                    const wasZooming = shopperScale < 1;\n                    \n                    if (tutorialStep === 0 || wasZooming) {\n                        shopperScale = Math.min(1, shopperScale + shopperZoomSpeed * dt);\n                        shopperOpacity = Math.min(1, shopperOpacity + dt * 2);\n                        shopperY = H/2 + (760 - H/2) * shopperScale;\n                    }\n\n                    // Trigger tutorial when shopper is ready\n                    if (!wasZooming && shopperScale === 1 && shopperCount === 0 && !tutorialSeen && tutorialStep === 0) {\n                        tutorialStep = 1;\n                    }\n\n                    if (tutorialStep === 0) {\n                        shopperTimer += dt * 1000;\n\n                        if (shopperTimer >= shopperMaxTime) {\n                            // Timeout - counts as mistake\n                            lives--;\n                            combo = 0;\n                            comboMultiplier = 1;\n                            feedbackText = 'TOO SLOW!';\n                            feedbackColor = '#ffaa00';\n                            feedbackTimer = 1200;\n                            decisionMade = true;\n\n                            if (lives <= 0) {\n                        endGame();\n                    } else {\n                        shopperCount++;\n                        setTimeout(() => { if (gameState === 'playing') spawnShopper(); }, 800);\n                    }\n                }\n            }\n        }\n    }\n\n    if (gameState === 'danger') {\n                dangerTimer -= dt * 1000;\n                if (dangerTimer <= 0) {\n                    stopSiren();\n                    lives--;\n                    shopperCount++;\n                    if (lives <= 0) {\n                    endGame();\n                } else {\n                    gameState = 'playing';\n                    spawnShopper();\n                }\n            }\n        }\n\n            if (feedbackTimer > 0) feedbackTimer -= dt * 1000;\n            scanLineOffset = (scanLineOffset + dt * 100) % 20;\n            \n            // Update scanning bar\n            scanBarProgress += dt * 0.4 * scanBarDirection;\n            if (scanBarProgress > 1) { scanBarProgress = 1; scanBarDirection = -1; }\n            if (scanBarProgress < 0) { scanBarProgress = 0; scanBarDirection = 1; }\n\n            // Render\n            ctx.clearRect(0, 0, W, H);\n\n            // Background\n            const bgImg = imageCache['background_mall_entrance'];\n            if (bgImg) {\n                const scale = Math.max(W / bgImg.width, H / bgImg.height);\n                const bw = bgImg.width * scale;\n                const bh = bgImg.height * scale;\n                ctx.drawImage(bgImg, (W - bw)/2, (H - bh)/2, bw, bh);\n            } else {\n                ctx.fillStyle = '#1a1a2e';\n                ctx.fillRect(0, 0, W, H);\n            }\n\n            // Darken background slightly\n            ctx.fillStyle = 'rgba(10, 10, 30, 0.2)';\n            ctx.fillRect(0, 0, W, H);\n\n            if (gameState === 'title') {\n                if (fontsLoaded) {\n                    drawTitleScreen();\n                } else {\n                    // Draw a simple loading screen while fonts load\n                    ctx.fillStyle = '#1a1a2e';\n                    ctx.fillRect(0, 0, W, H);\n                    ctx.font = '400 24px Inter';\n                    ctx.textAlign = 'center';\n                    ctx.fillStyle = '#ffffff';\n                    ctx.fillText('LOADING...', W/2, H/2);\n                }\n            } else if (gameState === 'playing' || gameState === 'gameover' || gameState === 'danger' || gameState === 'leaderboard') {\n                drawGameplay();\n                if (tutorialStep > 0) drawTutorial();\n                if (gameState === 'danger') drawDangerOverlay();\n                if (gameState === 'gameover') drawGameOver();\n                if (gameState === 'leaderboard') drawLeaderboard();\n            }\n\n            if (mode === 'edit') {\n                drawEditMode();\n            }\n\n            requestAnimationFrame(gameLoop);\n        }\n\n        function drawTitleScreen() {\n            // Title\n            ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';\n            ctx.fillRect(0, 0, W, H);\n\n            ctx.font = '900 80px Orbitron';\n            ctx.textAlign = 'center';\n            ctx.fillStyle = '#00ffcc';\n            ctx.shadowColor = '#00ffcc';\n            ctx.shadowBlur = 20;\n            ctx.fillText('MALL COP', W/2, 400);\n            ctx.font = '700 64px Orbitron';\n            ctx.fillStyle = '#ffffff';\n            ctx.shadowColor = '#ffffff';\n            ctx.fillText('SCANNER', W/2, 480);\n            ctx.shadowBlur = 0;\n\n            // Instructions\n            ctx.font = '700 32px Orbitron';\n            ctx.fillStyle = '#00ffcc';\n            ctx.fillText('THE MALL NEEDS YOU.', W/2, 600);\n            ctx.font = '600 24px Inter';\n            ctx.fillStyle = '#ffffff';\n            ctx.fillText('ONLY THE SHARPEST EYES', W/2, 640);\n            ctx.fillText('CAN PROTECT IT.', W/2, 670);\n            ctx.font = '700 28px Inter';\n            ctx.fillStyle = '#ffcc00';\n            ctx.fillText('ARE YOU READY?', W/2, 730);\n\n            // Start button\n            const pulse = Math.sin(Date.now() / 500) * 0.1 + 0.9;\n            ctx.globalAlpha = pulse;\n            drawRoundRect(210, 850, 300, 96, 16);\n            ctx.fillStyle = '#00ffcc';\n            ctx.fill();\n            ctx.globalAlpha = 1;\n            ctx.font = '700 32px Orbitron';\n            ctx.fillStyle = '#1a1a2e';\n            ctx.fillText('START', W/2, 908);\n        }\n\n        function drawTutorial() {\n            // Darken screen removed per user request\n            \n            const bounce = Math.sin(Date.now() / 200) * 20;\n            ctx.textAlign = 'center';\n\n            // Text box parameters\n            const boxW = 620;\n            const boxH = 170;\n            const boxX = W/2 - boxW/2;\n            const boxY = H/2 - 260;\n\n            if (tutorialStep > 0) {\n                // Draw background box for text\n                ctx.fillStyle = 'rgba(10, 10, 30, 0.95)';\n                drawRoundRect(boxX, boxY, boxW, boxH, 20);\n                ctx.fill();\n                \n                // Box border\n                ctx.strokeStyle = '#00ffcc';\n                ctx.lineWidth = 4;\n                ctx.stroke();\n\n                // Add a slight inner glow effect to the box\n                ctx.strokeStyle = 'rgba(0, 255, 204, 0.3)';\n                ctx.lineWidth = 2;\n                drawRoundRect(boxX + 5, boxY + 5, boxW - 10, boxH - 10, 15);\n                ctx.stroke();\n            }\n\n            if (tutorialStep === 1) {\n                // Pointing Hand (Pointing down from above)\n                ctx.font = '80px Arial';\n                ctx.fillText('\ud83d\udc47', W/2, FILTER_AREA.y - 40 + bounce);\n\n                // Text\n                ctx.font = '700 36px Orbitron';\n                ctx.fillStyle = '#00ffcc';\n                ctx.fillText('TAP TO CHANGE FILTERS', W/2, boxY + 55);\n                \n                ctx.font = '600 24px Inter';\n                ctx.fillStyle = '#ffffff';\n                ctx.fillText('Discover hidden items', W/2, boxY + 105);\n                ctx.fillText('by scanning them!', W/2, boxY + 140);\n                \n                // Outline box for Filter area\n                const pulse = (Math.sin(Date.now() / 400) + 1) / 2;\n                ctx.strokeStyle = `rgba(0, 255, 204, ${0.5 + pulse * 0.5})`;\n                ctx.lineWidth = 6;\n                drawRoundRect(FILTER_AREA.x - 5, FILTER_AREA.y - 5, FILTER_AREA.w + 10, FILTER_AREA.h + 10, 16);\n                ctx.stroke();\n            } else if (tutorialStep === 2) {\n                // Pointing Hand (Pointing down from above to DENY)\n                ctx.font = '80px Arial';\n                ctx.fillText('\ud83d\udc47', DENY_BTN.x + DENY_BTN.w/2, DENY_BTN.y - 40 + bounce);\n\n                // Text\n                ctx.font = '700 36px Orbitron';\n                ctx.fillStyle = '#00ffcc';\n                ctx.fillText('CHOOSE ALLOW OR DENY', W/2, boxY + 55);\n\n                ctx.font = '600 24px Inter';\n                ctx.fillStyle = '#ffffff';\n                ctx.fillText('Make your judgment based', W/2, boxY + 105);\n                ctx.fillText('on your scan results!', W/2, boxY + 140);\n\n                // Outline box for DENY button\n                const pulse = (Math.sin(Date.now() / 400) + 1) / 2;\n                ctx.strokeStyle = `rgba(0, 255, 204, ${0.5 + pulse * 0.5})`;\n                ctx.lineWidth = 6;\n                drawRoundRect(DENY_BTN.x - 5, DENY_BTN.y - 5, DENY_BTN.w + 10, DENY_BTN.h + 10, 20);\n                ctx.stroke();\n            } else if (tutorialStep === 3) {\n                // Pointing Hand (Pointing to Lives at top right)\n                ctx.font = '80px Arial';\n                ctx.fillText('\ud83d\udc46', W - 80, 140 + bounce);\n\n                // Text\n                ctx.font = '700 36px Orbitron';\n                ctx.fillStyle = '#ff4444';\n                ctx.fillText('WATCH YOUR LIVES', W/2, boxY + 55);\n\n                ctx.font = '600 24px Inter';\n                ctx.fillStyle = '#ffffff';\n                ctx.fillText('You have 3 lives total.', W/2, boxY + 105);\n                ctx.fillText('Don\\'t make mistakes or you\\'ll be fired!', W/2, boxY + 140);\n\n                ctx.font = '700 28px Inter';\n                ctx.fillStyle = '#ffffff';\n                ctx.fillText('(Tap anywhere to continue)', W/2, boxY + 235);\n            }\n        }\n\n        function drawGameplay() {\n            const filter = FILTERS[currentFilter];\n\n            // Scanner zone background\n            ctx.fillStyle = 'rgba(20, 20, 40, 0.3)';\n            drawRoundRect(SCANNER_AREA.x, SCANNER_AREA.y, SCANNER_AREA.w, SCANNER_AREA.h, 8);\n            ctx.fill();\n\n            // Filter overlay\n            ctx.fillStyle = filter.color;\n            drawRoundRect(SCANNER_AREA.x, SCANNER_AREA.y, SCANNER_AREA.w, SCANNER_AREA.h, 8);\n            ctx.fill();\n\n            // Scan lines\n            ctx.save();\n            ctx.beginPath();\n            drawRoundRect(SCANNER_AREA.x, SCANNER_AREA.y, SCANNER_AREA.w, SCANNER_AREA.h, 8);\n            ctx.clip();\n            ctx.strokeStyle = filter.color.replace('0.25', '0.15');\n            ctx.lineWidth = 1;\n            for (let y = SCANNER_AREA.y + scanLineOffset; y < SCANNER_AREA.y + SCANNER_AREA.h; y += 20) {\n                ctx.beginPath();\n                ctx.moveTo(SCANNER_AREA.x, y);\n                ctx.lineTo(SCANNER_AREA.x + SCANNER_AREA.w, y);\n                ctx.stroke();\n            }\n            ctx.restore();\n\n            // Draw shopper\n            if (currentShopper) {\n                const shopperImg = imageCache[currentShopper.shopperId];\n                const shopperCenterX = W / 2;\n                const shopperCenterY = shopperY; // Fixed Y position\n\n                ctx.save();\n                ctx.globalAlpha = shopperOpacity;\n                ctx.translate(shopperCenterX, shopperCenterY);\n                ctx.scale(shopperScale, shopperScale);\n\n                if (shopperImg) {\n                    drawImageContain(shopperImg, 0, 0, 160, 280);\n                }\n\n                // Draw items\n                for (const item of currentShopper.items) {\n                    const revealed = filter.reveals.includes(item.id);\n                    if (revealed) {\n                        const ix = item.pos.x;\n                        const iy = item.pos.y;\n                        const itemImg = imageCache[item.id];\n\n                        // Glow effect\n                        const glowColor = item.contraband ? '#ff0000' : '#00ff00';\n                        const glowIntensity = (Math.sin(Date.now() / 200) * 0.3 + 0.7) * (config.settings.glowIntensity || 1);\n                        ctx.shadowColor = glowColor;\n                        ctx.shadowBlur = 20 * glowIntensity;\n\n                        if (itemImg) {\n                            drawImageContain(itemImg, ix, iy, 60, 60);\n                        } else {\n                            ctx.fillStyle = glowColor;\n                            ctx.fillRect(ix - 20, iy - 20, 40, 40);\n                        }\n\n                        // Glow ring\n                        ctx.beginPath();\n                        ctx.arc(ix, iy, 35, 0, Math.PI * 2);\n                        ctx.strokeStyle = glowColor;\n                        ctx.globalAlpha = glowIntensity * 0.6;\n                        ctx.lineWidth = 3;\n                        ctx.stroke();\n                        ctx.globalAlpha = 1;\n                        ctx.shadowBlur = 0;\n                    }\n                }\n                ctx.restore();\n\n                // Moving scan bar\n                const barY = SCANNER_AREA.y + scanBarProgress * SCANNER_AREA.h;\n                ctx.save();\n                ctx.beginPath();\n                drawRoundRect(SCANNER_AREA.x, SCANNER_AREA.y, SCANNER_AREA.w, SCANNER_AREA.h, 8);\n                ctx.clip();\n                \n                const barGradient = ctx.createLinearGradient(0, barY - 25, 0, barY + 25);\n                const activeColor = filter.color.replace('0.25', '0.7');\n                barGradient.addColorStop(0, 'transparent');\n                barGradient.addColorStop(0.5, activeColor);\n                barGradient.addColorStop(1, 'transparent');\n                \n                ctx.fillStyle = barGradient;\n                ctx.fillRect(SCANNER_AREA.x, barY - 25, SCANNER_AREA.w, 50);\n                \n                ctx.strokeStyle = '#ffffff';\n                ctx.lineWidth = 3;\n                ctx.globalAlpha = 0.5;\n                ctx.beginPath();\n                ctx.moveTo(SCANNER_AREA.x, barY);\n                ctx.lineTo(SCANNER_AREA.x + SCANNER_AREA.w, barY);\n                ctx.stroke();\n                ctx.restore();\n\n                // Timer bar\n                if (!decisionMade && transitionTimer <= 0) {\n                    const progress = 1 - (shopperTimer / shopperMaxTime);\n                    const barW = 200;\n                    ctx.fillStyle = 'rgba(0,0,0,0.5)';\n                    drawRoundRect(W/2 - barW/2 - 2, 270, barW + 4, 16, 4);\n                    ctx.fill();\n                    const col = progress > 0.3 ? '#00ffcc' : '#ff4444';\n                    ctx.fillStyle = col;\n                    drawRoundRect(W/2 - barW/2, 272, barW * Math.max(0, progress), 12, 3);\n                    ctx.fill();\n                }\n            }\n\n            // Scanner frame\n            const frameImg = imageCache['scanner_frame'];\n            if (frameImg) {\n                drawImageContain(frameImg, W/2, SCANNER_AREA.y + SCANNER_AREA.h/2, SCANNER_AREA.w + 40, SCANNER_AREA.h + 40);\n            }\n\n            // HUD - Score\n            ctx.font = '700 32px Orbitron';\n            ctx.textAlign = 'left';\n            ctx.fillStyle = '#ffffff';\n            ctx.shadowColor = '#000000';\n            ctx.shadowBlur = 4;\n            ctx.fillText(score.toString(), 24, 48);\n            ctx.shadowBlur = 0;\n\n            ctx.font = '600 20px Inter';\n            ctx.fillStyle = '#ffcc00';\n            ctx.fillText('SCORE', 24, 76);\n\n            // Shopper count\n            ctx.font = '600 20px Inter';\n            ctx.textAlign = 'left';\n            ctx.fillStyle = '#ffcc00';\n            ctx.fillText('SHOPPER #' + (shopperCount + 1), 24, 115);\n\n            drawLeaderboardIcon();\n\n            // Combo\n            if (combo > 0) {\n                ctx.textAlign = 'center';\n                ctx.font = '700 28px Orbitron';\n                ctx.fillStyle = '#ffcc00';\n                ctx.shadowColor = '#ffcc00';\n                ctx.shadowBlur = 8;\n                ctx.fillText(comboMultiplier.toFixed(1) + '\u00d7', W/2, 48);\n                ctx.shadowBlur = 0;\n                ctx.font = '600 18px Inter';\n                ctx.fillStyle = '#aaaacc';\n                ctx.fillText('COMBO', W/2, 72);\n            }\n\n            // Lives\n            ctx.textAlign = 'right';\n            for (let i = 0; i < config.settings.lives; i++) {\n                const lx = W - 24 - i * 44;\n                ctx.font = '32px sans-serif';\n                ctx.fillStyle = i < lives ? '#ff4444' : '#333344';\n                ctx.fillText('\u2764', lx, 48);\n            }\n\n            // Filter display\n            ctx.fillStyle = 'rgba(20, 20, 50, 0.85)';\n            drawRoundRect(FILTER_AREA.x, FILTER_AREA.y, FILTER_AREA.w, FILTER_AREA.h, 12);\n            ctx.fill();\n            ctx.strokeStyle = filter.color.replace('0.25', '0.7');\n            ctx.lineWidth = 2;\n            drawRoundRect(FILTER_AREA.x, FILTER_AREA.y, FILTER_AREA.w, FILTER_AREA.h, 12);\n            ctx.stroke();\n\n            ctx.textAlign = 'center';\n            ctx.font = '700 28px Orbitron';\n            ctx.fillStyle = '#ffffff';\n            ctx.fillText(filter.name, W/2, FILTER_AREA.y + 45);\n            ctx.font = '400 20px Inter';\n            ctx.fillStyle = '#88aacc';\n            ctx.fillText('TAP TO ROTATE FILTER', W/2, FILTER_AREA.y + 85);\n\n            // Filter dots\n            for (let i = 0; i < FILTERS.length; i++) {\n                ctx.beginPath();\n                ctx.arc(W/2 - 30 + i * 20, FILTER_AREA.y + 105, 5, 0, Math.PI * 2);\n                ctx.fillStyle = i === currentFilter ? '#ffffff' : '#444466';\n                ctx.fill();\n            }\n\n            // DENY button\n            const denyScale = denyBtnPressed ? 0.95 : 1;\n            ctx.save();\n            ctx.translate(DENY_BTN.x + DENY_BTN.w/2, DENY_BTN.y + DENY_BTN.h/2);\n            ctx.scale(denyScale, denyScale);\n            drawRoundRect(-DENY_BTN.w/2, -DENY_BTN.h/2, DENY_BTN.w, DENY_BTN.h, 16);\n            ctx.fillStyle = decisionMade ? '#442222' : '#cc2222';\n            ctx.fill();\n            ctx.font = '700 36px Orbitron';\n            ctx.textAlign = 'center';\n            ctx.fillStyle = decisionMade ? '#884444' : '#ffffff';\n            ctx.fillText('DENY', 0, 12);\n            ctx.restore();\n\n            // ALLOW button\n            const allowScale = allowBtnPressed ? 0.95 : 1;\n            ctx.save();\n            ctx.translate(ALLOW_BTN.x + ALLOW_BTN.w/2, ALLOW_BTN.y + ALLOW_BTN.h/2);\n            ctx.scale(allowScale, allowScale);\n            drawRoundRect(-ALLOW_BTN.w/2, -ALLOW_BTN.h/2, ALLOW_BTN.w, ALLOW_BTN.h, 16);\n            ctx.fillStyle = decisionMade ? '#224422' : '#22cc66';\n            ctx.fill();\n            ctx.font = '700 36px Orbitron';\n            ctx.textAlign = 'center';\n            ctx.fillStyle = decisionMade ? '#448844' : '#ffffff';\n            ctx.fillText('ALLOW', 0, 12);\n            ctx.restore();\n\n            // Feedback text\n            if (feedbackTimer > 0) {\n                const alpha = Math.min(1, feedbackTimer / 400);\n                ctx.globalAlpha = alpha;\n                ctx.font = '900 48px Orbitron';\n                ctx.textAlign = 'center';\n                ctx.fillStyle = feedbackColor;\n                ctx.shadowColor = feedbackColor;\n                ctx.shadowBlur = 15;\n                ctx.fillText(feedbackText, W/2, 550);\n                ctx.shadowBlur = 0;\n                ctx.globalAlpha = 1;\n            }\n        }\n\n        function drawDangerOverlay() {\n            const flash = Math.sin(Date.now() / 100) > 0;\n            if (flash) {\n                ctx.fillStyle = 'rgba(255, 0, 0, 0.4)';\n                ctx.fillRect(0, 0, W, H);\n            }\n\n            ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';\n            ctx.fillRect(0, 500, W, 280);\n\n            ctx.textAlign = 'center';\n            ctx.font = '900 64px Orbitron';\n            ctx.fillStyle = flash ? '#ffffff' : '#ff0000';\n            ctx.shadowColor = '#ff0000';\n            ctx.shadowBlur = 20;\n            ctx.fillText('SECURITY BREACH!', W/2, 600);\n            \n            ctx.font = '700 40px Orbitron';\n            ctx.fillStyle = '#ffffff';\n            ctx.fillText('MALL IN DANGER', W/2, 680);\n            ctx.shadowBlur = 0;\n\n            ctx.font = '600 24px Inter';\n            ctx.fillStyle = '#ffaaaa';\n            ctx.fillText('CONTRABAND DETECTED', W/2, 730);\n        }\n\n        function drawGameOver() {\n            ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';\n            ctx.fillRect(0, 0, W, H);\n\n            ctx.textAlign = 'center';\n            ctx.font = '900 52px Orbitron';\n            ctx.fillStyle = '#ff4444';\n            ctx.shadowColor = '#ff4444';\n            ctx.shadowBlur = 20;\n            ctx.fillText('GAME OVER', W/2, 450);\n            ctx.shadowBlur = 0;\n\n            ctx.font = '600 28px Inter';\n            ctx.fillStyle = '#ffffff';\n            ctx.fillText('Final Score', W/2, 540);\n\n            ctx.font = '900 56px Orbitron';\n            ctx.fillStyle = '#00ffcc';\n            ctx.fillText(score.toString(), W/2, 610);\n\n            ctx.font = '600 22px Inter';\n            ctx.fillStyle = '#aaaacc';\n            ctx.fillText('Shoppers Scanned: ' + shopperCount, W/2, 680);\n            ctx.fillText('Best Combo: ' + comboMultiplier.toFixed(1) + '\u00d7', W/2, 720);\n\n            // Restart button\n            const pulse = Math.sin(Date.now() / 500) * 0.05 + 0.95;\n            ctx.globalAlpha = pulse;\n            drawRoundRect(210, 820, 300, 96, 16);\n            ctx.fillStyle = '#00ffcc';\n            ctx.fill();\n            ctx.globalAlpha = 1;\n            ctx.font = '700 28px Orbitron';\n            ctx.fillStyle = '#1a1a2e';\n            ctx.fillText('RESTART', W/2, 878);\n        }\n\n\n        function drawLeaderboardIcon() {\n            const badgeImg = imageCache['icon_badge'];\n            const iconX = LEADERBOARD_ICON_AREA.x + LEADERBOARD_ICON_AREA.w / 2;\n            const iconY = LEADERBOARD_ICON_AREA.y + LEADERBOARD_ICON_AREA.h / 2;\n            \n            if (badgeImg) {\n                drawImageContain(badgeImg, iconX, iconY, LEADERBOARD_ICON_AREA.w, LEADERBOARD_ICON_AREA.h);\n            }\n            \n            if (userRank !== null) {\n                ctx.font = '700 18px Orbitron';\n                ctx.textAlign = 'left';\n                ctx.fillStyle = '#ffcc00';\n                ctx.fillText(`RANK: ${userRank}`, LEADERBOARD_ICON_AREA.x + LEADERBOARD_ICON_AREA.w + 10, iconY + 6);\n            }\n        }\n\n        async function showLeaderboard() {\n            gameState = 'leaderboard';\n            leaderboardEntries = [];\n            userRank = null;\n            try {\n                const response = await lib.getTopNEntriesFromLeaderboard(10);\n                leaderboardEntries = response.entries;\n                userRank = response.userRank;\n            } catch (error) {\n                lib.log(`Failed to fetch leaderboard: ${error.message}`);\n                // Handle guest users or other errors\n                leaderboardEntries = [{ username: 'Guest', score: 'N/A', profilePicture: '' }]; // Placeholder\n                userRank = 'N/A';\n            }\n        }\n\n        function drawLeaderboard() {\n            ctx.fillStyle = 'rgba(0, 0, 0, 0.9)';\n            ctx.fillRect(0, 0, W, H);\n\n            ctx.textAlign = 'center';\n            ctx.font = '900 48px Orbitron';\n            ctx.fillStyle = '#00ffcc';\n            ctx.shadowColor = '#00ffcc';\n            ctx.shadowBlur = 20;\n            ctx.fillText('MALL SECURITY ELITE', W/2, 150);\n            ctx.shadowBlur = 0;\n\n            ctx.font = '700 32px Orbitron';\n            ctx.fillStyle = '#ffffff';\n            ctx.fillText('TOP 10', W/2, 220);\n\n            ctx.font = '600 24px Inter';\n            ctx.fillStyle = '#88aacc';\n            ctx.fillText('Rank | Player | Score', W/2, 280);\n\n            let yOffset = 320;\n            if (leaderboardEntries.length === 0) {\n                ctx.font = '400 20px Inter';\n                ctx.fillStyle = '#ffffff';\n                ctx.fillText('Loading leaderboard...', W/2, yOffset);\n            } else {\n                leaderboardEntries.forEach((entry, index) => {\n                    const rank = index + 1;\n                    const username = entry.username || 'Anonymous';\n                    const entryScore = entry.score || 0;\n\n                    ctx.font = '400 20px Inter';\n                    ctx.fillStyle = '#ffffff';\n                    ctx.textAlign = 'left';\n                    ctx.fillText(`${rank}.`, W/2 - 180, yOffset);\n                    ctx.textAlign = 'center';\n                    ctx.fillText(username, W/2, yOffset);\n                    ctx.textAlign = 'right';\n                    ctx.fillText(entryScore.toString(), W/2 + 180, yOffset);\n                    yOffset += 40;\n                });\n            }\n\n            // Player's rank\n            ctx.font = '700 28px Orbitron';\n            ctx.fillStyle = '#ffcc00';\n            ctx.textAlign = 'center';\n            ctx.fillText(`YOUR RANK: ${userRank !== null ? userRank : 'N/A'}`, W/2, yOffset + 60);\n\n            // Back button\n            const pulse = Math.sin(Date.now() / 500) * 0.1 + 0.9;\n            ctx.globalAlpha = pulse;\n            drawRoundRect(210, H - 150, 300, 96, 16);\n            ctx.fillStyle = '#00ffcc';\n            ctx.fill();\n            ctx.globalAlpha = 1;\n            ctx.font = '700 32px Orbitron';\n            ctx.fillStyle = '#1a1a2e';\n            ctx.fillText('BACK', W/2, H - 92);\n        }\n\n        function drawEditMode() {\n            // Draw a preview of the scanner with a sample shopper\n            ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';\n            ctx.fillRect(0, 0, W, H);\n\n            ctx.textAlign = 'center';\n            ctx.font = '700 32px Orbitron';\n            ctx.fillStyle = '#00ffcc';\n            ctx.fillText('EDIT MODE', W/2, 100);\n\n            ctx.font = '600 22px Inter';\n            ctx.fillStyle = '#aaaacc';\n            ctx.fillText('Use the settings panel to', W/2, 150);\n            ctx.fillText('adjust game parameters', W/2, 180);\n\n            // Preview filters\n            ctx.font = '600 20px Inter';\n            ctx.fillStyle = '#ffffff';\n            ctx.fillText('Scanner Filters:', W/2, 260);\n\n            for (let i = 0; i < FILTERS.length; i++) {\n                const fx = 120 + i * 180;\n                const fy = 300;\n                ctx.fillStyle = FILTERS[i].color.replace('0.25', '0.6');\n                drawRoundRect(fx, fy, 140, 80, 8);\n                ctx.fill();\n                ctx.font = '600 14px Inter';\n                ctx.fillStyle = '#ffffff';\n                ctx.textAlign = 'center';\n                ctx.fillText(FILTERS[i].name, fx + 70, fy + 50);\n            }\n\n            // Show current settings\n            ctx.textAlign = 'center';\n            ctx.font = '600 22px Inter';\n            ctx.fillStyle = '#ffffff';\n            ctx.fillText('Current Settings:', W/2, 460);\n\n            ctx.font = '400 20px Inter';\n            ctx.fillStyle = '#ccccee';\n            ctx.fillText(`Speed: ${config.settings.startingSpeed}\u00d7`, W/2, 510);\n            ctx.fillText(`Lives: ${config.settings.lives}`, W/2, 545);\n            ctx.fillText(`Max Combo: ${config.settings.maxCombo}\u00d7`, W/2, 580);\n            ctx.fillText(`Items/Shopper: ${config.settings.itemsPerShopper}`, W/2, 615);\n\n            // Preview shopper\n            const previewImg = imageCache['shopper_casual_male'];\n            if (previewImg) {\n                drawImageContain(previewImg, W/2, 850, 140, 240);\n            }\n\n            // Preview items around shopper\n            const itemIds = ['item_smartphone', 'item_knife', 'item_wallet'];\n            const colors = ['#00ff00', '#ff0000', '#00ff00'];\n            for (let i = 0; i < 3; i++) {\n                const ix = W/2 + ITEM_POSITIONS[i].x * 1.5;\n                const iy = 850 + ITEM_POSITIONS[i].y * 1.2;\n                ctx.beginPath();\n                ctx.arc(ix, iy, 25, 0, Math.PI * 2);\n                ctx.strokeStyle = colors[i];\n                ctx.lineWidth = 2;\n                ctx.shadowColor = colors[i];\n                ctx.shadowBlur = 10;\n                ctx.stroke();\n                ctx.shadowBlur = 0;\n                const img = imageCache[itemIds[i]];\n                if (img) drawImageContain(img, ix, iy, 40, 40);\n            }\n        }\n\n        // Start the loop\n        if (mode === 'play') {\n            gameState = 'title';\n            await Promise.all([\n                document.fonts.load('900 80px Orbitron'),\n                document.fonts.load('700 64px Orbitron'),\n                document.fonts.load('700 32px Orbitron'),\n                document.fonts.load('600 24px Inter'),\n                document.fonts.load('700 28px Inter')\n            ]);\n            fontsLoaded = true;\n        } else {\n            gameState = 'title';\n            fontsLoaded = true; // Always true in edit mode for immediate rendering\n        }\n\n        requestAnimationFrame(gameLoop);\n    }\n    </script>\n</body>\n</html>", "game_config": "{\"settings\": {\"startingSpeed\": 1, \"lives\": 3, \"maxCombo\": 5, \"itemsPerShopper\": 2, \"glowIntensity\": 1}}", "game_design_document": "## Core Game Loop and Mechanics\n\n**Mall Cop Scanner** is a fast-paced arcade game where players act as a security guard operating a sophisticated multi-layered scanner to detect contraband hidden on shoppers entering a mall.\n\n### Moment-to-Moment Gameplay\n\n1. **Shopper Approaches**: A shopper walks toward the security gate from the top of the screen\n2. **Scanner Activation**: The scanner automatically activates, showing the shopper through the current filter layer\n3. **Filter Rotation**: Player taps/clicks to rotate through different scanner filters (X-ray, thermal, chemical, electromagnetic, etc.)\n4. **Item Detection**: Different filters reveal different hidden items:\n   - Some items glow **green** (safe/allowed items like phones, keys, wallets)\n   - Some items glow **red** (contraband like weapons, drugs, stolen goods)\n   - Some items are invisible on certain filters but visible on others\n5. **Decision Making**: Once the player identifies all items (or runs out of time), they tap either **ALLOW** or **DENY**\n6. **Judgment**: \n   - **Correct ALLOW**: Shopper had only green items or no items \u2192 +points, shopper passes through\n   - **Correct DENY**: Shopper had at least one red item \u2192 +points, shopper is escorted away\n   - **Incorrect Decision**: Wrong call \u2192 lose 1 life (3 strikes = game over)\n7. **Next Shopper**: Immediately queue the next shopper with increasing difficulty\n\n### Progression and Engagement\n\n- **Speed Increases**: Shoppers approach faster as the game progresses\n- **Complex Combinations**: Later shoppers carry multiple items requiring multiple filter checks\n- **Score Multipliers**: Consecutive correct decisions build a combo multiplier\n- **Time Pressure**: Each shopper has a timer\u2014if they reach the gate before a decision, it counts as a mistake\n\n### Win/Lose Conditions\n\n- **Lose**: Make 3 mistakes (wrong decisions or timeouts)\n- **Win**: Survive as long as possible, achieving the highest score\n- **Scoring**: \n  - Correct decision: +100 points \u00d7 combo multiplier\n  - Combo multiplier increases by 0.5\u00d7 for each consecutive correct decision (max 5\u00d7)\n  - Combo resets on mistake\n\n### Compelling Replayability\n\n- **High Score Chase**: Beat your personal best\n- **Pattern Recognition**: Learn which filters reveal which contraband types\n- **Risk/Reward**: Quick decisions vs. thorough scanning\n- **Increasing Difficulty**: Each run gets progressively harder\n\n## Starting Scope\n\nThe first iteration should implement a **simple but polished core experience**:\n\n### Must Have (First Iteration)\n- **3-4 scanner filters** (X-ray, thermal, chemical, electromagnetic)\n- **5-6 item types** (2-3 safe items like phone/keys/wallet, 3 contraband items like knife/drugs/gun)\n- **Basic shopper sprites** (3-4 different character designs)\n- **Core mechanics**: tap to rotate filters, ALLOW/DENY buttons, 3-strike system\n- **Simple progression**: speed increases every 5 shoppers\n- **Score display** with combo multiplier\n- **Game over screen** with final score and restart button\n- **Visual feedback**: items glow green/red when detected, clear mistake indicators\n- **Sound effects**: scanner rotation, item detection, correct/incorrect decisions, game over\n\n### Future Iterations\n- More scanner filters (biometric, quantum, etc.)\n- More item types and combinations\n- Special \"boss\" shoppers with complex item patterns\n- Power-ups (slow time, reveal all, extra life)\n- Daily challenges\n- Leaderboards\n- Story mode with specific contraband targets\n- Customizable scanner skins\n\n## Game Perspective\n\n**Fixed-screen** with a **side-view** perspective:\n- The scanner station occupies the center of the screen\n- Shoppers approach from the top and walk toward the gate at the bottom\n- The scanner overlay covers the shopper area\n- UI elements (score, lives, buttons) frame the play area\n\n## Game World and Level Design\n\n### Screen Layout (720\u00d71280 portrait)\n\n**Top Section (0-200px)**:\n- Score display (top-left)\n- Lives remaining (top-right, shown as 3 hearts/badges)\n- Combo multiplier indicator (top-center)\n\n**Middle Section (200-900px)** - Main Play Area:\n- **Shopper Queue Zone (200-400px)**: Where next shopper waits\n- **Scanner Zone (400-700px)**: Active scanning area with filter overlay\n- **Gate Zone (700-900px)**: Security gate where shopper exits\n\n**Bottom Section (900-1280px)** - Control Area:\n- **Scanner Filter Display (900-1000px)**: Shows current filter name and icon\n- **Tap Zone (1000-1100px)**: Large \"TAP TO ROTATE FILTER\" button/area\n- **Decision Buttons (1100-1250px)**: \n  - ALLOW button (left, green)\n  - DENY button (right, red)\n  - Each button 300\u00d7120px minimum\n\n### Visual Design\n\n- **Scanner Overlay**: Semi-transparent colored overlay that changes with each filter\n  - X-ray: Blue-tinted with skeletal outlines\n  - Thermal: Orange/red heat map effect\n  - Chemical: Green with molecular patterns\n  - Electromagnetic: Purple with wave patterns\n- **Item Glow Effects**: Bright, pulsing glow (green or red) when items are detected\n- **Shopper Movement**: Smooth animation from top to gate (3-5 seconds initially, faster as game progresses)\n- **Background**: Modern mall security checkpoint with metal detector frame, conveyor belt aesthetic\n\n### Single Level Design\n\nSince this is an endless arcade game, there's one continuous level that increases in difficulty:\n- **Waves 1-5**: Single item per shopper, obvious contraband\n- **Waves 6-10**: Multiple items, mix of safe and contraband\n- **Waves 11+**: Complex combinations, faster movement, more items\n\n## Edit Mode Design\n\nPlayers can customize their scanner station and game parameters:\n\n### Editable Properties\n\n**Scanner Customization**:\n- **Filter Colors**: Change the color tint of each scanner filter\n- **Scanner Frame Style**: Choose from different scanner frame designs (modern, retro, futuristic)\n- **Detection Glow Intensity**: Adjust how bright items glow when detected\n\n**Gameplay Parameters**:\n- **Starting Speed**: How fast shoppers initially move (0.5\u00d7 to 2\u00d7)\n- **Lives Count**: Number of mistakes allowed (1-5)\n- **Score Multiplier Cap**: Maximum combo multiplier (2\u00d7 to 10\u00d7)\n- **Items Per Shopper**: Average number of items (1-5)\n\n**Visual Customization**:\n- **Background Theme**: Choose checkpoint style (modern mall, airport, border crossing, sci-fi)\n- **Shopper Variety**: Toggle different shopper character sets\n\n### Constraints\n\n- Starting speed cannot be so fast that scanning is impossible (minimum 2 seconds per shopper)\n- Lives count minimum of 1 (game must be loseable)\n- Score multiplier must be at least 1\u00d7\n- At least 2 scanner filters must be active\n\n## Control Design\n\n### Mobile (Touch) Controls\n\n**Primary Interaction - Scanner Rotation**:\n- **Tap anywhere in the middle 60% of screen** (Scanner Zone + Tap Zone): Rotate to next filter\n- Visual feedback: Filter name animates, overlay color changes, rotation sound plays\n\n**Decision Making**:\n- **Tap ALLOW button** (bottom-left): Approve shopper\n- **Tap DENY button** (bottom-right): Reject shopper\n- Buttons are large (300\u00d7120px) and thumb-friendly in bottom section\n\n**No Joysticks Needed**: This is a tap-based game with discrete actions\n\n### Desktop (Keyboard/Mouse) Controls\n\n**Primary Interaction**:\n- **SPACEBAR or LEFT CLICK anywhere in scanner area**: Rotate to next filter\n- **Mouse hover over items**: Optional subtle highlight to help identify items\n\n**Decision Making**:\n- **A key or Click ALLOW button**: Approve shopper\n- **D key or Click DENY button**: Reject shopper\n- **ENTER key**: Quick decision based on current scan (allows if no red detected)\n\n**Additional**:\n- **ESC**: Pause game\n- **R**: Restart after game over\n\n### Input Handling Details\n\n- Prevent accidental double-taps with 200ms cooldown on filter rotation\n- Disable decision buttons while shopper is transitioning (first 0.5s)\n- Clear all input states on game over\n- Show visual button press feedback (scale/color change)\n\n## Art Style and Visual Theme\n\n### Overall Style\n**Modern Security Tech with Arcade Polish** - Clean, professional security checkpoint aesthetic mixed with vibrant arcade game energy. Think \"Papers, Please\" meets \"Diner Dash\" with a contemporary mall security setting.\n\n### Color Palette\n- **Primary**: Cool grays and blues (security checkpoint feel)\n- **Accent**: Bright neon colors for scanner filters (cyan, magenta, lime, purple)\n- **Feedback**: Vivid green (safe) and red (contraband) for item detection\n- **UI**: High contrast white text on dark backgrounds for readability\n\n### Visual Elements\n\n**Scanner Effects**:\n- Semi-transparent colored overlays with subtle scan-line animations\n- Pulsing glow effects for detected items (animated, not static)\n- Smooth filter transitions with brief \"scanning\" animation\n\n**Characters (Shoppers)**:\n- Cartoon-style human figures, diverse and friendly-looking\n- Simple, clean designs that work well with scanner overlays\n- 3-4 variations (different body types, clothing, accessories)\n- Neutral expressions (not threatening)\n- Approximately 200\u00d7300px when in scanner zone\n\n**Items**:\n- Stylized but recognizable contraband and safe items\n- Clear silhouettes that read well through scanner filters\n- Approximately 40\u00d740px to 80\u00d780px depending on item type\n- Positioned naturally on shopper (pockets, bags, concealed)\n\n**Environment**:\n- Clean, modern security checkpoint\n- Metal detector frame around scanner zone\n- Subtle conveyor belt or floor markings showing movement path\n- Professional but not sterile (this is a mall, not an airport)\n\n**UI Elements**:\n- Bold, readable fonts (sans-serif)\n- Icon-based lives display (badge or heart icons)\n- Animated score counter\n- Glowing button states for ALLOW/DENY\n\n### Asset Approach\n- **Generated Assets**: Shopper characters, items, scanner frame, background elements\n- **Code Rendering**: Scanner overlay effects (CSS filters/Canvas), glow effects, UI elements, score displays, button shapes\n\n### Consistency Notes\n- All shoppers should share the same art style and level of detail\n- Items should be consistent in style and scale\n- Scanner effects should feel high-tech but not overly complex\n- Maintain clear visual hierarchy: items > shoppers > background", "persistent_storage_config": {"leaderboard": {"sort_ascending": false}}, "game_metadata": {"game_id": "01KTC4AVBW6D73RNTCMBKZ3H0H", "title": "Mall Cop Scanner", "description": "Rotate your multi-layered scanner to reveal what shoppers are hiding\u2014tap to cycle filters until contraband glows red!", "thumbnail_url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/thumbnail_6b7c4046-4a8e-4d39-a296-9184e28def04.webp", "created_at": 1780671176.0, "updated_at": 1781699984679.9236, "art_style": null}, "task_plan": null}