{"asset_map": {"background_music": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/music/background_music_32754c6d-b746-40f4-b89b-7c1caf217fa2.mp3", "type": "audio", "prompt": "Soft, calm instrumental background music for a children's puzzle game. Features a gentle piano melody, warm acoustic guitar accompaniment, and subtle orchestral string padding. C Major, 90 BPM, peaceful and encouraging mood similar to a Pixar storybook underscore. Seamlessly looping, non-intrusive, and heartwarming.", "description": "Peaceful and warm background music for a relaxing puzzle experience", "subtype": "music", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 120.0326530612245, "loop": true, "animation": null}, "piece_pickup_sfx": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/piece_pickup_sfx_0b2e8bd9-0501-45e0-95e8-0de2156bb210.mp3", "type": "audio", "prompt": "A gentle, tactile 'lift' sound effect for a cozy children's puzzle game. Soft organic whoosh combined with a subtle fabric or light wooden grab texture. Warm, inviting, and non-metallic. Very short duration with a smooth fade-out, designed to feel natural and satisfying for frequent interactions without being jarring.", "description": "A soft, tactile lifting sound for picking up a puzzle piece in a cozy game.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 1.36, "loop": false, "animation": null}, "piece_correct_sfx": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/piece_correct_sfx_e70b0675-22ea-4e2e-a62d-2e605281ddad.mp3", "type": "audio", "prompt": "A sharp, satisfying tactile click and snap sound effect. High-quality recording of a wooden or plastic puzzle piece locking perfectly into place. Clean, crisp attack with a very brief, resonant decay. Professional foley, minimalist, high-fidelity, designed for positive feedback in a relaxing puzzle game.", "description": "Short, satisfying tactile click for correct puzzle piece placement", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 0.4, "loop": false, "animation": null}, "piece_incorrect_sfx": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/piece_incorrect_sfx_fc320a09-ca84-4fa8-8d9f-2751c35317e4.mp3", "type": "audio", "prompt": "Gentle 'boop' sound effect, low pitch, soft and forgiving. Non-punishing, friendly audio feedback indicating a piece should be tried elsewhere. Muffled and round texture.", "description": "Gentle, non-punishing boop for incorrect placement", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 1.490045351473923, "loop": false, "animation": null}, "puzzle_complete_sfx": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/puzzle_complete_sfx_d4f06004-2d11-4fcf-9684-1946000f3bdb.mp3", "type": "audio", "prompt": "Cheerful musical chime sequence, ascending major scale notes, bright and joyful fanfare. Includes magical sparkle textures and high-pitched bells. Celebratory and rewarding finish for a puzzle level.", "description": "Cheerful ascending chime for completing a level", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 2.090861678004535, "loop": false, "animation": null}, "button_tap_sfx": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/button_tap_sfx_65d4016a-cbf8-4bdc-a851-fdc7d800c1e4.mp3", "type": "audio", "prompt": "Soft UI 'tap' sound effect, subtle and organic like a finger on a soft wooden surface. Minimalist, friendly, and highly responsive with a very short decay.", "description": "Soft UI tap sound for button interactions", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 1.3, "loop": false, "animation": null}, "story_reveal_sfx": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/story_reveal_sfx_47088371-b6ee-46c2-9c9b-2eb1ecfb7084.mp3", "type": "audio", "prompt": "Warm, magical 'shimmer' sound effect. Features gentle glockenspiel or wind chimes, sparkly and enchanting texture. Evokes the feeling of a storybook opening or a magical transition. Soft and wonder-filled.", "description": "Magical shimmer sound for the story transition", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 1.78, "loop": false, "animation": null}, "character_reference": {"url": "https://d16qq0o35va7kz.cloudfront.net/images/e8d113e0-b041-70a6-5237-c84075702131/1775556860625_01KNKPZ6PHHV7Z4GQVFMRQDC3G.IMG_7731.png", "type": "image", "prompt": null, "description": "Character reference image showing Milo (a young girl with curly hair in pigtails wearing a blue dress) and Lebo (a golden retriever puppy), used for maintaining character design consistency across all game illustrations. Pixar-inspired art style with the game title and puzzle pieces.", "subtype": null, "aspect_ratio": [1024, 1024], "compression_suggestion": "original", "duration_secs": null, "loop": null, "animation": null}, "level_1_scene": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/level_1_scene_7e9399db-601c-44bf-ae6e-5ed4a5505acf.webp", "type": "image", "prompt": "Transform the scene from image 1 to feature a chocolate Labrador mother nurturing a diverse litter of newborn puppies. The litter must include yellow, chocolate (including Milo), and black Labrador puppies, with at least 5 puppies total. The puppies should be small newborns with closed eyes, cuddled together. Set in a cozy barn with warm, soft lighting. Maintain the clean, polished Pixar-inspired art style from image 2. Ensure no text, no puzzle pieces, and no UI elements are present in the final illustration.", "description": "A heartwarming Level 1 puzzle scene showing a mother Labrador with a diverse litter of puppies in a cozy barn.", "subtype": "background", "aspect_ratio": [1024, 1024], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "bee_logo": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/bee_logo_f574ee59-dc5e-41d1-97c8-2e774cfc1705.webp", "type": "image", "prompt": "Generate a Pixar-inspired illustrated bee character logo. The bee should have soft shading, expressive cute facial features, large friendly eyes, and rounded forms. Use warm, cozy lighting to match a children's puzzle game aesthetic. High resolution, 3D rendered look with a clean, professional finish. Match the artistic style and quality of character_reference. Transparent background.", "description": "A friendly, cute bee character logo in a Pixar-inspired style for the game's branding.", "subtype": "ui", "aspect_ratio": [790, 815], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "Original.png": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/IMG_7731_png_dd110bbb-91ae-409a-8f22-a56c85ba28c5.webp", "type": "image", "prompt": "Transform the 3:2 landscape title screen into a 2:3 portrait orientation image. Reframe the composition to be a 9:16 tall image while keeping the girl Milo with curly pigtails, the golden retriever puppy Lebo, and the game title clearly visible and centered. Maintain the same Pixar-inspired art style, vibrant colors, and whimsical mood from the original image. Ensure no important elements are cut off by the vertical crop.", "description": "Portrait orientation version of the title screen featuring Milo, Lebo, and the game logo.", "subtype": "background", "aspect_ratio": [832, 1248], "compression_suggestion": "original", "duration_secs": null, "loop": null, "animation": null}, "level_8_scene": {"url": "https://d16qq0o35va7kz.cloudfront.net/images/e8d113e0-b041-70a6-5237-c84075702131/1776167843116_01KP5XMX9C953FZ9ZDXGPG0QWK.IMG-20260414-WA0002.jpg", "type": "image", "prompt": null, "description": "Chapter 8 puzzle scene showing Lebo in blue dress outfit meeting Milo for the first time, updated version", "subtype": null, "aspect_ratio": [1024, 1024], "compression_suggestion": "original", "duration_secs": null, "loop": null, "animation": null}, "level_9_scene": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/level_9_scene_a01636a1-e333-455f-8b1d-2cee6c5cfc33.webp", "type": "image", "prompt": "A clean, Pixar-style digital illustration of a joyful scene in a sun-drenched meadow. The scene features Lebo, a young African girl with curly afro puff pigtails adorned with blue and white beads, wearing a simple blue dress. She is playing joyfully with Milo, a small CHOCOLATE LABRADOR puppy with rich brown fur wearing a bright blue collar (#3A8EDB). The art style should have soft shading, warm golden-hour lighting, and expressive, soulful eyes. The image must be a pure illustration with NO text, NO captions, NO UI elements, and NO jigsaw puzzle piece overlays. Match the character designs and artistic style of image 1 and image 2.", "description": "Level 9 jigsaw puzzle scene featuring Milo the chocolate labrador and Lebo playing in a meadow.", "subtype": "background", "aspect_ratio": [832, 1248], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "level_2_scene": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/level_2_scene_3ce0c7c8-5c82-4230-ad06-16b6575900bb.webp", "type": "image", "prompt": "Transform level_2_scene to feature a diverse litter of Labrador puppies\u2014yellow, chocolate, and black\u2014playing together in a lush green meadow. Based on the puppies in level_1_scene, show them in dynamic and realistic play poses: wrestling, mid-leap, and tumbling over each other. Use a Pixar-inspired illustration style with clean lines, soft lighting, and vibrant colors. The composition should be energetic and joyful, suitable for a jigsaw puzzle, maintaining the same artistic DNA as character_reference.", "description": "A dynamic Pixar-style jigsaw puzzle scene of a diverse litter of Labrador puppies playing energetically in the grass.", "subtype": "background", "aspect_ratio": [1024, 1024], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "level_3_scene": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/level_3_scene_b4d4c292-65e3-4db2-973e-2eca98f40a50.webp", "type": "image", "prompt": "Modify the composition of image 1: Move the family carrying two puppies much closer to the foreground, walking away from the camera. Milo (a chocolate labrador) and a yellow lab puppy remain in the background on a porch, watching with calm, neutral expressions. Maintain the Pixar-inspired art style, lighting, and character designs from image 1 and image 2. High resolution, 3D render style, cinematic lighting, detailed environment.", "description": "A Pixar-style jigsaw puzzle scene showing a family walking away in the foreground while Milo and a puppy watch from a porch.", "subtype": "background", "aspect_ratio": [1024, 1024], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "level_4_scene": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/level_4_scene_c6d30a1c-ae49-4b28-8308-63a2c7cdc03b.webp", "type": "image", "prompt": "Zoom out and expand the bottom of the image in image 1 to show Lebo's full body and legs. The character Lebo must be entirely in the frame and not cut off at the bottom. Maintain the exact character design, clothing, and the lush park environment from image 1. Keep the Pixar-inspired art style and the existing 9:16 aspect ratio. Do not alter Lebo's appearance, only extend the ground and lower body detail.", "description": "An extended version of the Level 4 park scene with Lebo's full body visible, ensuring she is not cut off at the bottom.", "subtype": "background", "aspect_ratio": [832, 1248], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "level_5_scene": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/level_5_scene_2333b78f-b879-4a1d-b58c-d0cf2a4acfb1.webp", "type": "image", "prompt": "Extend the bottom of the image by 20% using generative fill to create a natural continuation of the ground. Maintain the Pixar-inspired illustration style, specifically continuing the textures of the grass and soil from image 1. Ensure the colors, lighting, and brushwork seamlessly blend with the existing scene featuring the father and characters, ensuring no elements look cut off at the bottom edge.", "description": "An extended version of the Level 5 puzzle scene with added bottom area for better framing.", "subtype": "background", "aspect_ratio": [832, 1248], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "level_7_scene": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/level_6_scene_923ce655-9874-46ef-9910-cd7b4f70fb86.webp", "type": "image", "prompt": "Generate a clean, high-resolution Pixar-inspired illustration for a jigsaw puzzle scene. In the foreground, Lebo (a young African American girl with curly hair in afro puffs, blue and white hair beads, and a blue dress with white polka dots) is arriving at a farm. Her dad is partially visible beside her, cropped at the edge of the frame. In the middle distance, Milo (a chocolate/golden labrador puppy wearing a yellow bow tie collar) is looking towards them, catching their eye for the first time. The setting is a charming farm with a barn and wooden fences, bathed in warm, golden hour lighting with soft shading. No text, no UI, no puzzle pieces. Maintain character consistency with image 1.", "description": "Level 6 jigsaw puzzle scene showing Lebo and her dad arriving at the farm and catching their first glimpse of Milo the puppy.", "subtype": "background", "aspect_ratio": [1024, 1024], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "level_6_scene": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/level_6_scene_59aecf1a-a86c-4c5b-8509-c37a90a54113.webp", "type": "image", "prompt": "Extend the bottom of the image by 20% to show more of the porch floor and the ground. Seamlessly continue the Pixar-inspired illustration style of image 1. Maintain the consistent lighting, soft textures, and heartwarming atmosphere. Ensure the wooden porch planks and the farm ground transition naturally without any visible seams or distortion, keeping the sad chocolate labrador puppy 'Milo' as the central focus.", "description": "An extended version of the level 6 scene showing Milo the puppy on a porch, providing more vertical space at the bottom.", "subtype": "background", "aspect_ratio": [832, 1248], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "level_10a_scene": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/level_10_scene_402bb50b-9bf1-4caa-b457-5d4096359ee1.webp", "type": "image", "prompt": "Transform the scene in image 1 to remove all text, titles, captions, and jigsaw puzzle overlays. Change the puppy (Milo) from golden to a chocolate Labrador with deep brown fur, matching the puppy in image 2. Ensure the girl (Lebo) matches her established design from image 2 and image 3: an African girl with curly afro puff pigtails, blue and white beads, and a blue dress. Milo must be wearing his signature blue collar. The setting is a cozy porch swing during a golden hour sunset. Pixar-inspired art style with soft shading, warm golden tones, and high detail. Clean illustration with no UI elements.", "description": "Heartwarming finale jigsaw puzzle scene of Lebo hugging Milo on a porch swing at sunset, corrected for character consistency.", "subtype": "background", "aspect_ratio": [1024, 1024], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "level_10_scene": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/level_10_scene_4dfb01b9-e356-48d7-a53e-c7abea49bf67.webp", "type": "image", "prompt": "Extend the bottom of image 1 by 20% to create a taller composition. Seamlessly continue the sidewalk and city street texture downwards, maintaining the Pixar-inspired 3D illustration style, warm lighting, and color palette. The transition must be invisible, ensuring the ground looks natural and consistent with the existing scene.", "description": "An extended version of the level 10 scene showing teenage Lebo walking adult Milo, providing more vertical space at the bottom for better framing.", "subtype": "background", "aspect_ratio": [832, 1248], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "star_reveal_sfx": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/star_reveal_sfx_601c915a-b9b3-4547-86cc-baf0b4a8cb8c.mp3", "type": "audio", "prompt": "A magical, delicate sparkle and twinkling chime sound effect for a cozy puzzle game. Features soft, high-pitched glockenspiel bells and a gentle fairy dust shimmer. The sound should be warm and rewarding with a soft attack and a clean, crystalline decay. One-shot, child-friendly, non-jarring, and harmonic with a peaceful atmosphere. Low intensity, high-frequency twinkles.", "description": "A magical, delicate sparkle chime for when a gold star is revealed in a puzzle game.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 0.95, "loop": false, "animation": null}, "star_icon": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/star_icon_a2065b3f-1418-4dbe-bbf3-9472e24023be.webp", "type": "image", "prompt": "Generate a high-quality 5-pointed golden star icon for a children's puzzle game. The star features a warm golden-to-orange gradient, soft rounded points with a 3D pillowy and plump appearance, and subtle glossy shine highlights. Pixar-inspired polished style with a cozy storybook aesthetic, clean edges, soft inner glow, and professional depth. Transparent background, high resolution, centered composition.", "description": "A premium golden 3D star icon for the puzzle completion reward system, matching the Pixar-inspired aesthetic.", "subtype": "ui", "aspect_ratio": [632, 612], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "outfit_button": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/outfit_button_1c4972d7-8025-418d-a076-dbea257717ff.webp", "type": "image", "prompt": "Remove the white background from the image to make it transparent, while perfectly preserving the clothing tag and the blue polka dot dress icon inside it. High-fidelity extraction of the UI element, keeping the Pixar-inspired 3D style and clean edges.", "description": "A UI button featuring a clothing tag with a blue polka dot dress icon, with a transparent background.", "subtype": "ui", "aspect_ratio": [536, 818], "compression_suggestion": "original", "duration_secs": null, "loop": null, "animation": null}, "outfit_blue_dress": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/outfit_blue_dress_c8ec0423-5edb-4527-ac52-f9dff5b9e64a.webp", "type": "image", "prompt": "Remove the white background and make it fully transparent. Keep the characters Lebo (the young girl with curly pigtails in a blue polka dot dress) and Milo (the golden retriever puppy) exactly as they appear in image 1, ensuring clean edges and a professional cutout.", "description": "Lebo and Milo in a blue dress outfit with a transparent background for UI or character selection", "subtype": "character", "aspect_ratio": [637, 831], "compression_suggestion": "original", "duration_secs": null, "loop": null, "animation": null}, "outfit_overalls": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/outfit_overalls_cbdadbc7-4b6c-4b67-8a56-9c6768de4d6a.webp", "type": "image", "prompt": "Remove the white background and make it fully transparent. Keep the characters Lebo (the girl in denim overalls, yellow shirt, and green backpack) and Milo (the chocolate labrador puppy) exactly as they are in the input image. Maintain the polished Pixar-inspired 3D art style and character consistency.", "description": "Lebo in denim overalls and Milo the puppy with a transparent background for character selection", "subtype": "character", "aspect_ratio": [551, 768], "compression_suggestion": "original", "duration_secs": null, "loop": null, "animation": null}, "lebo_purple_shirt": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/lebo_purple_shirt_e125f0f3-7cd9-4089-b58d-163be83fd00e.webp", "type": "image", "prompt": "Remove the background from the image of Lebo in the purple shirt with the monkey graphic, making it perfectly transparent while preserving the full character silhouette and detail of the clothing. High-quality Pixar-inspired 3D style.", "description": "Character portrait of Lebo wearing the purple monkey shirt with a transparent background for UI use.", "subtype": "character", "aspect_ratio": [414, 960], "compression_suggestion": "original", "duration_secs": null, "loop": null, "animation": null}, "milo_purple_collar": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/milo_purple_collar_f1b559f8-d88e-44a3-8e87-c91f9c1c9550.webp", "type": "image", "prompt": "Remove the background from the image of Milo the puppy to make it perfectly transparent, keeping only the puppy and his purple collar. High resolution, clean edges, Pixar-inspired 3D style consistency.", "description": "Milo the chocolate labrador puppy wearing a purple collar with a transparent background.", "subtype": "character", "aspect_ratio": [665, 807], "compression_suggestion": "original", "duration_secs": null, "loop": null, "animation": null}, "level_4_scene_overalls": {"url": "https://d16qq0o35va7kz.cloudfront.net/images/e8d113e0-b041-70a6-5237-c84075702131/1775885993671_01KNXGVHP799EM8F4VTV8V4WA3.4436E074-97BB-41EB-ABE6-72BC8E4FE2F6.png", "type": "image", "prompt": null, "description": "Chapter 4 puzzle scene showing Lebo in overalls outfit at the park, alternate version for outfit variant selection", "subtype": null, "aspect_ratio": [1024, 1024], "compression_suggestion": "original", "duration_secs": null, "loop": null, "animation": null}, "level_4_scene_purple": {"url": "https://d16qq0o35va7kz.cloudfront.net/images/e8d113e0-b041-70a6-5237-c84075702131/1779771506976_01KSHAC090R7GC1MJAMCE44AXR.IMG_8208.png", "type": "image", "prompt": null, "description": "Chapter 4 puzzle scene showing Lebo in purple shirt outfit at the park, alternate version for outfit variant selection", "subtype": null, "aspect_ratio": [1024, 1024], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "level_1_scene_purple": {"url": "https://d16qq0o35va7kz.cloudfront.net/images/e8d113e0-b041-70a6-5237-c84075702131/1775890347679_01KNXN0DMZW4ZS2MH6D0EXCTBG.IMG_7776.png", "type": "image", "prompt": null, "description": "Chapter 1 puzzle scene showing Lebo in purple shirt outfit meeting baby Milo the puppy in a sunny field, alternate outfit variant", "subtype": null, "aspect_ratio": [1024, 1024], "compression_suggestion": "original", "duration_secs": null, "loop": null, "animation": null}, "level_7_scene_purple": {"url": "https://d16qq0o35va7kz.cloudfront.net/images/e8d113e0-b041-70a6-5237-c84075702131/1775890353991_01KNXN0KT71CNYJ82TG478486Q.IMG_7777.png", "type": "image", "prompt": null, "description": "Chapter 7 puzzle scene showing Lebo in purple shirt outfit with her dad arriving at the farm to meet Milo, alternate outfit variant", "subtype": null, "aspect_ratio": [1024, 1024], "compression_suggestion": "original", "duration_secs": null, "loop": null, "animation": null}, "level_7_scene_overalls": {"url": "https://d16qq0o35va7kz.cloudfront.net/images/e8d113e0-b041-70a6-5237-c84075702131/1775890359208_01KNXN0RX85C9JQ477YSB4HKAS.IMG_7778.png", "type": "image", "prompt": null, "description": "Chapter 7 puzzle scene showing Lebo in overalls outfit with her dad arriving at the farm to meet Milo, alternate outfit variant", "subtype": null, "aspect_ratio": [1024, 1024], "compression_suggestion": "original", "duration_secs": null, "loop": null, "animation": null}, "level_8_scene_purple": {"url": "https://d16qq0o35va7kz.cloudfront.net/images/e8d113e0-b041-70a6-5237-c84075702131/1776167819063_01KP5XM5SQ0QX89EX29H3HQBGB.IMG-20260414-WA0003.jpg", "type": "image", "prompt": null, "description": "Chapter 8 puzzle scene showing Lebo in purple shirt outfit meeting Milo for the first time, alternate outfit variant", "subtype": null, "aspect_ratio": [1024, 1024], "compression_suggestion": "original", "duration_secs": null, "loop": null, "animation": null}, "level_8_scene_overalls": {"url": "https://d16qq0o35va7kz.cloudfront.net/images/e8d113e0-b041-70a6-5237-c84075702131/1776167835207_01KP5XMNJ7HWDY4SD4FWY5MSYH.IMG-20260414-WA0001.jpg", "type": "image", "prompt": null, "description": "Chapter 8 puzzle scene showing Lebo in overalls outfit meeting Milo for the first time, alternate outfit variant", "subtype": null, "aspect_ratio": [1024, 1024], "compression_suggestion": "original", "duration_secs": null, "loop": null, "animation": null}, "level_5_scene_overalls": {"url": "https://d16qq0o35va7kz.cloudfront.net/images/e8d113e0-b041-70a6-5237-c84075702131/1776168026906_01KP5XTGRT1TPQ7755K1BAHB6N.IMG-20260414-WA0000.jpg", "type": "image", "prompt": null, "description": "Chapter 5 puzzle scene showing Lebo in overalls outfit with her parents at the park, alternate outfit variant", "subtype": null, "aspect_ratio": [1024, 1024], "compression_suggestion": "original", "duration_secs": null, "loop": null, "animation": null}, "outfit_purple_combined": {"url": "https://d16qq0o35va7kz.cloudfront.net/images/e8d113e0-b041-70a6-5237-c84075702131/1779707430735_01KSFD8HTFDMYX3CDGSR39Z6E6.IMG_7770.png", "type": "image", "prompt": null, "description": "Combined character portrait showing Lebo wearing the purple monkey shirt and Milo the puppy with purple collar, for use as the purple outfit button in the outfit picker UI", "subtype": null, "aspect_ratio": [1536, 1024], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "custom_lock_icon": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/custom_lock_icon_7bdc0a55-5e81-4315-b843-8b6d5851ee00.webp", "type": "image", "prompt": "Generate a professional, high-quality UI icon of a friendly padlock. The lock features a warm golden-yellow color with a 3D shiny appearance, rounded soft edges, and a heart-shaped keyhole in the center. Designed in a Pixar-inspired aesthetic with subtle highlights and soft shadows for depth. Clean, polished, game-ready asset, transparent background, front view, simple and charming storybook style.", "description": "A cute, friendly golden heart-keyhole padlock icon in a Pixar-inspired 3D style for UI.", "subtype": "ui", "aspect_ratio": [532, 733], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "play_button_icon": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/play_button_icon_dcbb1d8d-2608-4c47-84a0-806c62059203.webp", "type": "image", "prompt": "Generate a professional 3D play button icon for a children's game, featuring a rounded triangular symbol pointing right. Use a vibrant golden yellow gradient matching the aesthetic of image 1. The style should be Pixar-inspired with soft shadows, high-quality rim lighting, and a polished, dimensional finish. Ensure clean edges and a friendly, premium feel. Transparent background, high resolution, game-ready UI asset.", "description": "A premium golden 3D play button icon with a Pixar-inspired aesthetic for the UI.", "subtype": "ui", "aspect_ratio": [596, 665], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "book_icon": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/book_icon_66778b03-4330-44c1-84a0-1e3c97a0b469.webp", "type": "image", "prompt": "Generate a premium golden 3D storybook icon, slightly open with warm golden pages visible. The book has a soft leather-like cover in rich amber and warm brown tones. Feature subtle magical sparkles and tiny stars floating around it. Use a Pixar-inspired 3D art style with soft rounded edges, warm lighting, and a cozy heartwarming aesthetic. Match the visual quality and material of the existing golden star and play button icons. High resolution, professional game asset, transparent background.", "description": "A premium golden 3D storybook icon for the UI with a magical fairy tale aesthetic.", "subtype": "ui", "aspect_ratio": [807, 782], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "dress_icon": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/dress_icon_3631b795-d65a-4ec7-94a5-f37fc70805d2.webp", "type": "image", "prompt": "Generate a high-quality 3D icon of a child's blue polka dot sundress, friendly and cute Pixar-inspired style. The dress has soft rounded shapes, warm inviting lighting, and a slight premium glow. Match the artistic style of the character Lebo's outfit in image 1. Professional game UI asset, clean edges, soft shading, vibrant colors, transparent background.", "description": "A cute 3D blue polka dot dress icon for the outfit picker UI, matching the Pixar-inspired aesthetic.", "subtype": "ui", "aspect_ratio": [624, 642], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "level_5_scene_purple": {"url": "https://d16qq0o35va7kz.cloudfront.net/images/e8d113e0-b041-70a6-5237-c84075702131/1779772231163_01KSHB23FVMBRZHG6R879AD9YT.IMG_8200.png", "type": "image", "prompt": null, "description": "Chapter 5 puzzle scene showing Lebo in purple shirt outfit with her parents at the park, alternate outfit variant", "subtype": null, "aspect_ratio": [1024, 1536], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "level_10a_scene_purple": {"url": "https://d16qq0o35va7kz.cloudfront.net/images/e8d113e0-b041-70a6-5237-c84075702131/1779742931976_01KSGF3Z08B8Q4HPH0NQAMQ746.IMG_8204.png", "type": "image", "prompt": null, "description": "Chapter 9 puzzle scene showing Lebo in yellow shirt with green overalls/backpack hugging Milo on a porch swing at sunset, alternate outfit variant for \"Never Alone Again\"", "subtype": null, "aspect_ratio": [1536, 1024], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "level_10a_scene_overalls": {"url": "https://d16qq0o35va7kz.cloudfront.net/images/e8d113e0-b041-70a6-5237-c84075702131/1779742931769_01KSGF3YSSR10WEJ31DBQR69RV.IMG_8205.png", "type": "image", "prompt": null, "description": "Chapter 9 puzzle scene showing Lebo in purple shirt outfit hugging Milo on a porch swing at sunset, alternate outfit variant for \"Never Alone Again\"", "subtype": null, "aspect_ratio": [1536, 1024], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}}, "game_code": "<!DOCTYPE html>\n<html>\n<head>\n    <title>Milo & Lebo's Puzzle Story</title>\n    <link href=\"https://fonts.googleapis.com/css2?family=Fredoka:wght@600;700&family=Nunito:wght@400;600;700;800&display=swap\" rel=\"stylesheet\">\n    <style>\n        :root {\n            --primary-blue: #4A90D9;\n            --primary-yellow: #F5C542;\n            --primary-green: #7BC47F;\n            --bg-cream: #FFF8E7;\n            --text-dark: #3D3D3D;\n            --text-light: #FFFFFF;\n            --shadow-soft: 0 4px 8px rgba(0,0,0,0.1);\n            --shadow-medium: 0 6px 12px rgba(0,0,0,0.15);\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        }\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: 'Nunito', sans-serif;\n            background: var(--bg-cream);\n        }\n\n        #game-container {\n            position: relative;\n            width: 100%;\n            height: 100%;\n            display: flex;\n            flex-direction: column;\n            background: linear-gradient(180deg, #87CEEB 0%, #E0F4FF 50%, var(--bg-cream) 100%);\n        }\n\n        /* Title Screen - AAA Premium Design */\n        #title-screen {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            justify-content: flex-end;\n            box-sizing: border-box;\n            overflow: hidden;\n        }\n\n        .title-hero-image {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            object-fit: cover;\n            object-position: center top;\n            z-index: 0;\n        }\n\n        .title-gradient-overlay {\n            position: absolute;\n            bottom: 0;\n            left: 0;\n            width: 100%;\n            height: 55%;\n            background: linear-gradient(\n                0deg,\n                rgba(0, 0, 0, 0.85) 0%,\n                rgba(0, 0, 0, 0.7) 25%,\n                rgba(0, 0, 0, 0.4) 50%,\n                rgba(0, 0, 0, 0.1) 75%,\n                transparent 100%\n            );\n            z-index: 1;\n            pointer-events: none;\n        }\n\n        .title-buttons-container {\n            position: relative;\n            z-index: 2;\n            width: 100%;\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            justify-content: flex-end;\n            padding: 0 30px 60px 30px;\n            box-sizing: border-box;\n            gap: 16px;\n        }\n\n        .title-menu-button {\n            width: 100%;\n            max-width: 420px;\n            padding: 20px 28px;\n            border-radius: 20px;\n            font-family: 'Fredoka', 'Nunito', sans-serif;\n            font-size: 26px;\n            font-weight: 700;\n            cursor: pointer;\n            transition: transform var(--transition-fast), box-shadow var(--transition-fast);\n            border: none;\n            text-align: center;\n            letter-spacing: 0.5px;\n        }\n\n        .title-menu-button:active {\n            transform: scale(0.96);\n        }\n\n        .title-menu-button.primary {\n            background: linear-gradient(180deg, #FFE066 0%, #F5C542 50%, #E8B22E 100%);\n            color: var(--text-dark);\n            box-shadow: \n                0 6px 0 #C9962A,\n                0 8px 20px rgba(0,0,0,0.4),\n                inset 0 2px 0 rgba(255,255,255,0.4);\n            text-shadow: 0 1px 2px rgba(0,0,0,0.1);\n        }\n\n        .title-menu-button.primary:active {\n            box-shadow: \n                0 3px 0 #C9962A,\n                0 4px 10px rgba(0,0,0,0.3),\n                inset 0 2px 0 rgba(255,255,255,0.4);\n            transform: translateY(3px) scale(0.98);\n        }\n\n        .title-menu-button.secondary {\n            background: rgba(255, 255, 255, 0.95);\n            color: var(--primary-blue);\n            border: none;\n            box-shadow: \n                0 6px 0 rgba(74, 144, 217, 0.4),\n                0 8px 20px rgba(0,0,0,0.3),\n                inset 0 2px 0 rgba(255,255,255,0.8);\n            text-shadow: 0 1px 2px rgba(74, 144, 217, 0.2);\n        }\n\n        .title-menu-button.secondary:active {\n            box-shadow: \n                0 3px 0 rgba(74, 144, 217, 0.4),\n                0 4px 10px rgba(0,0,0,0.2),\n                inset 0 2px 0 rgba(255,255,255,0.8);\n            transform: translateY(3px) scale(0.98);\n        }\n\n        .title-menu-button.tertiary {\n            background: rgba(255, 255, 255, 0.15);\n            backdrop-filter: blur(8px);\n            -webkit-backdrop-filter: blur(8px);\n            color: white;\n            font-size: 22px;\n            padding: 16px 28px;\n            box-shadow: \n                0 4px 16px rgba(0,0,0,0.2),\n                inset 0 1px 0 rgba(255,255,255,0.2);\n            text-shadow: 0 2px 4px rgba(0,0,0,0.3);\n            border: 1px solid rgba(255, 255, 255, 0.25);\n        }\n\n        .title-menu-button.tertiary:active {\n            background: rgba(255, 255, 255, 0.25);\n            transform: scale(0.96);\n        }\n\n        /* Chapters Screen */\n        #chapters-screen {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            display: none;\n            flex-direction: column;\n            align-items: center;\n            padding: 0;\n            box-sizing: border-box;\n            overflow-y: auto;\n            overflow-x: hidden;\n            background: linear-gradient(180deg, #7EC8E3 0%, #A8D8A8 35%, #7CB87C 65%, #5A9A5A 100%);\n        }\n\n        .chapters-bg-decorations {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            pointer-events: none;\n            overflow: hidden;\n            z-index: 0;\n        }\n\n        .chapters-cloud {\n            position: absolute;\n            background: rgba(255, 255, 255, 0.6);\n            border-radius: 50px;\n            filter: blur(2px);\n        }\n\n        .chapters-cloud-1 {\n            width: 120px;\n            height: 40px;\n            top: 8%;\n            left: 10%;\n            animation: float-gentle 20s ease-in-out infinite;\n        }\n\n        .chapters-cloud-2 {\n            width: 180px;\n            height: 50px;\n            top: 15%;\n            right: 5%;\n            animation: float-gentle 25s ease-in-out infinite reverse;\n        }\n\n        .chapters-cloud-3 {\n            width: 100px;\n            height: 35px;\n            top: 22%;\n            left: 60%;\n            opacity: 0.5;\n            animation: float-gentle 18s ease-in-out infinite 5s;\n        }\n\n        @keyframes float-gentle {\n            0%, 100% { transform: translateX(0) translateY(0); }\n            25% { transform: translateX(15px) translateY(-5px); }\n            50% { transform: translateX(5px) translateY(5px); }\n            75% { transform: translateX(-10px) translateY(-3px); }\n        }\n\n        .chapters-header {\n            width: 100%;\n            padding: 24px 20px 16px 20px;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            position: relative;\n            box-sizing: border-box;\n            z-index: 10;\n        }\n\n        .chapters-back-button {\n            position: absolute;\n            left: 20px;\n            top: 50%;\n            transform: translateY(-50%);\n            width: 52px;\n            height: 52px;\n            border-radius: 50%;\n            background: linear-gradient(180deg, #FFFFFF 0%, #F0F0F0 100%);\n            border: none;\n            box-shadow: \n                0 4px 0 rgba(0,0,0,0.1),\n                0 6px 20px rgba(0,0,0,0.15);\n            cursor: pointer;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            font-size: 26px;\n            color: var(--primary-blue);\n            transition: all var(--transition-fast);\n        }\n\n        .chapters-back-button:active {\n            transform: translateY(-50%) translateY(2px) scale(0.95);\n            box-shadow: \n                0 2px 0 rgba(0,0,0,0.1),\n                0 3px 10px rgba(0,0,0,0.1);\n        }\n\n        .chapters-title {\n            font-family: 'Fredoka', 'Nunito', sans-serif;\n            font-size: 38px;\n            font-weight: 700;\n            color: white;\n            text-align: center;\n            text-shadow: \n                0 3px 0 rgba(74, 144, 217, 0.4),\n                0 6px 20px rgba(0,0,0,0.2);\n            margin: 0;\n            letter-spacing: 1px;\n        }\n\n        .level-grid {\n            display: flex;\n            flex-direction: column;\n            gap: 18px;\n            width: 100%;\n            max-width: 680px;\n            padding: 16px 24px 40px 24px;\n            box-sizing: border-box;\n            position: relative;\n            z-index: 1;\n        }\n\n        .level-card {\n            position: relative;\n            width: 100%;\n            height: 140px;\n            border-radius: 20px;\n            overflow: hidden;\n            box-shadow: \n                0 6px 0 rgba(0,0,0,0.08),\n                0 10px 30px rgba(0,0,0,0.2);\n            cursor: pointer;\n            transition: all var(--transition-normal) var(--ease-out);\n            background: linear-gradient(135deg, #E8E4DC 0%, #D4CFC4 100%);\n            display: flex;\n            align-items: center;\n            border: 3px solid rgba(255,255,255,0.3);\n        }\n\n        .level-card:active {\n            transform: scale(0.97) translateY(2px);\n            box-shadow: \n                0 3px 0 rgba(0,0,0,0.08),\n                0 5px 15px rgba(0,0,0,0.15);\n        }\n\n        .level-card.locked {\n            filter: none;\n            cursor: not-allowed;\n            opacity: 0.85;\n        }\n\n        .level-card.locked .level-card-overlay {\n            background: linear-gradient(90deg, rgba(80,60,40,0.6) 0%, rgba(80,60,40,0.35) 60%, rgba(80,60,40,0.15) 100%);\n        }\n\n        .level-card.completed {\n            border-color: rgba(123, 196, 127, 0.5);\n        }\n\n        .level-card-image {\n            width: 100%;\n            height: 100%;\n            object-fit: cover;\n            position: absolute;\n            top: 0;\n            left: 0;\n        }\n\n        .level-card-overlay {\n            position: relative;\n            z-index: 2;\n            padding: 20px 24px;\n            color: white;\n            display: flex;\n            flex-direction: column;\n            justify-content: center;\n            width: 100%;\n            height: 100%;\n            background: linear-gradient(90deg, rgba(0,0,0,0.7) 0%, rgba(0,0,0,0.4) 60%, transparent 100%);\n            box-sizing: border-box;\n        }\n\n        .level-card-number {\n            font-size: 14px;\n            font-weight: 800;\n            opacity: 0.95;\n            text-transform: uppercase;\n            letter-spacing: 2px;\n            text-shadow: 0 1px 3px rgba(0,0,0,0.3);\n        }\n\n        .level-card-title {\n            font-family: 'Fredoka', 'Nunito', sans-serif;\n            font-size: 24px;\n            font-weight: 600;\n            margin-top: 6px;\n            text-shadow: 0 2px 4px rgba(0,0,0,0.3);\n            max-width: 70%;\n            line-height: 1.2;\n        }\n\n        .level-card-status {\n            position: absolute;\n            top: 50%;\n            right: 20px;\n            transform: translateY(-50%);\n            width: 52px;\n            height: 52px;\n            border-radius: 50%;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            font-size: 24px;\n            box-shadow: \n                0 4px 0 rgba(0,0,0,0.15),\n                0 6px 15px rgba(0,0,0,0.2);\n            z-index: 3;\n            border: 3px solid rgba(255,255,255,0.3);\n        }\n\n        .level-card-status.completed {\n            background: linear-gradient(180deg, #8CD790 0%, #5CB85C 100%);\n            color: white;\n        }\n\n        .level-card-status.locked {\n            background: linear-gradient(180deg, #FFFFFF 0%, #F5F5F5 50%, #EBEBEB 100%);\n            color: transparent;\n            overflow: hidden;\n        }\n\n        .level-card-status.locked img {\n            width: 28px;\n            height: 28px;\n            object-fit: contain;\n            filter: drop-shadow(0 1px 2px rgba(0,0,0,0.2));\n        }\n\n        .level-card-status.playable {\n            background: linear-gradient(180deg, #FFFFFF 0%, #F5F5F5 100%);\n            color: var(--text-dark);\n            animation: pulse-play 2s ease-in-out infinite;\n        }\n\n        @keyframes pulse-play {\n            0%, 100% { transform: translateY(-50%) scale(1); box-shadow: 0 4px 0 rgba(0,0,0,0.15), 0 6px 15px rgba(0,0,0,0.2); }\n            50% { transform: translateY(-50%) scale(1.08); box-shadow: 0 4px 0 rgba(0,0,0,0.15), 0 8px 25px rgba(245,197,66,0.4); }\n        }\n\n        /* Game Screen - AAA Premium Design */\n        #game-screen {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            display: none;\n            flex-direction: column;\n            background: linear-gradient(180deg, #7EC8E3 0%, #B8E4F0 30%, #E8F4E5 60%, #A8D8A8 100%);\n            overflow: hidden;\n        }\n\n        .game-screen-decorations {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            pointer-events: none;\n            overflow: hidden;\n            z-index: 0;\n        }\n\n        .game-cloud {\n            position: absolute;\n            background: rgba(255, 255, 255, 0.5);\n            border-radius: 50px;\n            filter: blur(1px);\n        }\n\n        .game-cloud-1 {\n            width: 100px;\n            height: 35px;\n            top: 12%;\n            left: 5%;\n            animation: float-gentle 22s ease-in-out infinite;\n        }\n\n        .game-cloud-2 {\n            width: 140px;\n            height: 45px;\n            top: 8%;\n            right: 10%;\n            animation: float-gentle 28s ease-in-out infinite reverse;\n        }\n\n        .header {\n            height: 8%;\n            min-height: 70px;\n            padding: 0 20px;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            position: relative;\n            box-sizing: border-box;\n            z-index: 10;\n            background: linear-gradient(180deg, rgba(255,255,255,0.4) 0%, rgba(255,255,255,0.1) 100%);\n            backdrop-filter: blur(8px);\n            -webkit-backdrop-filter: blur(8px);\n            border-bottom: 1px solid rgba(255,255,255,0.3);\n        }\n\n        .back-button {\n            position: absolute;\n            left: 16px;\n            top: 50%;\n            transform: translateY(-50%);\n            width: 48px;\n            height: 48px;\n            min-width: 48px;\n            border-radius: 50%;\n            background: linear-gradient(180deg, #FFFFFF 0%, #F0F0F0 100%);\n            border: none;\n            box-shadow: \n                0 4px 0 rgba(0,0,0,0.1),\n                0 6px 16px rgba(0,0,0,0.15);\n            cursor: pointer;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            font-size: 24px;\n            color: var(--primary-blue);\n            transition: all var(--transition-fast);\n        }\n\n        .back-button:active {\n            transform: translateY(-50%) translateY(2px) scale(0.95);\n            box-shadow: \n                0 2px 0 rgba(0,0,0,0.1),\n                0 3px 8px rgba(0,0,0,0.1);\n        }\n\n        .outfit-button {\n            position: absolute;\n            right: 16px;\n            top: 50%;\n            transform: translateY(-50%);\n            width: 56px;\n            height: 70px;\n            background: transparent;\n            border: none;\n            cursor: pointer;\n            padding: 0;\n            transition: transform var(--transition-fast);\n            filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));\n        }\n\n        .outfit-button:active {\n            transform: translateY(-50%) scale(0.9);\n        }\n\n        .outfit-button img {\n            width: 100%;\n            height: 100%;\n            object-fit: contain;\n        }\n\n        /* Outfit Picker Popup - Premium Design */\n        #outfit-popup {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            background: linear-gradient(135deg, rgba(74, 144, 217, 0.4) 0%, rgba(155, 89, 182, 0.4) 50%, rgba(245, 197, 66, 0.3) 100%);\n            z-index: 2000;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            flex-direction: column;\n            padding: 20px;\n            box-sizing: border-box;\n            backdrop-filter: blur(12px);\n            -webkit-backdrop-filter: blur(12px);\n            animation: popup-fade-in 0.3s ease-out;\n        }\n\n        @keyframes popup-fade-in {\n            from { opacity: 0; }\n            to { opacity: 1; }\n        }\n\n        @keyframes popup-slide-up {\n            from { transform: translateY(30px) scale(0.95); opacity: 0; }\n            to { transform: translateY(0) scale(1); opacity: 1; }\n        }\n\n        .outfit-popup-content {\n            background: linear-gradient(180deg, #FFFFFF 0%, #F8F6FF 100%);\n            border-radius: 36px;\n            padding: 0;\n            width: 90%;\n            max-width: 620px;\n            max-height: 85%;\n            overflow: hidden;\n            box-shadow: \n                0 25px 80px rgba(74, 144, 217, 0.35),\n                0 10px 30px rgba(0, 0, 0, 0.2),\n                inset 0 1px 0 rgba(255, 255, 255, 0.9);\n            animation: popup-slide-up 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);\n            border: 2px solid rgba(255, 255, 255, 0.8);\n        }\n\n        .outfit-popup-header {\n            display: flex;\n            align-items: center;\n            justify-content: space-between;\n            padding: 30px 30px 20px 30px;\n            background: linear-gradient(180deg, rgba(74, 144, 217, 0.08) 0%, transparent 100%);\n            border-bottom: 1px solid rgba(74, 144, 217, 0.1);\n        }\n\n        .outfit-popup-title {\n            font-family: 'Fredoka', 'Nunito', sans-serif;\n            font-size: 36px;\n            font-weight: 700;\n            color: #F5A623;\n            margin: 0;\n            display: flex;\n            align-items: center;\n            gap: 14px;\n            text-shadow: \n                -2px -2px 0 #C77D18,\n                2px -2px 0 #C77D18,\n                -2px 2px 0 #C77D18,\n                2px 2px 0 #C77D18,\n                0 4px 0 #A66815,\n                0 5px 10px rgba(0,0,0,0.3);\n            letter-spacing: 1px;\n        }\n\n        .outfit-popup-title-icon {\n            width: 40px;\n            height: 40px;\n            object-fit: contain;\n            vertical-align: middle;\n            margin-right: 8px;\n            filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));\n        }\n\n        .chapters-title-icon {\n            width: 44px;\n            height: 44px;\n            object-fit: contain;\n            vertical-align: middle;\n            margin-right: 8px;\n            filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));\n        }\n\n        .outfit-close-button {\n            width: 52px;\n            height: 52px;\n            border-radius: 50%;\n            background: linear-gradient(135deg, #f8f8f8 0%, #e8e8e8 100%);\n            border: 2px solid rgba(0, 0, 0, 0.05);\n            font-size: 28px;\n            color: #888;\n            cursor: pointer;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            transition: all 0.2s ease;\n            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n        }\n\n        .outfit-close-button:hover {\n            background: linear-gradient(135deg, #fff 0%, #f0f0f0 100%);\n            color: var(--primary-blue);\n            transform: rotate(90deg);\n        }\n\n        .outfit-close-button:active {\n            transform: rotate(90deg) scale(0.9);\n        }\n\n        .outfit-grid {\n            display: grid;\n            grid-template-columns: repeat(3, 1fr);\n            gap: 22px;\n            padding: 30px 30px 40px 30px;\n            overflow-y: auto;\n            max-height: calc(85vh - 140px);\n        }\n\n        .outfit-option {\n            aspect-ratio: 0.75;\n            border-radius: 24px;\n            border: 4px solid transparent;\n            background: linear-gradient(180deg, #FFFFFF 0%, #F5F3FF 100%);\n            cursor: pointer;\n            transition: all 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);\n            overflow: hidden;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            min-height: 180px;\n            padding: 16px;\n            box-shadow: \n                0 4px 12px rgba(74, 144, 217, 0.15),\n                0 2px 4px rgba(0, 0, 0, 0.05),\n                inset 0 1px 0 rgba(255, 255, 255, 0.8);\n            position: relative;\n        }\n\n        .outfit-option::after {\n            content: '';\n            position: absolute;\n            inset: 0;\n            border-radius: 17px;\n            background: linear-gradient(180deg, rgba(255,255,255,0.4) 0%, transparent 50%);\n            pointer-events: none;\n        }\n\n        .outfit-option:hover {\n            transform: translateY(-4px) scale(1.03);\n            border-color: var(--primary-blue);\n            box-shadow: \n                0 12px 28px rgba(74, 144, 217, 0.3),\n                0 4px 8px rgba(0, 0, 0, 0.1);\n        }\n\n        .outfit-option:active {\n            transform: translateY(-2px) scale(1.01);\n        }\n\n        .outfit-option.selected {\n            border-color: var(--primary-yellow);\n            background: linear-gradient(180deg, #FFFEF5 0%, #FFF8E1 100%);\n            box-shadow: \n                0 0 0 4px rgba(245, 197, 66, 0.25),\n                0 8px 24px rgba(245, 197, 66, 0.35),\n                0 4px 8px rgba(0, 0, 0, 0.1);\n        }\n\n        .outfit-option.selected::before {\n            content: '\u2713';\n            position: absolute;\n            top: 8px;\n            right: 8px;\n            width: 24px;\n            height: 24px;\n            background: var(--primary-yellow);\n            border-radius: 50%;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            font-size: 14px;\n            font-weight: 700;\n            color: var(--text-dark);\n            box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);\n            z-index: 2;\n        }\n\n        .outfit-option img {\n            width: 100%;\n            height: 100%;\n            object-fit: contain;\n            filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.15));\n            transition: filter 0.3s ease, transform 0.3s ease;\n        }\n\n        .outfit-option:hover img {\n            transform: scale(1.03);\n        }\n\n        .outfit-option.selected img {\n            filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));\n        }\n\n        .outfit-coming-soon {\n            text-align: center;\n            padding: 50px 30px;\n            color: #666;\n            grid-column: 1 / -1;\n        }\n\n        .outfit-coming-soon-icon {\n            font-size: 72px;\n            margin-bottom: 16px;\n            opacity: 1;\n        }\n\n        .outfit-coming-soon-icon img {\n            width: 72px;\n            height: 72px;\n            object-fit: contain;\n            filter: drop-shadow(0 4px 8px rgba(0,0,0,0.2));\n        }\n\n        .outfit-coming-soon-title {\n            font-family: 'Fredoka', 'Nunito', sans-serif;\n            font-size: 24px;\n            font-weight: 700;\n            color: #F5A623;\n            margin-bottom: 8px;\n            text-shadow: \n                -1px -1px 0 #C77D18,\n                1px -1px 0 #C77D18,\n                -1px 1px 0 #C77D18,\n                1px 1px 0 #C77D18,\n                0 2px 0 #A66815,\n                0 3px 6px rgba(0,0,0,0.2);\n        }\n\n        .outfit-coming-soon-text {\n            font-size: 18px;\n            font-weight: 600;\n            color: #888;\n        }\n\n        .chapter-title {\n            font-family: 'Fredoka', 'Nunito', sans-serif;\n            font-size: 32px;\n            font-weight: 700;\n            color: white;\n            text-align: center;\n            margin: 0;\n            padding: 0 70px;\n            text-shadow: \n                0 2px 0 rgba(74, 144, 217, 0.4),\n                0 4px 12px rgba(0,0,0,0.15);\n            letter-spacing: 0.5px;\n        }\n\n        /* Puzzle Area - Premium Design */\n        .puzzle-area {\n            height: 70%;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            padding: 0 16px;\n            position: relative;\n            overflow: hidden;\n            box-sizing: border-box;\n            margin-top: -38px;\n            z-index: 1;\n        }\n\n        .puzzle-frame {\n            position: relative;\n            padding: 12px;\n            background: linear-gradient(145deg, #8B6914 0%, #C9962A 20%, #F5C542 50%, #C9962A 80%, #8B6914 100%);\n            border-radius: 20px;\n            box-shadow: \n                0 8px 0 rgba(101, 67, 33, 0.4),\n                0 12px 40px rgba(0,0,0,0.3),\n                inset 0 2px 0 rgba(255,255,255,0.3),\n                inset 0 -2px 0 rgba(0,0,0,0.2);\n        }\n\n        .puzzle-frame-inner {\n            background: linear-gradient(180deg, #F5F0E6 0%, #EBE6DC 100%);\n            border-radius: 12px;\n            padding: 8px;\n            box-shadow: \n                inset 0 2px 8px rgba(0,0,0,0.1),\n                inset 0 0 0 2px rgba(139, 105, 20, 0.2);\n        }\n\n        #puzzle-canvas {\n            border-radius: 8px;\n            box-shadow: \n                0 4px 16px rgba(0,0,0,0.15),\n                inset 0 0 0 1px rgba(0,0,0,0.05);\n            background: transparent;\n            display: block;\n        }\n\n        /* Piece Tray - Premium Design */\n        .piece-tray {\n            height: 20%;\n            min-height: 200px;\n            background: linear-gradient(180deg, #FFFFFF 0%, #F8F6F0 100%);\n            border-top-left-radius: 32px;\n            border-top-right-radius: 32px;\n            box-shadow: \n                0 -6px 0 rgba(74, 144, 217, 0.15),\n                0 -8px 30px rgba(0,0,0,0.12);\n            padding: 20px 16px 12px 16px;\n            box-sizing: border-box;\n            transition: all var(--transition-normal) var(--ease-out);\n            margin-top: -20px;\n            position: relative;\n            z-index: 5;\n            border-top: 3px solid rgba(255,255,255,0.8);\n        }\n\n        .piece-tray::before {\n            content: '';\n            position: absolute;\n            top: 10px;\n            left: 50%;\n            transform: translateX(-50%);\n            width: 50px;\n            height: 5px;\n            background: rgba(0,0,0,0.1);\n            border-radius: 3px;\n        }\n\n        .piece-tray.story-mode {\n            height: auto;\n            min-height: 140px;\n            max-height: 35%;\n            overflow-y: auto;\n            padding: 24px 28px;\n            background: linear-gradient(180deg, #FFFEF5 0%, #FFF8E1 100%);\n            border-top: 3px solid rgba(245, 197, 66, 0.4);\n        }\n\n        .piece-tray.story-mode::before {\n            display: none;\n        }\n\n        .tray-story-title {\n            font-family: 'Fredoka', 'Nunito', sans-serif;\n            font-size: 36px;\n            font-weight: 700;\n            background: linear-gradient(135deg, var(--primary-blue) 0%, #7B68EE 100%);\n            -webkit-background-clip: text;\n            -webkit-text-fill-color: transparent;\n            background-clip: text;\n            margin-bottom: 12px;\n            text-align: center;\n        }\n\n        .tray-story-text {\n            font-size: 26px;\n            line-height: 1.7;\n            color: var(--text-dark);\n            text-align: center;\n            font-weight: 500;\n        }\n\n        .tray-scroll {\n            display: flex;\n            gap: 14px;\n            overflow-x: auto;\n            overflow-y: visible;\n            padding: 10px 8px 12px 8px;\n            height: 100%;\n            align-items: center;\n            -webkit-overflow-scrolling: touch;\n            touch-action: pan-x;\n            scroll-behavior: smooth;\n            margin-top: 8px;\n        }\n\n        /* Bottom Controls Area - Premium Design */\n        .bottom-controls {\n            height: 6%;\n            min-height: 60px;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            padding: 0 20px;\n            box-sizing: border-box;\n            background: linear-gradient(180deg, transparent 0%, rgba(255,255,255,0.3) 100%);\n            position: relative;\n            z-index: 5;\n        }\n\n        .peak-button {\n            padding: 16px 32px;\n            border-radius: 50px;\n            background: linear-gradient(180deg, #FFFFFF 0%, #F0F0F0 100%);\n            border: none;\n            color: var(--primary-blue);\n            font-family: 'Fredoka', 'Nunito', sans-serif;\n            font-size: 22px;\n            font-weight: 700;\n            cursor: pointer;\n            box-shadow: \n                0 5px 0 rgba(74, 144, 217, 0.3),\n                0 8px 20px rgba(0,0,0,0.15),\n                inset 0 2px 0 rgba(255,255,255,0.8);\n            transition: all var(--transition-fast);\n            white-space: nowrap;\n            min-height: 60px;\n            width: calc(100% - 40px);\n            max-width: 400px;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            gap: 12px;\n            user-select: none;\n            -webkit-user-select: none;\n            touch-action: manipulation;\n            text-shadow: 0 1px 2px rgba(74, 144, 217, 0.2);\n        }\n\n        .peak-button.active {\n            background: linear-gradient(180deg, #5BA3E8 0%, var(--primary-blue) 100%);\n            color: white;\n            box-shadow: \n                0 3px 0 rgba(0, 80, 160, 0.4),\n                0 5px 15px rgba(74, 144, 217, 0.4),\n                inset 0 2px 0 rgba(255,255,255,0.2);\n            transform: translateY(2px);\n            text-shadow: 0 1px 2px rgba(0,0,0,0.2);\n        }\n\n        .peak-button:active {\n            transform: translateY(3px) scale(0.98);\n            box-shadow: \n                0 2px 0 rgba(74, 144, 217, 0.3),\n                0 3px 10px rgba(0,0,0,0.1),\n                inset 0 2px 0 rgba(255,255,255,0.8);\n        }\n\n        .peak-button .eye-icon {\n            width: 36px;\n            height: 36px;\n            display: inline-block;\n        }\n\n        .peak-button .eye-icon svg {\n            width: 100%;\n            height: 100%;\n            fill: currentColor;\n        }\n\n        .bottom-continue-button {\n            padding: 18px 40px;\n            border-radius: 50px;\n            background: linear-gradient(180deg, #FFE066 0%, #F5C542 50%, #E8B22E 100%);\n            border: none;\n            font-family: 'Fredoka', 'Nunito', sans-serif;\n            font-size: 24px;\n            font-weight: 700;\n            color: var(--text-dark);\n            cursor: pointer;\n            box-shadow: \n                0 6px 0 #C9962A,\n                0 10px 25px rgba(0,0,0,0.2),\n                inset 0 2px 0 rgba(255,255,255,0.4);\n            transition: all var(--transition-fast);\n            min-height: 60px;\n            width: calc(100% - 40px);\n            max-width: 400px;\n            text-shadow: 0 1px 2px rgba(0,0,0,0.1);\n            letter-spacing: 0.5px;\n        }\n\n        .bottom-continue-button:active {\n            transform: translateY(4px);\n            box-shadow: \n                0 2px 0 #C9962A,\n                0 4px 10px rgba(0,0,0,0.15),\n                inset 0 2px 0 rgba(255,255,255,0.4);\n        }\n\n        .tray-scroll::-webkit-scrollbar {\n            height: 6px;\n        }\n\n        .tray-scroll::-webkit-scrollbar-track {\n            background: #f0f0f0;\n            border-radius: 3px;\n        }\n\n        .tray-scroll::-webkit-scrollbar-thumb {\n            background: #ccc;\n            border-radius: 3px;\n        }\n\n        .tray-piece {\n            flex-shrink: 0;\n            border-radius: 4px;\n            cursor: grab;\n            transition: transform var(--transition-fast), filter var(--transition-fast);\n            background: transparent;\n            filter: drop-shadow(3px 3px 6px rgba(0,0,0,0.25));\n        }\n\n        .tray-piece:hover {\n            transform: scale(1.05) translateY(-4px);\n            filter: drop-shadow(4px 6px 10px rgba(0,0,0,0.3));\n        }\n\n        .tray-piece:active {\n            cursor: grabbing;\n            transform: scale(1.08) translateY(-6px);\n            filter: drop-shadow(6px 8px 14px rgba(0,0,0,0.35));\n        }\n\n        /* Star Overlay */\n        #star-overlay {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            background: rgba(0,0,0,0.6);\n            z-index: 1500;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            backdrop-filter: blur(4px);\n            -webkit-backdrop-filter: blur(4px);\n        }\n\n        .star-overlay-content {\n            background: white;\n            border-radius: 24px;\n            padding: 40px 30px;\n            text-align: center;\n            width: 80%;\n            max-width: 400px;\n            box-shadow: 0 10px 30px rgba(0,0,0,0.3);\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n        }\n\n        .star-overlay-title {\n            font-family: 'Fredoka', 'Nunito', sans-serif;\n            font-size: 36px;\n            color: var(--primary-blue);\n            margin-top: 0;\n            margin-bottom: 20px;\n        }\n\n        .stars-container {\n            display: flex;\n            gap: 15px;\n            margin-bottom: 20px;\n        }\n\n        .star {\n            width: 72px;\n            height: 72px;\n            color: #E0E0E0;\n            transform: scale(0) rotate(-180deg);\n            opacity: 0;\n        }\n\n        .star img {\n            width: 100%;\n            height: 100%;\n            object-fit: contain;\n            filter: drop-shadow(0 4px 6px rgba(0,0,0,0.1));\n        }\n\n        .star.filled {\n            animation: star-bounce-pop 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;\n        }\n\n        .star.filled img {\n            filter: drop-shadow(0 0 12px rgba(255, 215, 0, 0.7)) drop-shadow(0 4px 6px rgba(0,0,0,0.15));\n        }\n\n        .star:not(.filled) img {\n            filter: grayscale(1) brightness(1.5) opacity(0.4);\n        }\n\n        @keyframes star-bounce-pop {\n            0% { \n                transform: scale(0) rotate(-180deg); \n                opacity: 0; \n            }\n            50% { \n                transform: scale(1.4) rotate(15deg); \n                opacity: 1; \n            }\n            70% { \n                transform: scale(0.85) rotate(-8deg); \n                opacity: 1; \n            }\n            85% { \n                transform: scale(1.1) rotate(3deg); \n                opacity: 1; \n            }\n            100% { \n                transform: scale(1) rotate(0deg); \n                opacity: 1; \n                filter: drop-shadow(0 0 12px rgba(255, 215, 0, 0.7)); \n            }\n        }\n\n        .star-message {\n            font-size: 20px;\n            color: var(--text-dark);\n            margin-bottom: 30px;\n            min-height: 30px;\n            font-weight: 700;\n        }\n\n        #star-continue-button {\n            padding: 16px 20px;\n            border-radius: 24px;\n            background: linear-gradient(180deg, #F7D04C 0%, var(--primary-yellow) 100%);\n            border: none;\n            font-family: 'Fredoka', 'Nunito', sans-serif;\n            font-size: 24px;\n            font-weight: 700;\n            color: var(--text-dark);\n            cursor: pointer;\n            box-shadow: 0 4px 0 #D4A82E, var(--shadow-soft);\n            transition: transform var(--transition-fast), box-shadow var(--transition-fast);\n            width: 100%;\n        }\n\n        #star-continue-button:active {\n            transform: translateY(2px);\n            box-shadow: 0 2px 0 #D4A82E, 0 2px 4px rgba(0,0,0,0.1);\n        }\n\n        /* Dragging Piece */\n        #dragging-piece {\n            position: fixed;\n            pointer-events: none;\n            z-index: 1000;\n            display: none;\n            filter: drop-shadow(4px 4px 8px rgba(0,0,0,0.3));\n        }\n\n\n        /* Tutorial Overlay */\n        #tutorial-overlay {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            pointer-events: none;\n            z-index: 100;\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            justify-content: center;\n        }\n\n        .tutorial-content {\n            position: absolute;\n            bottom: 22%;\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            animation: tutorial-fade-in 0.6s ease-out;\n        }\n\n        @keyframes tutorial-fade-in {\n            from { opacity: 0; transform: translateY(20px); }\n            to { opacity: 1; transform: translateY(0); }\n        }\n\n        .tutorial-hand {\n            font-size: 112px;\n            animation: tutorial-drag 2s ease-in-out infinite;\n            filter: drop-shadow(0 4px 8px rgba(0,0,0,0.3));\n            transform-origin: center center;\n        }\n\n        @keyframes tutorial-drag {\n            0%, 100% { \n                transform: translateY(0) rotate(-15deg); \n                opacity: 1;\n            }\n            15% {\n                transform: translateY(0) rotate(-15deg) scale(0.95);\n            }\n            50% { \n                transform: translateY(-80px) rotate(0deg); \n                opacity: 1;\n            }\n            65% {\n                transform: translateY(-80px) rotate(0deg) scale(1.05);\n                opacity: 0.8;\n            }\n            80% {\n                opacity: 0;\n                transform: translateY(-80px) rotate(0deg);\n            }\n            95% {\n                opacity: 0;\n                transform: translateY(0) rotate(-15deg);\n            }\n        }\n\n        .tutorial-path {\n            position: absolute;\n            bottom: 60px;\n            width: 4px;\n            height: 70px;\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            justify-content: space-between;\n            opacity: 0.6;\n        }\n\n        .tutorial-dot {\n            width: 8px;\n            height: 8px;\n            background: var(--primary-blue);\n            border-radius: 50%;\n            animation: tutorial-dot-pulse 2s ease-in-out infinite;\n            box-shadow: 0 0 8px rgba(74, 144, 217, 0.5);\n        }\n\n        .tutorial-dot:nth-child(1) { animation-delay: 0s; }\n        .tutorial-dot:nth-child(2) { animation-delay: 0.15s; }\n        .tutorial-dot:nth-child(3) { animation-delay: 0.3s; }\n        .tutorial-dot:nth-child(4) { animation-delay: 0.45s; }\n        .tutorial-dot:nth-child(5) { animation-delay: 0.6s; }\n\n        @keyframes tutorial-dot-pulse {\n            0%, 30%, 100% { opacity: 0.3; transform: scale(0.8); }\n            15% { opacity: 1; transform: scale(1.2); }\n        }\n\n        .tutorial-text {\n            margin-top: 16px;\n            padding: 14px 28px;\n            background: linear-gradient(180deg, rgba(255,255,255,0.98) 0%, rgba(248,246,240,0.98) 100%);\n            border-radius: 50px;\n            font-family: 'Fredoka', 'Nunito', sans-serif;\n            font-size: 22px;\n            font-weight: 700;\n            color: var(--primary-blue);\n            box-shadow: \n                0 4px 0 rgba(74, 144, 217, 0.2),\n                0 6px 20px rgba(0,0,0,0.15);\n            animation: tutorial-text-bounce 2s ease-in-out infinite;\n            white-space: nowrap;\n        }\n\n        @keyframes tutorial-text-bounce {\n            0%, 100% { transform: translateY(0); }\n            50% { transform: translateY(-4px); }\n        }\n\n        .tutorial-arrow {\n            font-size: 28px;\n            margin-bottom: 8px;\n            color: var(--primary-blue);\n            animation: tutorial-arrow-bounce 1s ease-in-out infinite;\n        }\n\n        @keyframes tutorial-arrow-bounce {\n            0%, 100% { transform: translateY(0); }\n            50% { transform: translateY(-6px); }\n        }\n\n        #tutorial-overlay.hiding {\n            animation: tutorial-fade-out 0.4s ease-out forwards;\n        }\n\n        /* Peek Tutorial Overlay */\n        #peek-tutorial-overlay {\n            position: absolute;\n            bottom: 80px;\n            left: 0;\n            width: 100%;\n            pointer-events: none;\n            z-index: 100;\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            justify-content: flex-end;\n            padding-bottom: 20px;\n        }\n\n        .peek-tutorial-content {\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            animation: tutorial-fade-in 0.6s ease-out;\n        }\n\n        .peek-hand {\n            animation: peek-hand-bounce 1.2s ease-in-out infinite !important;\n        }\n\n        @keyframes peek-hand-bounce {\n            0%, 100% { \n                transform: translateY(0); \n            }\n            50% { \n                transform: translateY(15px); \n            }\n        }\n\n        #peek-tutorial-overlay.hiding {\n            animation: tutorial-fade-out 0.4s ease-out forwards;\n        }\n\n        @keyframes tutorial-fade-out {\n            from { opacity: 1; }\n            to { opacity: 0; }\n        }\n\n        /* Confetti */\n        .confetti {\n            position: fixed;\n            pointer-events: none;\n            z-index: 1001;\n            animation: confetti-fall 3s ease-out forwards;\n        }\n\n        @keyframes confetti-fall {\n            0% {\n                opacity: 1;\n                transform: translateY(0) rotate(0deg) scale(1);\n            }\n            10% {\n                opacity: 1;\n            }\n            100% {\n                opacity: 0;\n                transform: translateY(100vh) rotate(1080deg) scale(0.5);\n            }\n        }\n\n        /* Hidden */\n        .hidden {\n            display: none !important;\n        }\n\n        /* Loading Screen */\n        #loading-screen {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            background: linear-gradient(180deg, #64B5F6 0%, #90CAF9 40%, #E3F2FD 100%);\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            justify-content: center;\n            z-index: 2000;\n            transition: opacity 0.5s ease-out;\n            overflow: hidden;\n        }\n\n        .loading-sky-elements {\n            position: absolute;\n            inset: 0;\n            pointer-events: none;\n            overflow: hidden;\n        }\n\n        .loading-sun {\n            position: absolute;\n            top: 15%;\n            right: 15%;\n            width: 140px;\n            height: 140px;\n            background: radial-gradient(circle, #FFFDE7 0%, #FFF59D 40%, rgba(255,245,157,0) 70%);\n            border-radius: 50%;\n            box-shadow: 0 0 60px 30px rgba(255, 235, 59, 0.3);\n            animation: pulse-sun 4s ease-in-out infinite alternate;\n        }\n\n        @keyframes pulse-sun {\n            0% { transform: scale(1); opacity: 0.8; }\n            100% { transform: scale(1.1); opacity: 1; }\n        }\n\n        .loading-cloud {\n            position: absolute;\n            background: white;\n            border-radius: 50px;\n            box-shadow: 0 8px 16px rgba(0,0,0,0.05);\n        }\n        .loading-cloud::before, .loading-cloud::after {\n            content: '';\n            position: absolute;\n            background: white;\n            border-radius: 50%;\n        }\n\n        .loading-cloud-1 {\n            width: 160px; height: 50px; top: 20%; left: -200px;\n            animation: float-cloud 30s linear infinite;\n        }\n        .loading-cloud-1::before { width: 80px; height: 80px; top: -40px; left: 20px; }\n        .loading-cloud-1::after { width: 60px; height: 60px; top: -25px; right: 20px; }\n\n        .loading-cloud-2 {\n            width: 220px; height: 70px; top: 35%; left: -250px;\n            animation: float-cloud 45s linear infinite 15s;\n            opacity: 0.8; transform: scale(0.8);\n        }\n        .loading-cloud-2::before { width: 110px; height: 110px; top: -55px; left: 30px; }\n        .loading-cloud-2::after { width: 80px; height: 80px; top: -35px; right: 30px; }\n\n        .loading-cloud-3 {\n            width: 120px; height: 40px; top: 10%; left: -150px;\n            animation: float-cloud 25s linear infinite 5s;\n            opacity: 0.9; transform: scale(0.6);\n        }\n        .loading-cloud-3::before { width: 60px; height: 60px; top: -30px; left: 15px; }\n        .loading-cloud-3::after { width: 45px; height: 45px; top: -20px; right: 15px; }\n\n        @keyframes float-cloud {\n            0% { transform: translateX(0); }\n            100% { transform: translateX(calc(100vw + 400px)); }\n        }\n\n        .loading-hills {\n            position: absolute;\n            inset: 0;\n            pointer-events: none;\n            overflow: hidden;\n        }\n\n        .loading-hill {\n            position: absolute;\n            border-radius: 50%;\n        }\n\n        .loading-hill-bg {\n            width: 160%; height: 60vh;\n            bottom: -35vh; left: -30%;\n            background: linear-gradient(180deg, #81C784 0%, #4CAF50 100%);\n            box-shadow: inset 0 30px 60px rgba(255,255,255,0.25), 0 -5px 20px rgba(0,0,0,0.1);\n        }\n\n        .loading-hill-fg {\n            width: 130%; height: 45vh;\n            bottom: -20vh; right: -15%;\n            background: linear-gradient(180deg, #66BB6A 0%, #388E3C 100%);\n            box-shadow: inset 0 20px 50px rgba(255,255,255,0.2), 0 -10px 25px rgba(0,0,0,0.15);\n        }\n\n        #loading-screen.fade-out {\n            opacity: 0;\n            pointer-events: none;\n        }\n\n        .loading-content {\n            position: relative;\n            z-index: 10;\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            justify-content: center;\n        }\n        \n        .loading-title {\n            font-family: 'Fredoka', 'Nunito', sans-serif;\n            font-size: 48px;\n            font-weight: 700;\n            color: white;\n            margin-bottom: 40px;\n            text-shadow: 0 4px 8px rgba(0,0,0,0.2);\n            letter-spacing: 1px;\n            text-align: center;\n        }\n\n        .loading-container {\n            position: relative;\n            width: 180px;\n            height: 180px;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            background: rgba(255, 255, 255, 0.2);\n            border-radius: 50%;\n            box-shadow: 0 8px 32px rgba(0,0,0,0.1);\n            backdrop-filter: blur(4px);\n            -webkit-backdrop-filter: blur(4px);\n        }\n\n        .loading-ring {\n            position: absolute;\n            width: 100%;\n            height: 100%;\n            border-radius: 50%;\n            border: 6px solid rgba(255, 255, 255, 0.3);\n            box-sizing: border-box;\n        }\n\n        .loading-ring-progress {\n            position: absolute;\n            width: 100%;\n            height: 100%;\n            border-radius: 50%;\n            border: 6px solid transparent;\n            border-top-color: white;\n            box-sizing: border-box;\n            animation: loading-spin 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;\n        }\n\n        @keyframes loading-spin {\n            0% { transform: rotate(0deg); }\n            100% { transform: rotate(360deg); }\n        }\n\n        .loading-bee {\n            position: relative;\n            width: 100px;\n            height: 100px;\n            object-fit: contain;\n            animation: bee-hover 2s ease-in-out infinite;\n            z-index: 1;\n            filter: drop-shadow(0 10px 15px rgba(0,0,0,0.2));\n        }\n\n        @keyframes bee-hover {\n            0%, 100% { transform: translateY(0) rotate(-3deg); }\n            50% { transform: translateY(-12px) rotate(3deg); }\n        }\n\n        .loading-presents {\n            font-family: 'Nunito', sans-serif;\n            font-size: 24px;\n            font-weight: 700;\n            color: white;\n            margin-top: 40px;\n            opacity: 0.9;\n            letter-spacing: 3px;\n            text-shadow: 0 2px 8px rgba(0,0,0,0.15);\n            text-transform: uppercase;\n        }\n\n        /* Image Preview Popup */\n        #image-preview-popup {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            background: rgba(0,0,0,0.85);\n            z-index: 2000;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            flex-direction: column;\n            padding: 20px;\n            box-sizing: border-box;\n            backdrop-filter: blur(4px);\n            -webkit-backdrop-filter: blur(4px);\n        }\n\n        .preview-image-container {\n            width: 100%;\n            max-width: 680px;\n            aspect-ratio: 1;\n            border-radius: 16px;\n            overflow: hidden;\n            box-shadow: 0 10px 40px rgba(0,0,0,0.5);\n        }\n\n        .preview-image {\n            width: 100%;\n            height: 100%;\n            object-fit: cover;\n        }\n\n        .preview-title {\n            font-family: 'Fredoka', 'Nunito', sans-serif;\n            font-size: 24px;\n            color: white;\n            margin-top: 20px;\n            text-align: center;\n        }\n\n        .preview-hint {\n            font-size: 16px;\n            color: rgba(255,255,255,0.6);\n            margin-top: 12px;\n        }\n\n        /* Edit Mode Styles */\n        .edit-panel {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            background: var(--bg-cream);\n            overflow-y: auto;\n            padding: 20px;\n            box-sizing: border-box;\n        }\n\n        .edit-header {\n            display: flex;\n            align-items: center;\n            justify-content: space-between;\n            margin-bottom: 20px;\n            padding-right: 10px;\n        }\n\n        .edit-title {\n            font-size: 28px;\n            font-weight: 800;\n            color: var(--primary-blue);\n            margin: 0;\n        }\n\n        .edit-level-card {\n            background: white;\n            border-radius: 16px;\n            padding: 16px;\n            margin-bottom: 16px;\n            box-shadow: var(--shadow-soft);\n        }\n\n        .edit-level-header {\n            display: flex;\n            align-items: center;\n            gap: 12px;\n            margin-bottom: 12px;\n        }\n\n        .edit-level-thumb {\n            width: 60px;\n            height: 60px;\n            border-radius: 8px;\n            object-fit: cover;\n            cursor: pointer;\n            transition: transform var(--transition-fast), box-shadow var(--transition-fast);\n        }\n\n        .edit-level-thumb:hover {\n            transform: scale(1.05);\n            box-shadow: 0 4px 12px rgba(0,0,0,0.2);\n        }\n\n        .edit-level-thumb:active {\n            transform: scale(0.98);\n        }\n\n        .edit-level-info {\n            flex: 1;\n        }\n\n        .edit-level-number {\n            font-size: 12px;\n            color: #888;\n            font-weight: 600;\n        }\n\n        .edit-field {\n            margin-bottom: 12px;\n        }\n\n        .edit-label {\n            font-size: 14px;\n            font-weight: 600;\n            color: var(--text-dark);\n            margin-bottom: 6px;\n            display: block;\n        }\n\n        .edit-input {\n            width: 100%;\n            padding: 10px 12px;\n            border: 2px solid #E0E0E0;\n            border-radius: 8px;\n            font-family: 'Nunito', sans-serif;\n            font-size: 16px;\n            box-sizing: border-box;\n            transition: border-color var(--transition-fast);\n        }\n\n        .edit-input:focus {\n            outline: none;\n            border-color: var(--primary-blue);\n        }\n\n        .edit-textarea {\n            min-height: 100px;\n            resize: vertical;\n        }\n\n        .edit-select {\n            width: 100%;\n            padding: 10px 12px;\n            border: 2px solid #E0E0E0;\n            border-radius: 8px;\n            font-family: 'Nunito', sans-serif;\n            font-size: 16px;\n            background: white;\n            cursor: pointer;\n        }\n    </style>\n</head>\n<body>\n    <div id=\"game-container\">\n        <!-- Loading Screen -->\n        <!-- Loading Screen -->\n        <div id=\"loading-screen\">\n            <div class=\"loading-sky-elements\">\n                <div class=\"loading-sun\"></div>\n                <div class=\"loading-cloud loading-cloud-1\"></div>\n                <div class=\"loading-cloud loading-cloud-2\"></div>\n                <div class=\"loading-cloud loading-cloud-3\"></div>\n            </div>\n            <div class=\"loading-hills\">\n                <div class=\"loading-hill loading-hill-bg\"></div>\n                <div class=\"loading-hill loading-hill-fg\"></div>\n            </div>\n            <div class=\"loading-content\">\n                <div class=\"loading-title\">Robee Verse</div>\n                <div class=\"loading-container\">\n                    <div class=\"loading-ring\"></div>\n                    <div class=\"loading-ring-progress\"></div>\n                    <img id=\"loading-bee\" class=\"loading-bee\" src=\"\" alt=\"Bee Logo\">\n                </div>\n                <div class=\"loading-presents\">presents</div>\n            </div>\n        </div>\n\n        <!-- Title Screen -->\n        <div id=\"title-screen\" style=\"display: none;\">\n            <img class=\"title-hero-image\" id=\"title-hero-image\" src=\"\" alt=\"Milo & Lebo\">\n            <div class=\"title-gradient-overlay\"></div>\n            <div class=\"title-buttons-container\">\n                <button class=\"title-menu-button primary\" id=\"start-button\">Start</button>\n                <button class=\"title-menu-button secondary\" id=\"continue-button\">Continue Story</button>\n                <button class=\"title-menu-button tertiary\" id=\"view-chapters-button\">View Chapters</button>\n            </div>\n        </div>\n\n        <!-- Chapters Screen -->\n        <div id=\"chapters-screen\">\n            <div class=\"chapters-bg-decorations\">\n                <div class=\"chapters-cloud chapters-cloud-1\"></div>\n                <div class=\"chapters-cloud chapters-cloud-2\"></div>\n                <div class=\"chapters-cloud chapters-cloud-3\"></div>\n            </div>\n            <div class=\"chapters-header\">\n                <button class=\"chapters-back-button\" id=\"chapters-back-button\">\u2190</button>\n                <h1 class=\"chapters-title\"><img id=\"chapters-book-icon\" class=\"chapters-title-icon\" src=\"\" alt=\"\"> Chapters</h1>\n            </div>\n            <div class=\"level-grid\" id=\"level-grid\"></div>\n        </div>\n\n        <!-- Game Screen -->\n        <div id=\"game-screen\">\n            <div class=\"game-screen-decorations\">\n                <div class=\"game-cloud game-cloud-1\"></div>\n                <div class=\"game-cloud game-cloud-2\"></div>\n            </div>\n            <div class=\"header\">\n                <button class=\"back-button hidden\" id=\"back-button\">\u2190</button>\n                <h2 class=\"chapter-title\" id=\"chapter-title\">Chapter 1</h2>\n                <button class=\"outfit-button\" id=\"outfit-button\">\n                    <img id=\"outfit-button-img\" src=\"\" alt=\"Pick Outfit\">\n                </button>\n            </div>\n            <div class=\"puzzle-area\">\n                <div class=\"puzzle-frame\" id=\"puzzle-frame\">\n                    <div class=\"puzzle-frame-inner\">\n                        <canvas id=\"puzzle-canvas\"></canvas>\n                    </div>\n                </div>\n            </div>\n            <div class=\"piece-tray\" id=\"piece-tray\">\n                <div class=\"tray-scroll\" id=\"tray-scroll\"></div>\n                <div class=\"tray-story-title hidden\" id=\"tray-story-title\"></div>\n                <div class=\"tray-story-text hidden\" id=\"tray-story-text\"></div>\n            </div>\n            <div class=\"bottom-controls\" id=\"bottom-controls\">\n                <button class=\"peak-button hidden\" id=\"peak-button\">\n                    <span class=\"eye-icon\"><svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z\"/></svg></span>\n                    <span>Peek</span>\n                </button>\n                <button class=\"bottom-continue-button hidden\" id=\"bottom-continue-button\">Continue</button>\n            </div>\n            \n\n        </div>\n\n        <!-- Edit Panel -->\n        <div class=\"edit-panel hidden\" id=\"edit-panel\">\n            <div class=\"edit-header\">\n                <h1 class=\"edit-title\">Edit Levels</h1>\n            </div>\n            <div id=\"edit-levels-container\"></div>\n        </div>\n\n        <div id=\"image-preview-popup\" class=\"hidden\">\n            <div class=\"preview-image-container\">\n                <img class=\"preview-image\" id=\"preview-image\" src=\"\" alt=\"Level Preview\">\n            </div>\n            <div class=\"preview-title\" id=\"preview-title\"></div>\n            <div class=\"preview-hint\">Tap anywhere to close</div>\n        </div>\n\n        <div id=\"star-overlay\" class=\"hidden\">\n            <div class=\"star-overlay-content\">\n                <h2 class=\"star-overlay-title\">Level Complete!</h2>\n                <div class=\"stars-container\">\n                    <div class=\"star\" id=\"star-1\"><img id=\"star-1-img\" src=\"\" alt=\"Star\"></div>\n                    <div class=\"star\" id=\"star-2\"><img id=\"star-2-img\" src=\"\" alt=\"Star\"></div>\n                    <div class=\"star\" id=\"star-3\"><img id=\"star-3-img\" src=\"\" alt=\"Star\"></div>\n                </div>\n                <div class=\"star-message\" id=\"star-message\"></div>\n                <button id=\"star-continue-button\">Continue</button>\n            </div>\n        </div>\n\n        <!-- Outfit Picker Popup -->\n        <!-- Tutorial Overlay -->\n        <div id=\"tutorial-overlay\" class=\"hidden\">\n            <div class=\"tutorial-content\">\n                <div class=\"tutorial-path\">\n                    <div class=\"tutorial-dot\"></div>\n                    <div class=\"tutorial-dot\"></div>\n                    <div class=\"tutorial-dot\"></div>\n                    <div class=\"tutorial-dot\"></div>\n                    <div class=\"tutorial-dot\"></div>\n                </div>\n                <div class=\"tutorial-hand\">\ud83d\udc46</div>\n                <div class=\"tutorial-text\">Drag pieces up to the puzzle!</div>\n            </div>\n        </div>\n\n        <div id=\"peek-tutorial-overlay\" class=\"hidden\">\n            <div class=\"peek-tutorial-content\">\n                <div class=\"tutorial-hand peek-hand\">\ud83d\udc47</div>\n                <div class=\"tutorial-text\">Hold to peek at the picture!</div>\n            </div>\n        </div>\n\n        <div id=\"outfit-popup\" class=\"hidden\">\n            <div class=\"outfit-popup-content\">\n                <div class=\"outfit-popup-header\">\n                    <h2 class=\"outfit-popup-title\"><img id=\"outfit-dress-icon\" class=\"outfit-popup-title-icon\" src=\"\" alt=\"\"> Pick an Outfit</h2>\n                    <button class=\"outfit-close-button\" id=\"outfit-close-button\">\u00d7</button>\n                </div>\n                <div class=\"outfit-grid\" id=\"outfit-grid\"></div>\n            </div>\n        </div>\n\n        <!-- Dragging Piece -->\n        <canvas id=\"dragging-piece\"></canvas>\n    </div>\n\n    <script>\n        /**\n         * Milo & Lebo's Puzzle Story\n         * \n         * A charming jigsaw puzzle game where players complete illustrated scenes\n         * to reveal heartwarming stories about a girl named Milo and her puppy Lebo.\n         * \n         * Features:\n         * - 6 levels with increasing difficulty (2x2 to 7x7 grids)\n         * - Drag-and-drop puzzle mechanics\n         * - Reference image toggle for help\n         * - Beautiful story reveals upon completion\n         * - Progress saving\n         */\n\n        async function run(mode) {\n            // ==================== CONSTANTS ====================\n            const VIEWPORT_WIDTH = 720;\n            const VIEWPORT_HEIGHT = 1280;\n            const SNAP_THRESHOLD = 40;\n            const PIECE_SCALE_DRAGGING = 1.1;\n            // Get reference opacity from level config\n            function getReferenceOpacity(levelIndex) {\n                const level = getLevels()[levelIndex];\n                return level.peekOpacity || 0.5;\n            }\n\n            // ==================== DOM ELEMENTS ====================\n            const gameContainer = document.getElementById('game-container');\n            const titleScreen = document.getElementById('title-screen');\n            const chaptersScreen = document.getElementById('chapters-screen');\n            const gameScreen = document.getElementById('game-screen');\n            const levelGrid = document.getElementById('level-grid');\n            const titleHeroImage = document.getElementById('title-hero-image');\n            const startButton = document.getElementById('start-button');\n            const continueButton = document.getElementById('continue-button');\n            const viewChaptersButton = document.getElementById('view-chapters-button');\n            const chaptersBackButton = document.getElementById('chapters-back-button');\n            const backButton = document.getElementById('back-button');\n            const chapterTitle = document.getElementById('chapter-title');\n            const peakButton = document.getElementById('peak-button');\n            const puzzleCanvas = document.getElementById('puzzle-canvas');\n            const ctx = puzzleCanvas.getContext('2d');\n            const trayScroll = document.getElementById('tray-scroll');\n            const bottomContinueButton = document.getElementById('bottom-continue-button');\n            const draggingPieceCanvas = document.getElementById('dragging-piece');\n            const draggingCtx = draggingPieceCanvas.getContext('2d');\n            const editPanel = document.getElementById('edit-panel');\n            const editLevelsContainer = document.getElementById('edit-levels-container');\n            const pieceTray = document.getElementById('piece-tray');\n            const trayStoryTitle = document.getElementById('tray-story-title');\n            const trayStoryText = document.getElementById('tray-story-text');\n            const loadingScreen = document.getElementById('loading-screen');\n            const imagePreviewPopup = document.getElementById('image-preview-popup');\n            const previewImage = document.getElementById('preview-image');\n            const previewTitle = document.getElementById('preview-title');\n            const outfitButton = document.getElementById('outfit-button');\n            const outfitButtonImg = document.getElementById('outfit-button-img');\n            const outfitPopup = document.getElementById('outfit-popup');\n            const outfitCloseButton = document.getElementById('outfit-close-button');\n            const tutorialOverlay = document.getElementById('tutorial-overlay');\n\n\n            // ==================== AUDIO CONTEXT ====================\n            let audioContext = null;\n            const audioBuffers = {};\n            let musicSource = null;\n            let musicGainNode = null;\n            let sfxGainNode = null;\n\n            function initAudio() {\n                try {\n                    if (!audioContext) {\n                        audioContext = new (window.AudioContext || window.webkitAudioContext)();\n                        musicGainNode = audioContext.createGain();\n                        musicGainNode.gain.value = 0.25;\n                        musicGainNode.connect(audioContext.destination);\n                        sfxGainNode = audioContext.createGain();\n                        sfxGainNode.gain.value = 0.5;\n                        sfxGainNode.connect(audioContext.destination);\n                    }\n                    if (audioContext.state === 'suspended') {\n                        const p = audioContext.resume();\n                        if (p) p.catch(() => {});\n                    }\n                } catch (e) {\n                    console.warn('Audio init failed:', e);\n                }\n            }\n\n            async function loadAudio(id) {\n                if (audioBuffers[id]) return audioBuffers[id];\n                try {\n                    const assetInfo = lib.getAsset(id);\n                    if (!assetInfo || assetInfo.type !== 'audio') return null;\n                    const response = await fetch(assetInfo.url);\n                    const arrayBuffer = await response.arrayBuffer();\n                    const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);\n                    if (!audioBuffers[id]) {\n                        audioBuffers[id] = audioBuffer;\n                    }\n                    return audioBuffers[id];\n                } catch (e) {\n                    lib.log(`Failed to load audio ${id}: ${e.message}`);\n                    return null;\n                }\n            }\n\n            function playSound(id, loop = false) {\n                if (!audioContext || !audioBuffers[id]) return null;\n                const source = audioContext.createBufferSource();\n                source.buffer = audioBuffers[id];\n                source.loop = loop;\n                source.connect(loop ? musicGainNode : sfxGainNode);\n                source.start(0);\n                return source;\n            }\n\n            function playMusic() {\n                if (musicSource) {\n                    musicSource.stop();\n                }\n                musicSource = playSound('background_music', true);\n            }\n\n            function stopMusic() {\n                if (musicSource) {\n                    musicSource.stop();\n                    musicSource = null;\n                }\n            }\n            // ==================== ASSET LOADING ====================\n            const imageCache = {};\n\n            async function loadImage(id) {\n                if (imageCache[id]) return imageCache[id];\n                return new Promise((resolve, reject) => {\n                    const assetInfo = lib.getAsset(id);\n                    if (!assetInfo || assetInfo.type !== 'image') {\n                        reject(new Error(`Asset ${id} not found or not an image`));\n                        return;\n                    }\n                    const img = new Image();\n                    img.crossOrigin = 'anonymous';\n                    img.onload = () => {\n                        imageCache[id] = img;\n                        resolve(img);\n                    };\n                    img.onerror = () => reject(new Error(`Failed to load image ${id}`));\n                    img.src = assetInfo.url;\n                });\n            }\n            async function preloadAllAssets() {\n                initAudio();\n                \n                // Load all level images and variants from config\n                const levels = getLevels();\n                const imagePromises = [];\n                levels.forEach(level => {\n                    imagePromises.push(loadImage(level.imageId).catch(e => {\n                        lib.log(`Failed to load level image ${level.imageId}: ${e.message}`);\n                        return null;\n                    }));\n                    if (level.imageVariants) {\n                        for (const variantId of Object.values(level.imageVariants)) {\n                            imagePromises.push(loadImage(variantId).catch(e => {\n                                lib.log(`Failed to load variant image ${variantId}: ${e.message}`);\n                                return null;\n                            }));\n                        }\n                    }\n                });\n                // Load audio\n                const audioIds = [\n                    'background_music',\n                    'piece_pickup_sfx',\n                    'piece_correct_sfx',\n                    'piece_incorrect_sfx',\n                    'puzzle_complete_sfx',\n                    'button_tap_sfx',\n                    'story_reveal_sfx',\n                    'star_reveal_sfx'\n                ];\n                \n                const audioPromises = audioIds.map(id => loadAudio(id).catch(e => {\n                    lib.log(`Failed to load audio ${id}: ${e.message}`);\n                    return null;\n                }));\n                \n                await Promise.all([...imagePromises, ...audioPromises]);\n            }\n\n            // ==================== GAME STATE ====================\n            let currentLevel = 0;\n            let gamePhase = 'menu'; // 'menu', 'playing', 'completed'\n            let showReference = false;\n            let isPeaking = false; // True when user is holding Peak button\n            let pieces = [];\n            let placedPieces = [];\n            let draggingPiece = null;\n            let dragOffsetX = 0;\n            let dragOffsetY = 0;\n            let gridCells = [];\n            let gridRows = 0;\n            let gridCols = 0;\n            let cellSize = 0;\n            let gridOffsetX = 0;\n            let gridOffsetY = 0;\n            let currentLevelImage = null;\n            let highlightedCell = -1;\n            let referencePreviewMode = false; // True during initial 2-second preview\n            let referencePreviewTimeout = null;\n            let peekCount = 0;\n            let pendingPreviewStart = false; // True when preview should start after star overlay dismissed\n            let pendingLevelIndex = null; // Level to start after outfit selection\n            let pendingDelayPreview = false; // Whether to delay preview for pending level\n            let tutorialShown = false; // Track if tutorial has been shown this session\n            let tutorialVisible = false; // Track if tutorial is currently visible\n            let peekTutorialShown = false; // Track if peek button tutorial has been shown\n            let peekTutorialVisible = false; // Track if peek tutorial is currently visible\n            let firstPeekUsed = false; // Track if first peek has been used (first peek is free)\n            \n            // Jigsaw piece edge types: 0 = flat, 1 = tab (outward), -1 = blank (inward)\n            let pieceEdges = []; // Store edge configurations for each piece\n            const TAB_SIZE = 0.25; // Tab size relative to cell size\n\n            // ==================== TUTORIAL ====================\n            function showTutorial() {\n                if (tutorialShown || currentLevel !== 0) return;\n                tutorialVisible = true;\n                tutorialOverlay.classList.remove('hidden');\n                tutorialOverlay.classList.remove('hiding');\n            }\n\n            function hideTutorial() {\n                if (!tutorialVisible) return;\n                tutorialVisible = false;\n                tutorialShown = true;\n                tutorialOverlay.classList.add('hiding');\n                setTimeout(() => {\n                    tutorialOverlay.classList.add('hidden');\n                    tutorialOverlay.classList.remove('hiding');\n                }, 400);\n            }\n\n            // ==================== PEEK TUTORIAL ====================\n            function showPeekTutorial() {\n                if (peekTutorialShown || currentLevel === 0) return; // Don't show on level 1 (no peek button)\n                const peekTutorialOverlay = document.getElementById('peek-tutorial-overlay');\n                peekTutorialVisible = true;\n                peekTutorialOverlay.classList.remove('hidden');\n                peekTutorialOverlay.classList.remove('hiding');\n            }\n\n            function hidePeekTutorial() {\n                if (!peekTutorialVisible) return;\n                const peekTutorialOverlay = document.getElementById('peek-tutorial-overlay');\n                peekTutorialVisible = false;\n                peekTutorialShown = true;\n                peekTutorialOverlay.classList.add('hiding');\n                setTimeout(() => {\n                    peekTutorialOverlay.classList.add('hidden');\n                    peekTutorialOverlay.classList.remove('hiding');\n                }, 400);\n            }\n\n            // ==================== GAME CONFIG ACCESS ====================\n            function getConfig() {\n                return window.gameConfig;\n            }\n\n            function getLevels() {\n                return getConfig().levels || [];\n            }\n\n            function getCompletedLevels() {\n                return getConfig().completedLevels || [];\n            }\n\n            function getSelectedOutfit() {\n                // Default to blue dress if not set\n                if (!getConfig().selectedOutfit) {\n                    getConfig().selectedOutfit = 'outfit_blue_dress';\n                }\n                return getConfig().selectedOutfit;\n            }\n\n            function setSelectedOutfit(outfitId) {\n                getConfig().selectedOutfit = outfitId;\n            }\n\n            // Get the correct image ID for a level based on selected outfit\n            function getLevelImageId(levelIndex) {\n                const level = getLevels()[levelIndex];\n                const selectedOutfit = getSelectedOutfit();\n                \n                // Check if this level has outfit variants\n                if (level.imageVariants && level.imageVariants[selectedOutfit]) {\n                    return level.imageVariants[selectedOutfit];\n                }\n                \n                // Fall back to default imageId\n                return level.imageId;\n            }\n\n            function markLevelCompleted(levelIndex) {\n                const completed = getCompletedLevels();\n                if (!completed.includes(levelIndex)) {\n                    completed.push(levelIndex);\n                    getConfig().completedLevels = completed;\n                }\n            }\n\n            function isLevelUnlocked(levelIndex) {\n                if (levelIndex === 0) return true;\n                return getCompletedLevels().includes(levelIndex - 1);\n            }\n\n            function isLevelCompleted(levelIndex) {\n                return getCompletedLevels().includes(levelIndex);\n            }\n\n            // ==================== MENU RENDERING ====================\n            function renderMenu() {\n                levelGrid.innerHTML = '';\n                const levels = getLevels();\n                \n                levels.forEach((level, index) => {\n                    const card = document.createElement('div');\n                    card.className = 'level-card';\n                    \n                    const unlocked = isLevelUnlocked(index);\n                    const completed = isLevelCompleted(index);\n                    \n                    if (!unlocked) {\n                        card.classList.add('locked');\n                    } else if (completed) {\n                        card.classList.add('completed');\n                    }\n                    \n                    // Only show actual image for COMPLETED levels to avoid story spoilers\n                    if (completed) {\n                        const img = imageCache[level.imageId];\n                        if (img) {\n                            const imgEl = document.createElement('img');\n                            imgEl.className = 'level-card-image';\n                            imgEl.src = img.src;\n                            card.appendChild(imgEl);\n                        }\n                    } else {\n                        // Soft, warm placeholder for uncompleted levels\n                        const placeholder = document.createElement('div');\n                        placeholder.style.cssText = `\n                            width: 100%;\n                            height: 100%;\n                            background: linear-gradient(135deg, #FFF8E7 0%, #FFE4B5 50%, #FFDAB9 100%);\n                            position: absolute;\n                            top: 0;\n                            left: 0;\n                        `;\n                        // Add soft puzzle piece decorations\n                        const decoration = document.createElement('div');\n                        decoration.style.cssText = `\n                            position: absolute;\n                            right: 80px;\n                            top: 50%;\n                            transform: translateY(-50%);\n                            font-size: 40px;\n                            opacity: 0.25;\n                        `;\n                        decoration.textContent = '\ud83e\udde9';\n                        placeholder.appendChild(decoration);\n                        card.appendChild(placeholder);\n                    }\n                    \n                    // Overlay - hide title for locked/uncompleted levels\n                    const overlay = document.createElement('div');\n                    overlay.className = 'level-card-overlay';\n                    const displayTitle = completed ? level.title : (unlocked ? 'Tap to discover...' : 'Coming soon...');\n                    const titleStyle = completed ? '' : 'opacity: 0.8; font-style: italic;';\n                    overlay.innerHTML = `\n                        <div class=\"level-card-number\">Chapter ${index + 1}</div>\n                        <div class=\"level-card-title\" style=\"${titleStyle}\">${displayTitle}</div>\n                    `;\n                    card.appendChild(overlay);\n                    \n                    // Status icon - show for all states\n                    const status = document.createElement('div');\n                    status.className = 'level-card-status';\n                    if (completed) {\n                        status.classList.add('completed');\n                        status.textContent = '\u2713';\n                    } else if (!unlocked) {\n                        status.classList.add('locked');\n                        const lockAsset = lib.getAsset('custom_lock_icon');\n                        if (lockAsset) {\n                            const lockImg = document.createElement('img');\n                            lockImg.src = lockAsset.url;\n                            lockImg.alt = 'Locked';\n                            status.appendChild(lockImg);\n                        } else {\n                            status.textContent = '\ud83d\udd12';\n                        }\n                    } else {\n                        // Unlocked but not completed - show play icon\n                        status.classList.add('playable');\n                        const playAsset = lib.getAsset('play_button_icon');\n                        if (playAsset) {\n                            const playImg = document.createElement('img');\n                            playImg.src = playAsset.url;\n                            playImg.alt = 'Play';\n                            playImg.style.width = '32px';\n                            playImg.style.height = '32px';\n                            playImg.style.objectFit = 'contain';\n                            playImg.style.filter = 'drop-shadow(0 1px 2px rgba(0,0,0,0.2))';\n                            status.appendChild(playImg);\n                        } else {\n                            status.textContent = '\u25b6';\n                        }\n                    }\n                    card.appendChild(status);\n                    \n                    // Click handler\n                    if (unlocked) {\n                        card.addEventListener('click', () => {\n                            playSound('button_tap_sfx');\n                            startLevel(index);\n                        });\n                    }\n                    \n                    levelGrid.appendChild(card);\n                });\n            }\n\n            // ==================== LEVEL MANAGEMENT ====================\n            async function startLevel(levelIndex, delayPreview = false, skipOutfitCheck = false) {\n                const level = getLevels()[levelIndex];\n                \n                // Check if this level has outfit variants and show picker first\n                if (!skipOutfitCheck && level.imageVariants && typeof level.imageVariants === 'object') {\n                    pendingLevelIndex = levelIndex;\n                    pendingDelayPreview = delayPreview;\n                    showOutfitPickerForLevel(levelIndex);\n                    return;\n                }\n                \n                currentLevel = levelIndex;\n                \n                // Load level image (based on selected outfit for levels with variants)\n                const imageIdToLoad = getLevelImageId(levelIndex);\n                try {\n                    currentLevelImage = await loadImage(imageIdToLoad);\n                } catch (e) {\n                    // If variant image not found, fall back to default\n                    lib.log(`Failed to load level image ${imageIdToLoad}, trying fallback: ${e.message}`);\n                    try {\n                        currentLevelImage = await loadImage(level.imageId);\n                    } catch (e2) {\n                        lib.log(`Failed to load fallback image: ${e2.message}`);\n                        return;\n                    }\n                }\n                \n                // Show game screen FIRST so dimensions are calculated correctly\n                titleScreen.style.display = 'none';\n                chaptersScreen.style.display = 'none';\n                gameScreen.style.display = 'flex';\n                \n                // Setup puzzle\n                gridRows = level.gridRows || level.gridSize || 2;\n                gridCols = level.gridCols || level.gridSize || 2;\n                setupPuzzle();\n                \n                // Clear any existing preview timeout\n                if (referencePreviewTimeout) {\n                    clearTimeout(referencePreviewTimeout);\n                    referencePreviewTimeout = null;\n                }\n                \n                peekCount = 0;\n                firstPeekUsed = false; // Reset first peek flag for new level\n                pendingPreviewStart = false;\n                \n                // Set reference state based on level config\n                // showPeekButton: false = reference always ON, no button (Chapter 1)\n                // showPeekButton: true = auto-reveal 3s, then hide, press-and-hold to peek\n                isPeaking = false;\n                if (!level.showPeekButton) {\n                    showReference = true;\n                    referencePreviewMode = false;\n                    peakButton.classList.add('hidden');\n                } else if (delayPreview) {\n                    // Delay preview until star overlay is dismissed\n                    showReference = false;\n                    referencePreviewMode = false;\n                    pendingPreviewStart = true;\n                    peakButton.classList.remove('hidden');\n                    peakButton.classList.remove('active');\n                } else {\n                    // Show reference (semi-transparent) for 3 seconds\n                    showReference = true;\n                    referencePreviewMode = true;\n                    peakButton.classList.remove('hidden');\n                    peakButton.classList.remove('active');\n                    \n                    // Hide the reference after initial visibility time (default 3 seconds)\n                    const visibilityTime = level.initialVisibilityTime || 3000;\n                    referencePreviewTimeout = setTimeout(() => {\n                        referencePreviewMode = false;\n                        showReference = false;\n                        render();\n                        // Show peek tutorial when reference hides for first time\n                        if (!peekTutorialShown) {\n                            showPeekTutorial();\n                        }\n                    }, visibilityTime);\n                }\n                \n                // Hide continue button, show tray in puzzle mode\n                bottomContinueButton.classList.add('hidden');\n                pieceTray.style.display = 'block';\n                pieceTray.classList.remove('story-mode');\n                trayScroll.classList.remove('hidden');\n                trayStoryTitle.classList.add('hidden');\n                trayStoryText.classList.add('hidden');\n                \n                // Update UI\n                chapterTitle.textContent = `Chapter ${currentLevel + 1}`;\n                backButton.classList.remove('hidden');\n                \n                gamePhase = 'playing';\n                \n                // Start music if not playing\n                if (!musicSource) {\n                    playMusic();\n                }\n                \n                render();\n                \n                // Show tutorial for first chapter (only if not already shown)\n                if (levelIndex === 0 && !tutorialShown) {\n                    setTimeout(() => showTutorial(), 800);\n                }\n            }\n\n            function setupPuzzle() {\n                // Calculate canvas size with 10px margins on each side\n                const puzzleArea = document.querySelector('.puzzle-area');\n                const sideMargin = 10;\n                const areaWidth = puzzleArea.clientWidth - (sideMargin * 2);\n                const areaHeight = puzzleArea.clientHeight * 0.98;\n                \n                // Use consistent 1:1 aspect ratio for all puzzles\n                const targetAspect = 1;\n                let canvasWidth, canvasHeight;\n                \n                // Fit square to available space\n                if (areaWidth / targetAspect <= areaHeight) {\n                    canvasWidth = areaWidth;\n                    canvasHeight = canvasWidth / targetAspect;\n                } else {\n                    canvasHeight = areaHeight;\n                    canvasWidth = canvasHeight * targetAspect;\n                }\n                \n                puzzleCanvas.width = canvasWidth;\n                puzzleCanvas.height = canvasHeight;\n                \n                // Cell size fills the entire canvas - no margins\n                cellSize = Math.floor(Math.min(canvasWidth / gridCols, canvasHeight / gridRows));\n                \n                // Recalculate to fill exactly\n                const gridWidth = cellSize * gridCols;\n                const gridHeight = cellSize * gridRows;\n                \n                // Minimal offset to center any rounding difference\n                gridOffsetX = (canvasWidth - gridWidth) / 2;\n                gridOffsetY = (canvasHeight - gridHeight) / 2;\n                \n                // Generate jigsaw edge configurations\n                generateJigsawEdges();\n                \n                // Create grid cells\n                gridCells = [];\n                for (let row = 0; row < gridRows; row++) {\n                    for (let col = 0; col < gridCols; col++) {\n                        gridCells.push({\n                            row,\n                            col,\n                            x: gridOffsetX + col * cellSize,\n                            y: gridOffsetY + row * cellSize,\n                            filled: false\n                        });\n                    }\n                }\n                \n                // Create pieces\n                pieces = [];\n                placedPieces = [];\n                \n                const totalPieces = gridRows * gridCols;\n                const pieceIndices = [];\n                for (let i = 0; i < totalPieces; i++) {\n                    pieceIndices.push(i);\n                }\n                \n                // Shuffle pieces\n                for (let i = pieceIndices.length - 1; i > 0; i--) {\n                    const j = Math.floor(Math.random() * (i + 1));\n                    [pieceIndices[i], pieceIndices[j]] = [pieceIndices[j], pieceIndices[i]];\n                }\n                \n                // Create piece objects with edge info\n                pieceIndices.forEach((index, trayIndex) => {\n                    const row = Math.floor(index / gridCols);\n                    const col = index % gridCols;\n                    pieces.push({\n                        id: index,\n                        row,\n                        col,\n                        trayIndex,\n                        placed: false,\n                        edges: pieceEdges[row][col]\n                    });\n                });\n                \n                // Render tray\n                renderTray();\n            }\n            \n            // Generate consistent jigsaw edge patterns for rectangular grid\n            function generateJigsawEdges() {\n                pieceEdges = [];\n                \n                // Create edge map: each internal edge is randomly tab (1) or blank (-1)\n                // Horizontal edges (between rows)\n                const horizontalEdges = [];\n                for (let row = 0; row <= gridRows; row++) {\n                    horizontalEdges[row] = [];\n                    for (let col = 0; col < gridCols; col++) {\n                        if (row === 0 || row === gridRows) {\n                            horizontalEdges[row][col] = 0; // Border = flat\n                        } else {\n                            horizontalEdges[row][col] = Math.random() > 0.5 ? 1 : -1;\n                        }\n                    }\n                }\n                \n                // Vertical edges (between columns)\n                const verticalEdges = [];\n                for (let row = 0; row < gridRows; row++) {\n                    verticalEdges[row] = [];\n                    for (let col = 0; col <= gridCols; col++) {\n                        if (col === 0 || col === gridCols) {\n                            verticalEdges[row][col] = 0; // Border = flat\n                        } else {\n                            verticalEdges[row][col] = Math.random() > 0.5 ? 1 : -1;\n                        }\n                    }\n                }\n                \n                // Assign edges to each piece (top, right, bottom, left)\n                for (let row = 0; row < gridRows; row++) {\n                    pieceEdges[row] = [];\n                    for (let col = 0; col < gridCols; col++) {\n                        pieceEdges[row][col] = {\n                            top: horizontalEdges[row][col],\n                            bottom: -horizontalEdges[row + 1][col], // Opposite of neighbor\n                            left: verticalEdges[row][col],\n                            right: -verticalEdges[row][col + 1] // Opposite of neighbor\n                        };\n                    }\n                }\n            }\n            \n            // Draw a jigsaw edge path - classic puzzle piece shape\n            function drawJigsawEdge(ctx, startX, startY, length, edgeType, direction) {\n                // edgeType: 0 = flat, 1 = tab (outward), -1 = blank (inward)\n                // direction: 'right', 'down', 'left', 'up'\n                \n                if (edgeType === 0) {\n                    // Flat edge\n                    switch(direction) {\n                        case 'right': ctx.lineTo(startX + length, startY); break;\n                        case 'down': ctx.lineTo(startX, startY + length); break;\n                        case 'left': ctx.lineTo(startX - length, startY); break;\n                        case 'up': ctx.lineTo(startX, startY - length); break;\n                    }\n                    return;\n                }\n                \n                const sign = edgeType; // 1 or -1\n                \n                // Normalized curve points (0 to 1) for an OUTWARD tab (y > 0)\n                // Scaled to have max Y ~0.25\n                const curves = [\n                    // cx1, cy1, cx2, cy2, x, y\n                    [0.2, -0.01, 0.3, 0.01, 0.38, 0],\n                    [0.45, 0.03, 0.25, 0.08, 0.25, 0.15],\n                    [0.25, 0.23, 0.35, 0.25, 0.5, 0.25],\n                    [0.65, 0.25, 0.75, 0.23, 0.75, 0.15],\n                    [0.75, 0.08, 0.55, 0.03, 0.62, 0],\n                    [0.7, 0.01, 0.8, -0.01, 1, 0]\n                ];\n                \n                for (let i = 0; i < curves.length; i++) {\n                    const c = curves[i];\n                    let cx1, cy1, cx2, cy2, ex, ey;\n                    \n                    if (direction === 'right') {\n                        cx1 = startX + c[0] * length; cy1 = startY - c[1] * length * sign;\n                        cx2 = startX + c[2] * length; cy2 = startY - c[3] * length * sign;\n                        ex = startX + c[4] * length;  ey = startY - c[5] * length * sign;\n                    } else if (direction === 'down') {\n                        cx1 = startX + c[1] * length * sign; cy1 = startY + c[0] * length;\n                        cx2 = startX + c[3] * length * sign; cy2 = startY + c[2] * length;\n                        ex = startX + c[5] * length * sign;  ey = startY + c[4] * length;\n                    } else if (direction === 'left') {\n                        cx1 = startX - c[0] * length; cy1 = startY + c[1] * length * sign;\n                        cx2 = startX - c[2] * length; cy2 = startY + c[3] * length * sign;\n                        ex = startX - c[4] * length;  ey = startY + c[5] * length * sign;\n                    } else if (direction === 'up') {\n                        cx1 = startX - c[1] * length * sign; cy1 = startY - c[0] * length;\n                        cx2 = startX - c[3] * length * sign; cy2 = startY - c[2] * length;\n                        ex = startX - c[5] * length * sign;  ey = startY - c[4] * length;\n                    }\n                    \n                    ctx.bezierCurveTo(cx1, cy1, cx2, cy2, ex, ey);\n                }\n            }\n            \n            // Create complete jigsaw piece path\n            function createPiecePath(ctx, x, y, size, edges) {\n                ctx.beginPath();\n                ctx.moveTo(x, y);\n                \n                // Top edge (left to right)\n                drawJigsawEdge(ctx, x, y, size, edges.top, 'right');\n                \n                // Right edge (top to bottom)  \n                drawJigsawEdge(ctx, x + size, y, size, edges.right, 'down');\n                \n                // Bottom edge (right to left)\n                drawJigsawEdge(ctx, x + size, y + size, size, edges.bottom, 'left');\n                \n                // Left edge (bottom to top)\n                drawJigsawEdge(ctx, x, y + size, size, edges.left, 'up');\n                \n                ctx.closePath();\n            }\n\n            function renderTray() {\n                trayScroll.innerHTML = '';\n                \n                // Calculate piece size based on tray height\n                const trayRect = pieceTray.getBoundingClientRect();\n                const trayInnerHeight = trayRect.height - 40;\n                const basePieceSize = Math.min(trayInnerHeight * 0.75, cellSize * 0.85, 120);\n                const pieceDisplaySize = basePieceSize;\n                const canvasSize = pieceDisplaySize * (1 + TAB_SIZE * 2); // Extra space for tabs\n                \n                pieces.filter(p => !p.placed).forEach(piece => {\n                    const canvas = document.createElement('canvas');\n                    canvas.className = 'tray-piece';\n                    canvas.width = canvasSize;\n                    canvas.height = canvasSize;\n                    canvas.dataset.pieceId = piece.id;\n                    canvas.style.width = canvasSize + 'px';\n                    canvas.style.height = canvasSize + 'px';\n                    \n                    const pctx = canvas.getContext('2d');\n                    \n                    // Draw jigsaw piece with clipping\n                    const offset = pieceDisplaySize * TAB_SIZE;\n                    \n                    // Create clipping path for jigsaw shape\n                    createPiecePath(pctx, offset, offset, pieceDisplaySize, piece.edges);\n                    pctx.save();\n                    pctx.clip();\n                    \n                    // Draw piece from source image, scaling to match full canvas\n                    const scaleX = pieceDisplaySize * gridCols / puzzleCanvas.width;\n                    const scaleY = pieceDisplaySize * gridRows / puzzleCanvas.height;\n                    const totalW = puzzleCanvas.width * scaleX;\n                    const totalH = puzzleCanvas.height * scaleY;\n                    const drawX = -piece.col * pieceDisplaySize - (gridOffsetX * scaleX) + offset;\n                    const drawY = -piece.row * pieceDisplaySize - (gridOffsetY * scaleY) + offset;\n                    \n                    drawCoverImage(pctx, currentLevelImage, drawX, drawY, totalW, totalH);\n                    \n                    pctx.restore();\n                    \n                    // Draw border\n                    createPiecePath(pctx, offset, offset, pieceDisplaySize, piece.edges);\n                    pctx.strokeStyle = 'rgba(0,0,0,0.4)';\n                    pctx.lineWidth = 2;\n                    pctx.stroke();\n                    \n                    // Add subtle shadow\n                    pctx.shadowColor = 'rgba(0,0,0,0.2)';\n                    pctx.shadowBlur = 4;\n                    pctx.shadowOffsetX = 2;\n                    pctx.shadowOffsetY = 2;\n                    \n                    trayScroll.appendChild(canvas);\n                });\n            }\n\n            // Helper to draw image like object-fit: cover\n            function drawCoverImage(context, img, destX, destY, destW, destH) {\n                const imgRatio = img.width / img.height;\n                const destRatio = destW / destH;\n                let srcX, srcY, srcW, srcH;\n                \n                if (imgRatio > destRatio) {\n                    srcH = img.height;\n                    srcW = img.height * destRatio;\n                    srcX = (img.width - srcW) / 2;\n                    srcY = 0;\n                } else {\n                    srcW = img.width;\n                    srcH = img.width / destRatio;\n                    srcX = 0;\n                    srcY = (img.height - srcH) / 2;\n                }\n                \n                context.drawImage(img, srcX, srcY, srcW, srcH, destX, destY, destW, destH);\n            }\n\n            // ==================== RENDERING ====================\n            function render() {\n                if (gamePhase !== 'playing') return;\n                \n                ctx.clearRect(0, 0, puzzleCanvas.width, puzzleCanvas.height);\n                \n                // Fill canvas with soft background (visible where pieces aren't placed)\n                ctx.fillStyle = '#F0EDE5';\n                ctx.fillRect(0, 0, puzzleCanvas.width, puzzleCanvas.height);\n                \n                // Draw reference image FIRST (as background layer) if enabled\n                // This ensures it appears UNDER all puzzle pieces\n                if (showReference && currentLevelImage) {\n                    // Progressive opacity based on current level\n                    ctx.globalAlpha = getReferenceOpacity(currentLevel);\n                    drawCoverImage(\n                        ctx,\n                        currentLevelImage,\n                        0, 0,\n                        puzzleCanvas.width, puzzleCanvas.height\n                    );\n                    ctx.globalAlpha = 1;\n                    \n                    // Draw faint jigsaw grid lines on reference\n                    ctx.strokeStyle = 'rgba(0,0,0,0.15)';\n                    ctx.lineWidth = 1;\n                    for (let row = 0; row < gridRows; row++) {\n                        for (let col = 0; col < gridCols; col++) {\n                            const x = gridOffsetX + col * cellSize;\n                            const y = gridOffsetY + row * cellSize;\n                            const edges = pieceEdges[row][col];\n                            createPiecePath(ctx, x, y, cellSize, edges);\n                            ctx.stroke();\n                        }\n                    }\n                }\n                \n                // Draw empty slot outlines (jigsaw shaped)\n                ctx.strokeStyle = '#CCC';\n                ctx.lineWidth = 2;\n                ctx.setLineDash([5, 5]);\n                for (let row = 0; row < gridRows; row++) {\n                    for (let col = 0; col < gridCols; col++) {\n                        const cell = gridCells[row * gridCols + col];\n                        if (!cell.filled) {\n                            const x = gridOffsetX + col * cellSize;\n                            const y = gridOffsetY + row * cellSize;\n                            const edges = pieceEdges[row][col];\n                            createPiecePath(ctx, x, y, cellSize, edges);\n                            ctx.stroke();\n                        }\n                    }\n                }\n                ctx.setLineDash([]);\n                \n                // Draw placed pieces (jigsaw shaped)\n                placedPieces.forEach(piece => {\n                    const destX = gridOffsetX + piece.col * cellSize;\n                    const destY = gridOffsetY + piece.row * cellSize;\n                    \n                    // Create clipping path for jigsaw shape\n                    createPiecePath(ctx, destX, destY, cellSize, piece.edges);\n                    ctx.save();\n                    ctx.clip();\n                    \n                    // Draw piece image - scale to fill entire canvas\n                    drawCoverImage(\n                        ctx,\n                        currentLevelImage,\n                        0, 0, puzzleCanvas.width, puzzleCanvas.height\n                    );\n                    \n                    ctx.restore();\n                    \n                    // Draw piece border\n                    createPiecePath(ctx, destX, destY, cellSize, piece.edges);\n                    ctx.strokeStyle = 'rgba(0,0,0,0.2)';\n                    ctx.lineWidth = 1;\n                    ctx.stroke();\n                });\n                \n                // Draw highlighted cell (jigsaw shaped)\n                if (highlightedCell >= 0 && highlightedCell < gridCells.length) {\n                    const cell = gridCells[highlightedCell];\n                    if (!cell.filled) {\n                        const edges = pieceEdges[cell.row][cell.col];\n                        createPiecePath(ctx, cell.x, cell.y, cellSize, edges);\n                        ctx.fillStyle = 'rgba(74, 144, 217, 0.3)';\n                        ctx.fill();\n                        ctx.strokeStyle = '#4A90D9';\n                        ctx.lineWidth = 3;\n                        ctx.stroke();\n                    }\n                }\n            }\n\n            function startPeaking() {\n                const level = getLevels()[currentLevel];\n                if (!level.showPeekButton) return; // Level doesn't use peak\n                isPeaking = true;\n                \n                // Only count peeks after the first one (first peek is free)\n                if (firstPeekUsed) {\n                    peekCount++;\n                } else {\n                    firstPeekUsed = true;\n                }\n                \n                showReference = true;\n                peakButton.classList.add('active');\n                peekTutorialShown = true; // They found the button, no need to show hint anymore\n                // Hide peek tutorial when user uses the button\n                hidePeekTutorial();\n                render();\n            }\n\n            function stopPeaking() {\n                const level = getLevels()[currentLevel];\n                if (!level.showPeekButton) return; // Level doesn't use peak\n                isPeaking = false;\n                if (!referencePreviewMode) {\n                    showReference = false;\n                }\n                peakButton.classList.remove('active');\n                render();\n            }\n\n            // ==================== INPUT HANDLING ====================\n            function getCanvasCoords(clientX, clientY) {\n                const rect = puzzleCanvas.getBoundingClientRect();\n                return {\n                    x: (clientX - rect.left) * (puzzleCanvas.width / rect.width),\n                    y: (clientY - rect.top) * (puzzleCanvas.height / rect.height)\n                };\n            }\n\n            function findCellAtPosition(x, y) {\n                for (let i = 0; i < gridCells.length; i++) {\n                    const cell = gridCells[i];\n                    if (x >= cell.x && x < cell.x + cellSize &&\n                        y >= cell.y && y < cell.y + cellSize) {\n                        return i;\n                    }\n                }\n                return -1;\n            }\n\n            function startDrag(piece, clientX, clientY) {\n                draggingPiece = piece;\n                \n                // Hide tutorial when user starts dragging\n                hideTutorial();\n                \n                // Setup dragging canvas with space for tabs\n                const displaySize = cellSize * PIECE_SCALE_DRAGGING;\n                const canvasSize = Math.max(displaySize * (1 + TAB_SIZE * 2), 100);\n                draggingPieceCanvas.width = canvasSize;\n                draggingPieceCanvas.height = canvasSize;\n                draggingPieceCanvas.style.display = 'block';\n                \n                // Draw jigsaw piece on dragging canvas\n                const offset = displaySize * TAB_SIZE;\n                \n                // Create clipping path\n                createPiecePath(draggingCtx, offset, offset, displaySize, piece.edges);\n                draggingCtx.save();\n                draggingCtx.clip();\n                \n                // Scale to match full canvas proportions\n                const scaleX = displaySize * gridCols / puzzleCanvas.width;\n                const scaleY = displaySize * gridRows / puzzleCanvas.height;\n                const totalW = puzzleCanvas.width * scaleX;\n                const totalH = puzzleCanvas.height * scaleY;\n                const drawX = -piece.col * displaySize - (gridOffsetX * scaleX) + offset;\n                const drawY = -piece.row * displaySize - (gridOffsetY * scaleY) + offset;\n                \n                drawCoverImage(draggingCtx, currentLevelImage, drawX, drawY, totalW, totalH);\n                \n                draggingCtx.restore();\n                \n                // Add border\n                createPiecePath(draggingCtx, offset, offset, displaySize, piece.edges);\n                draggingCtx.strokeStyle = 'rgba(74, 144, 217, 0.8)';\n                draggingCtx.lineWidth = 3;\n                draggingCtx.stroke();\n                \n                // Add drop shadow effect\n                draggingCtx.shadowColor = 'rgba(0,0,0,0.3)';\n                draggingCtx.shadowBlur = 8;\n                draggingCtx.shadowOffsetX = 4;\n                draggingCtx.shadowOffsetY = 4;\n                \n                // Position at center\n                dragOffsetX = canvasSize / 2;\n                dragOffsetY = canvasSize / 2;\n                updateDragPosition(clientX, clientY);\n            }\n\n            function updateDragPosition(clientX, clientY) {\n                if (!draggingPiece) return;\n                \n                draggingPieceCanvas.style.left = (clientX - dragOffsetX) + 'px';\n                draggingPieceCanvas.style.top = (clientY - dragOffsetY) + 'px';\n                \n                // Check for cell highlight - only highlight if it's the correct cell\n                const coords = getCanvasCoords(clientX, clientY);\n                const cellIndex = findCellAtPosition(coords.x, coords.y);\n                \n                if (cellIndex >= 0) {\n                    const cell = gridCells[cellIndex];\n                    // Only highlight if this is the correct position for the dragging piece\n                    if (cell.row === draggingPiece.row && cell.col === draggingPiece.col && !cell.filled) {\n                        highlightedCell = cellIndex;\n                    } else {\n                        highlightedCell = -1;\n                    }\n                } else {\n                    highlightedCell = -1;\n                }\n                \n                render();\n            }\n\n            function endDrag(clientX, clientY) {\n                if (!draggingPiece) return;\n                \n                const coords = getCanvasCoords(clientX, clientY);\n                const cellIndex = findCellAtPosition(coords.x, coords.y);\n                \n                let placed = false;\n                \n                if (cellIndex >= 0) {\n                    const cell = gridCells[cellIndex];\n                    \n                    // Check if this is the correct cell for this piece\n                    if (cell.row === draggingPiece.row && cell.col === draggingPiece.col && !cell.filled) {\n                        // Correct placement!\n                        cell.filled = true;\n                        draggingPiece.placed = true;\n                        placedPieces.push(draggingPiece);\n                        placed = true;\n                        \n                        playSound('piece_correct_sfx');\n                        \n                        // Check for completion\n                        if (placedPieces.length === gridRows * gridCols) {\n                            setTimeout(() => completePuzzle(), 300);\n                        }\n                    } else {\n                        // Wrong placement\n                        playSound('piece_incorrect_sfx');\n                    }\n                }\n                \n                // Hide dragging canvas\n                draggingPieceCanvas.style.display = 'none';\n                draggingPiece = null;\n                highlightedCell = -1;\n                \n                // Update tray\n                renderTray();\n                render();\n            }\n\n            // Touch events for tray pieces\n            let potentialDragPiece = null;\n            let potentialDragTouch = {x: 0, y: 0};\n            \n            trayScroll.addEventListener('touchstart', (e) => {\n                if (gamePhase !== 'playing') return;\n                \n                const touch = e.touches[0];\n                const target = e.target;\n                \n                if (target && target.classList.contains('tray-piece')) {\n                    const pieceId = parseInt(target.dataset.pieceId);\n                    potentialDragPiece = pieces.find(p => p.id === pieceId);\n                    potentialDragTouch = {x: touch.clientX, y: touch.clientY};\n                } else {\n                    potentialDragPiece = null;\n                }\n            }, { passive: true });\n            \n            trayScroll.addEventListener('touchmove', (e) => {\n                if (gamePhase !== 'playing') return;\n                \n                if (draggingPiece) {\n                    if (e.cancelable) e.preventDefault();\n                    return;\n                }\n                \n                if (potentialDragPiece) {\n                    const touch = e.touches[0];\n                    const deltaX = Math.abs(touch.clientX - potentialDragTouch.x);\n                    const deltaY = Math.abs(touch.clientY - potentialDragTouch.y);\n                    \n                    if (deltaY > 10 && deltaY > deltaX) {\n                        initAudio();\n                        if (potentialDragPiece && !potentialDragPiece.placed) {\n                            startDrag(potentialDragPiece, touch.clientX, touch.clientY);\n                        }\n                        potentialDragPiece = null;\n                        if (e.cancelable) e.preventDefault();\n                    } else if (deltaX > 10) {\n                        potentialDragPiece = null;\n                    }\n                }\n            }, { passive: false });\n            \n            trayScroll.addEventListener('touchend', (e) => {\n                potentialDragPiece = null;\n            }, { passive: true });\n\n            document.addEventListener('touchmove', (e) => {\n                if (draggingPiece) {\n                    e.preventDefault();\n                    const touch = e.touches[0];\n                    updateDragPosition(touch.clientX, touch.clientY);\n                }\n            }, { passive: false });\n\n            document.addEventListener('touchend', (e) => {\n                if (draggingPiece) {\n                    const touch = e.changedTouches[0];\n                    endDrag(touch.clientX, touch.clientY);\n                }\n            });\n\n            // Mouse events for tray pieces\n            trayScroll.addEventListener('mousedown', (e) => {\n                if (gamePhase !== 'playing') return;\n                \n                const target = e.target;\n                \n                if (target.classList.contains('tray-piece')) {\n                    e.preventDefault();\n                    initAudio();\n                    \n                    const pieceId = parseInt(target.dataset.pieceId);\n                    const piece = pieces.find(p => p.id === pieceId);\n                    \n                    if (piece && !piece.placed) {\n                        startDrag(piece, e.clientX, e.clientY);\n                    }\n                }\n            });\n\n            document.addEventListener('mousemove', (e) => {\n                if (draggingPiece) {\n                    updateDragPosition(e.clientX, e.clientY);\n                }\n            });\n\n            document.addEventListener('mouseup', (e) => {\n                if (draggingPiece) {\n                    endDrag(e.clientX, e.clientY);\n                }\n            });\n\n            // Peak button - press and hold\n            peakButton.addEventListener('mousedown', (e) => {\n                e.preventDefault();\n                initAudio();\n                playSound('button_tap_sfx');\n                startPeaking();\n            });\n\n            peakButton.addEventListener('mouseup', () => {\n                stopPeaking();\n            });\n\n            peakButton.addEventListener('mouseleave', () => {\n                if (isPeaking) stopPeaking();\n            });\n\n            peakButton.addEventListener('touchstart', (e) => {\n                e.preventDefault();\n                initAudio();\n                playSound('button_tap_sfx');\n                startPeaking();\n            });\n\n            peakButton.addEventListener('touchend', (e) => {\n                e.preventDefault();\n                stopPeaking();\n            });\n\n            peakButton.addEventListener('touchcancel', () => {\n                stopPeaking();\n            });\n\n            // Back button\n            backButton.addEventListener('click', () => {\n                playSound('button_tap_sfx');\n                showMenu();\n            });\n\n            // Outfit button\n            outfitButton.addEventListener('click', () => {\n                playSound('button_tap_sfx');\n                showOutfitPopup();\n            });\n\n            function showOutfitPopup() {\n                // Check if outfits are unlocked (Chapter 4+, level index 3+)\n                const outfitGrid = document.getElementById('outfit-grid');\n                const isUnlocked = currentLevel >= 3;\n                \n                // Update grid content based on unlock state\n                updateOutfitGridContent(isUnlocked);\n                \n                // Update selected state in popup before showing\n                const currentSelectedOutfit = getSelectedOutfit();\n                outfitPopup.querySelectorAll('.outfit-option').forEach(opt => {\n                    if (opt.dataset.outfitId === currentSelectedOutfit) {\n                        opt.classList.add('selected');\n                    } else {\n                        opt.classList.remove('selected');\n                    }\n                });\n                outfitPopup.classList.remove('hidden');\n            }\n            \n            function showOutfitPickerForLevel(levelIndex) {\n                // Show outfit picker with a message about the upcoming chapter\n                const level = getLevels()[levelIndex];\n                const popupTitle = outfitPopup.querySelector('.outfit-popup-title');\n                if (popupTitle) {\n                    // Keep the dress icon image when updating title\n                    const dressAsset = lib.getAsset('dress_icon');\n                    const dressIconHtml = dressAsset \n                        ? `<img id=\"outfit-dress-icon\" class=\"outfit-popup-title-icon\" src=\"${dressAsset.url}\" alt=\"\">` \n                        : '';\n                    popupTitle.innerHTML = `${dressIconHtml} Pick an Outfit`;\n                }\n                \n                // Update grid content - outfits unlocked at Chapter 4 (level index 3)\n                const isUnlocked = levelIndex >= 3;\n                updateOutfitGridContent(isUnlocked);\n                \n                // Update selected state\n                const currentSelectedOutfit = getSelectedOutfit();\n                outfitPopup.querySelectorAll('.outfit-option').forEach(opt => {\n                    if (opt.dataset.outfitId === currentSelectedOutfit) {\n                        opt.classList.add('selected');\n                    } else {\n                        opt.classList.remove('selected');\n                    }\n                });\n                \n                // Show the popup\n                outfitPopup.classList.remove('hidden');\n            }\n\n            outfitCloseButton.addEventListener('click', () => {\n                playSound('button_tap_sfx');\n                outfitPopup.classList.add('hidden');\n                \n                // If there was a pending level, start it with current outfit\n                if (pendingLevelIndex !== null) {\n                    const levelToStart = pendingLevelIndex;\n                    const delayPreview = pendingDelayPreview;\n                    pendingLevelIndex = null;\n                    pendingDelayPreview = false;\n                    startLevel(levelToStart, delayPreview, true);\n                }\n            });\n\n            // Close outfit popup when clicking outside content\n            outfitPopup.addEventListener('click', (e) => {\n                if (e.target === outfitPopup) {\n                    playSound('button_tap_sfx');\n                    outfitPopup.classList.add('hidden');\n                    \n                    // If there was a pending level, start it with current outfit\n                    if (pendingLevelIndex !== null) {\n                        const levelToStart = pendingLevelIndex;\n                        const delayPreview = pendingDelayPreview;\n                        pendingLevelIndex = null;\n                        pendingDelayPreview = false;\n                        startLevel(levelToStart, delayPreview, true);\n                    }\n                }\n            });\n\n            // Bottom continue button (in bottom controls)\n            bottomContinueButton.addEventListener('click', () => {\n                playSound('button_tap_sfx');\n                showStarOverlay();\n            });\n\n            // Star overlay continue button\n            document.getElementById('star-continue-button').addEventListener('click', () => {\n                playSound('button_tap_sfx');\n                document.getElementById('star-overlay').classList.add('hidden');\n                \n                // Start the delayed preview if pending\n                if (pendingPreviewStart) {\n                    pendingPreviewStart = false;\n                    const level = getLevels()[currentLevel];\n                    if (level.showPeekButton) {\n                        showReference = true;\n                        referencePreviewMode = true;\n                        render();\n                        \n                        // Hide the reference after initial visibility time (default 3 seconds)\n                        const visTime = getLevels()[currentLevel].initialVisibilityTime || 3000;\n                        referencePreviewTimeout = setTimeout(() => {\n                            referencePreviewMode = false;\n                            showReference = false;\n                            render();\n                            if (!peekTutorialShown) {\n                                showPeekTutorial();\n                            }\n                        }, visTime);\n                    }\n                }\n            });\n\n            function showStarOverlay() {\n                const starOverlay = document.getElementById('star-overlay');\n                starOverlay.classList.remove('hidden');\n                \n                // Calculate stars based on peek count and free peeks per chapter\n                let stars = 3;\n                if (currentLevel !== 0) { // Level 1 always gives 3 stars\n                    // Determine free peeks based on chapter\n                    let freePeeks;\n                    if (currentLevel <= 2) { // Chapters 2-3 (levels 1-2)\n                        freePeeks = 2;\n                    } else { // Chapter 4+ (levels 3+)\n                        freePeeks = 3;\n                    }\n                    \n                    const overPeeks = peekCount - freePeeks;\n                    if (overPeeks >= 3) stars = 1;\n                    else if (overPeeks > 0) stars = 2;\n                    // else stars stays at 3 (within free peek allowance)\n                }\n                \n                // Reset visual state\n                for (let i = 1; i <= 3; i++) {\n                    const starEl = document.getElementById(`star-${i}`);\n                    starEl.classList.remove('filled');\n                }\n                document.getElementById('star-message').textContent = \"\";\n                \n                // Messages based on stars\n                const messages = [\n                    \"\",\n                    \"Good! Try peeking less next time.\",\n                    \"Great job! Only a few peeks.\",\n                    \"Perfect! No peeks needed!\"\n                ];\n                \n                // Start next level in the background visually (overlying the next level screen)\n                const levels = getLevels();\n                // We actually don't start level here, because the user wants to see the completed image, click continue, THEN show star system over the \"next level's screen\".\n                // Let's transition to the next level before showing stars!\n                \n                if (currentLevel < levels.length - 1) {\n                    // Pre-start next level so it's in the background\n                    // Pass true to delay the reference preview until star overlay dismissed\n                    startLevel(currentLevel + 1, true);\n                    // But currentLevel is now updated! So we must decrement it back if they dismiss? \n                    // No, when they dismiss they just continue playing the level we just started.\n                    // But wait, the continue button above calls startLevel AGAIN.\n                    // Let's fix the continue button logic below.\n                } else {\n                    showMenu();\n                }\n                \n                // After changing level, re-show star overlay because menu/game switch might have hidden it? No, z-index is 1500 and it's outside screens.\n                \n                // Animate stars with bouncy pop - 0.2 second cascade\n                for (let i = 1; i <= stars; i++) {\n                    setTimeout(() => {\n                        document.getElementById(`star-${i}`).classList.add('filled');\n                        playSound('star_reveal_sfx');\n                    }, i * 200);\n                }\n                \n                setTimeout(() => {\n                    document.getElementById('star-message').textContent = messages[stars];\n                    if (stars === 3) {\n                        createConfetti(150); // Huge confetti explosion for 3 stars!\n                        playSound('puzzle_complete_sfx'); // Extra sound effect for max stars\n                    } else if (stars > 0) {\n                        createConfetti(40); // Small confetti for completing with fewer stars\n                    }\n                }, stars * 200 + 400);\n            }\n\n            // ==================== COMPLETION ====================\n            function completePuzzle() {\n                gamePhase = 'completed';\n                \n                // Mark level as completed\n                markLevelCompleted(currentLevel);\n                \n                // Play completion sound\n                playSound('puzzle_complete_sfx');\n                \n                // Show confetti\n                createConfetti();\n                \n                // Hide peak button\n                peakButton.classList.add('hidden');\n                \n                // Draw completed image seamlessly on canvas\n                renderCompletedImage();\n                \n                // Transform tray to story mode after short delay\n                setTimeout(() => {\n                    playSound('story_reveal_sfx');\n                    \n                    const level = getLevels()[currentLevel];\n                    \n                    // Switch tray to story mode\n                    pieceTray.classList.add('story-mode');\n                    trayScroll.classList.add('hidden');\n                    trayStoryTitle.classList.remove('hidden');\n                    trayStoryText.classList.remove('hidden');\n                    \n                    // Set story content\n                    trayStoryTitle.textContent = level.title;\n                    trayStoryText.textContent = level.story;\n                    \n                    // Show continue button\n                    bottomContinueButton.classList.remove('hidden');\n                    \n                    // Update bottom continue button text\n                    const levels = getLevels();\n                    if (currentLevel < levels.length - 1) {\n                        bottomContinueButton.textContent = 'Continue';\n                    } else {\n                        bottomContinueButton.textContent = 'Back to Menu';\n                    }\n                }, 800);\n            }\n            \n            function renderCompletedImage() {\n                // Clear canvas and draw full seamless image filling entire canvas\n                ctx.clearRect(0, 0, puzzleCanvas.width, puzzleCanvas.height);\n                \n                // Draw the complete illustration filling the entire canvas - no white frame\n                drawCoverImage(\n                    ctx,\n                    currentLevelImage,\n                    0, 0,\n                    puzzleCanvas.width, puzzleCanvas.height\n                );\n            }\n\n            function createConfetti(amount = 50) {\n                const colors = ['#F5C542', '#4A90D9', '#7BC47F', '#FF6B6B', '#9B59B6', '#FFFFFF', '#FFD700'];\n                \n                for (let i = 0; i < amount; i++) {\n                    setTimeout(() => {\n                        const confetti = document.createElement('div');\n                        confetti.className = 'confetti';\n                        // Spread confetti out more widely\n                        confetti.style.left = (Math.random() * 120 - 10) + 'vw';\n                        confetti.style.top = -20 + 'px';\n                        confetti.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)];\n                        confetti.style.borderRadius = Math.random() > 0.5 ? '50%' : '4px';\n                        const size = 8 + Math.random() * 12;\n                        confetti.style.width = size + 'px';\n                        confetti.style.height = size + 'px';\n                        confetti.style.boxShadow = `0 0 ${Math.random() * 8}px ${confetti.style.backgroundColor}`;\n                        \n                        // Vary animation duration for more dynamic effect\n                        confetti.style.animationDuration = (2 + Math.random() * 2.5) + 's';\n                        \n                        document.body.appendChild(confetti);\n                        \n                        setTimeout(() => confetti.remove(), 5000);\n                    }, i * (1500 / amount)); // Distribute evenly over 1.5 seconds\n                }\n            }\n\n            // ==================== MENU ====================\n            function showTitleScreen() {\n                gamePhase = 'menu';\n                titleScreen.style.display = 'flex';\n                chaptersScreen.style.display = 'none';\n                gameScreen.style.display = 'none';\n                editPanel.classList.add('hidden');\n                \n                // Update continue button state based on progress\n                const completedLevels = getCompletedLevels();\n                if (completedLevels.length === 0) {\n                    continueButton.style.display = 'none';\n                } else {\n                    continueButton.style.display = 'block';\n                }\n            }\n\n            function showChaptersScreen() {\n                gamePhase = 'menu';\n                titleScreen.style.display = 'none';\n                chaptersScreen.style.display = 'flex';\n                gameScreen.style.display = 'none';\n                editPanel.classList.add('hidden');\n                renderMenu();\n            }\n\n            function showMenu() {\n                gamePhase = 'menu';\n                hideTutorial(); // Hide tutorial if showing\n                if (mode === 'edit') {\n                    titleScreen.style.display = 'none';\n                    chaptersScreen.style.display = 'none';\n                    gameScreen.style.display = 'none';\n                    editPanel.classList.remove('hidden');\n                    renderEditPanel();\n                } else {\n                    showTitleScreen();\n                }\n            }\n\n            // Get the next level to play (first incomplete level)\n            function getNextLevelToPlay() {\n                const completed = getCompletedLevels();\n                const levels = getLevels();\n                \n                for (let i = 0; i < levels.length; i++) {\n                    if (!completed.includes(i)) {\n                        return i;\n                    }\n                }\n                \n                // All levels completed, return last level\n                return levels.length - 1;\n            }\n\n            // Title screen button handlers\n            startButton.addEventListener('click', () => {\n                playSound('button_tap_sfx');\n                initAudio();\n                startLevel(0);\n            });\n\n            continueButton.addEventListener('click', () => {\n                playSound('button_tap_sfx');\n                initAudio();\n                const nextLevel = getNextLevelToPlay();\n                startLevel(nextLevel);\n            });\n\n            viewChaptersButton.addEventListener('click', () => {\n                playSound('button_tap_sfx');\n                showChaptersScreen();\n            });\n\n            chaptersBackButton.addEventListener('click', () => {\n                playSound('button_tap_sfx');\n                showTitleScreen();\n            });\n\n            // ==================== IMAGE PREVIEW ====================\n            function showImagePreview(imageSrc, title) {\n                previewImage.src = imageSrc;\n                previewTitle.textContent = title;\n                imagePreviewPopup.classList.remove('hidden');\n            }\n\n            function hideImagePreview() {\n                imagePreviewPopup.classList.add('hidden');\n            }\n\n            // Close preview on click\n            imagePreviewPopup.addEventListener('click', () => {\n                hideImagePreview();\n            });\n\n            // ==================== EDIT MODE ====================\n            function setupEditMode() {\n                editPanel.classList.remove('hidden');\n                titleScreen.style.display = 'none';\n                chaptersScreen.style.display = 'none';\n                gameScreen.style.display = 'none';\n                \n                renderEditPanel();\n            }\n\n            function renderEditPanel() {\n                editLevelsContainer.innerHTML = '';\n                const levels = getLevels();\n                \n                const outfitNames = {\n                    'outfit_blue_dress': '\ud83d\udc57 Blue Dress',\n                    'outfit_overalls': '\ud83d\udc56 Overalls',\n                    'outfit_purple_shirt': '\ud83d\udc9c Purple Shirt'\n                };\n                \n                levels.forEach((level, index) => {\n                    const card = document.createElement('div');\n                    card.className = 'edit-level-card';\n                    \n                    const img = imageCache[level.imageId];\n                    const imgSrc = img ? img.src : '';\n                    \n                    // Build outfit variants HTML if they exist\n                    let outfitVariantsHTML = '';\n                    if (level.imageVariants) {\n                        outfitVariantsHTML = `\n                            <div class=\"edit-field\">\n                                <label class=\"edit-label\">Outfit Variants</label>\n                                <div style=\"display:flex;gap:10px;flex-wrap:wrap;\">\n                        `;\n                        \n                        for (const [outfitId, variantImageId] of Object.entries(level.imageVariants)) {\n                            const variantImg = imageCache[variantImageId];\n                            const variantSrc = variantImg ? variantImg.src : '';\n                            const outfitLabel = outfitNames[outfitId] || outfitId;\n                            const hasImage = variantSrc ? '' : 'style=\"background:#ddd;display:flex;align-items:center;justify-content:center;font-size:10px;color:#888;\"';\n                            \n                            outfitVariantsHTML += `\n                                <div style=\"text-align:center;\">\n                                    <div class=\"edit-level-thumb\" data-level=\"${index}\" data-imgsrc=\"${variantSrc}\" data-variant=\"${outfitId}\" title=\"Click to preview\" ${hasImage}>\n                                        ${variantSrc ? `<img src=\"${variantSrc}\" style=\"width:100%;height:100%;object-fit:cover;border-radius:8px;\">` : 'Missing'}\n                                    </div>\n                                    <div style=\"font-size:11px;color:#666;margin-top:4px;\">${outfitLabel}</div>\n                                </div>\n                            `;\n                        }\n                        \n                        outfitVariantsHTML += `\n                                </div>\n                            </div>\n                        `;\n                    }\n                    \n                    card.innerHTML = `\n                        <div class=\"edit-level-header\">\n                            <img class=\"edit-level-thumb\" src=\"${imgSrc}\" alt=\"Level ${index + 1}\" data-level=\"${index}\" data-imgsrc=\"${imgSrc}\" title=\"Click to preview\">\n                            <div class=\"edit-level-info\">\n                                <div class=\"edit-level-number\">Chapter ${index + 1}</div>\n                                <div style=\"font-weight:600;font-size:14px;\">${level.title}</div>\n                            </div>\n                        </div>\n                        ${outfitVariantsHTML}\n                        <div class=\"edit-field\">\n                            <label class=\"edit-label\">Title</label>\n                            <input type=\"text\" class=\"edit-input\" data-level=\"${index}\" data-field=\"title\" value=\"${level.title}\" maxlength=\"50\">\n                        </div>\n                        <div class=\"edit-field\">\n                            <label class=\"edit-label\">Grid Size (Rows \u00d7 Cols)</label>\n                            <div style=\"display:flex;gap:8px;align-items:center;\">\n                                <input type=\"number\" class=\"edit-input\" style=\"width:60px\" data-level=\"${index}\" data-field=\"gridRows\" value=\"${level.gridRows || 2}\" min=\"2\" max=\"11\">\n                                <span>\u00d7</span>\n                                <input type=\"number\" class=\"edit-input\" style=\"width:60px\" data-level=\"${index}\" data-field=\"gridCols\" value=\"${level.gridCols || 2}\" min=\"2\" max=\"11\">\n                                <span style=\"color:#888;font-size:12px;\">(${(level.gridRows || 2) * (level.gridCols || 2)} pieces)</span>\n                            </div>\n                        </div>\n                        <div class=\"edit-field\">\n                            <label class=\"edit-label\">Peek Opacity</label>\n                            <input type=\"number\" class=\"edit-input\" style=\"width:80px\" data-level=\"${index}\" data-field=\"peekOpacity\" value=\"${level.peekOpacity || 0.5}\" min=\"0\" max=\"1\" step=\"0.05\">\n                        </div>\n                        <div class=\"edit-field\">\n                            <label class=\"edit-label\">Story</label>\n                            <textarea class=\"edit-input edit-textarea\" data-level=\"${index}\" data-field=\"story\" maxlength=\"500\">${level.story}</textarea>\n                        </div>\n                    `;\n                    \n                    editLevelsContainer.appendChild(card);\n                });\n                \n                // Add event listeners\n                editLevelsContainer.querySelectorAll('.edit-input, .edit-select').forEach(input => {\n                    input.addEventListener('change', (e) => {\n                        const levelIndex = parseInt(e.target.dataset.level);\n                        const field = e.target.dataset.field;\n                        let value = e.target.value;\n                        \n                        if (field === 'gridRows' || field === 'gridCols') {\n                            value = parseInt(value);\n                        }\n                        if (field === 'peekOpacity') {\n                            value = parseFloat(value);\n                        }\n                        \n                        getLevels()[levelIndex][field] = value;\n                    });\n                });\n\n                // Add click listeners for image previews\n                editLevelsContainer.querySelectorAll('.edit-level-thumb').forEach(thumb => {\n                    thumb.addEventListener('click', (e) => {\n                        const levelIndex = parseInt(e.currentTarget.dataset.level);\n                        const variantId = e.currentTarget.dataset.variant;\n                        \n                        if (variantId) {\n                            setSelectedOutfit(variantId);\n                        }\n                        \n                        editPanel.classList.add('hidden');\n                        startLevel(levelIndex);\n                    });\n                });\n            }\n\n            // ==================== GAME PARAMETERS ====================\n            lib.showGameParameters({\n                name: 'Game Settings',\n                params: {\n                    'Reference on Level 1': {\n                        key: 'settings.referenceDefaultLevel1',\n                        type: 'checkbox'\n                    }\n                }\n            });\n\n            // ==================== STAR ICONS ====================\n            function loadStarIcons() {\n                const starAsset = lib.getAsset('star_icon');\n                if (starAsset) {\n                    document.getElementById('star-1-img').src = starAsset.url;\n                    document.getElementById('star-2-img').src = starAsset.url;\n                    document.getElementById('star-3-img').src = starAsset.url;\n                }\n            }\n\n            // ==================== BOOK ICON ====================\n            function loadBookIcon() {\n                const bookAsset = lib.getAsset('book_icon');\n                if (bookAsset) {\n                    document.getElementById('chapters-book-icon').src = bookAsset.url;\n                }\n            }\n\n            // ==================== DRESS ICON ====================\n            function loadDressIcon() {\n                const dressAsset = lib.getAsset('dress_icon');\n                if (dressAsset) {\n                    document.getElementById('outfit-dress-icon').src = dressAsset.url;\n                }\n            }\n\n            // ==================== OUTFIT BUTTON ====================\n            function loadOutfitButton() {\n                const outfitAsset = lib.getAsset('outfit_button');\n                if (outfitAsset) {\n                    outfitButtonImg.src = outfitAsset.url;\n                }\n            }\n            \n            // Outfit data shared between functions\n            const outfitData = [\n                { id: 'outfit_blue_dress', images: ['outfit_blue_dress'] },\n                { id: 'outfit_overalls', images: ['outfit_overalls'] },\n                { id: 'outfit_purple_shirt', images: ['outfit_purple_combined'] }\n            ];\n            \n            function updateOutfitGridContent(isUnlocked) {\n                const outfitGrid = document.getElementById('outfit-grid');\n                outfitGrid.innerHTML = '';\n                \n                if (!isUnlocked) {\n                    // Show silhouetted outfit options FIRST\n                    outfitData.forEach(outfit => {\n                        const option = document.createElement('div');\n                        option.className = 'outfit-option';\n                        option.style.cursor = 'not-allowed';\n                        option.style.opacity = '0.7';\n                        \n                        outfit.images.forEach(imageId => {\n                            const asset = lib.getAsset(imageId);\n                            if (asset) {\n                                const img = document.createElement('img');\n                                img.src = asset.url;\n                                img.alt = imageId;\n                                // Apply silhouette filter - dark shadow effect\n                                img.style.filter = 'brightness(0) opacity(0.6)';\n                                if (imageId === 'outfit_purple_combined') {\n                                    img.style.objectFit = 'cover';\n                                    img.style.objectPosition = 'center';\n                                }\n                                option.appendChild(img);\n                            }\n                        });\n                        \n                        outfitGrid.appendChild(option);\n                    });\n                    \n                    // Show unlock message AFTER silhouettes\n                    const header = document.createElement('div');\n                    header.className = 'outfit-coming-soon';\n                    header.style.padding = '20px 30px 10px 30px';\n                    \n                    // Get custom lock icon\n                    const lockAsset = lib.getAsset('custom_lock_icon');\n                    const lockIconHTML = lockAsset \n                        ? `<img src=\"${lockAsset.url}\" alt=\"Locked\">` \n                        : '\ud83d\udd12';\n                    \n                    header.innerHTML = `\n                        <div class=\"outfit-coming-soon-icon\" style=\"font-size:48px;margin-bottom:8px;\">${lockIconHTML}</div>\n                        <div class=\"outfit-coming-soon-title\" style=\"font-size:20px;\">Outfits Unlock in Chapter 4</div>\n                        <div class=\"outfit-coming-soon-text\" style=\"font-size:14px;\">Lebo arrives soon!</div>\n                    `;\n                    outfitGrid.appendChild(header);\n                    return;\n                }\n                \n                // Show actual outfit options\n                const currentSelectedOutfit = getSelectedOutfit();\n                \n                outfitData.forEach(outfit => {\n                    const option = document.createElement('div');\n                    option.className = 'outfit-option';\n                    option.dataset.outfitId = outfit.id;\n                    \n                    if (outfit.images.length > 1) {\n                        option.style.display = 'flex';\n                        option.style.justifyContent = 'space-evenly';\n                        option.style.alignItems = 'center';\n                        option.style.gap = '4px';\n                    }\n                    \n                    let allLoaded = true;\n                    outfit.images.forEach((imageId, imageIndex) => {\n                        const asset = lib.getAsset(imageId);\n                        if (asset) {\n                            const img = document.createElement('img');\n                            img.src = asset.url;\n                            img.alt = imageId;\n                            if (outfit.images.length > 1) {\n                                img.style.width = (100 / outfit.images.length - 5) + '%';\n                                img.style.height = '100%';\n                                img.style.objectFit = 'contain';\n                            }\n                            \n                            if (imageId === 'outfit_purple_combined') {\n                                img.style.objectFit = 'cover';\n                                img.style.objectPosition = 'center';\n                            }\n                            \n                            option.appendChild(img);\n                        } else {\n                            allLoaded = false;\n                        }\n                    });\n                    \n                    if (allLoaded) {\n                        // Mark current selection\n                        if (outfit.id === currentSelectedOutfit) {\n                            option.classList.add('selected');\n                        }\n                        \n                        option.addEventListener('click', () => {\n                            playSound('button_tap_sfx');\n                            outfitGrid.querySelectorAll('.outfit-option').forEach(o => o.classList.remove('selected'));\n                            option.classList.add('selected');\n                            \n                            // Save selected outfit to config\n                            setSelectedOutfit(outfit.id);\n                            \n                            // Close popup\n                            outfitPopup.classList.add('hidden');\n                            \n                            // Check if there's a pending level to start (from pre-chapter outfit selection)\n                            if (pendingLevelIndex !== null) {\n                                const levelToStart = pendingLevelIndex;\n                                const delayPreview = pendingDelayPreview;\n                                pendingLevelIndex = null;\n                                pendingDelayPreview = false;\n                                startLevel(levelToStart, delayPreview, true); // skipOutfitCheck = true\n                            } else if (gamePhase === 'playing' || gamePhase === 'completed') {\n                                // Restart current level to apply the outfit change\n                                startLevel(currentLevel, false, true);\n                            } else if (mode === 'edit' && gamePhase === 'menu') {\n                                // Refresh the edit panel to show current outfit selection\n                                renderEditPanel();\n                            }\n                        });\n                        \n                        outfitGrid.appendChild(option);\n                    }\n                });\n            }\n            \n            function loadOutfitOptions() {\n                // Initial load - will be updated when popup is shown\n                updateOutfitGridContent(false);\n            }\n\n            // ==================== INITIALIZATION ====================\n            async function init() {\n                // Show loading screen with bee logo\n                loadingScreen.style.display = 'flex';\n                \n                const beeAsset = lib.getAsset('bee_logo');\n                if (beeAsset) {\n                    document.getElementById('loading-bee').src = beeAsset.url;\n                }\n                \n                // Preload all assets\n                await preloadAllAssets();\n                \n                // Load title hero image\n                const titleAsset = lib.getAsset('Original.png');\n                if (titleAsset) {\n                    titleHeroImage.src = titleAsset.url;\n                }\n                \n                // Minimum loading time for smooth transition (at least 2 seconds)\n                await new Promise(resolve => setTimeout(resolve, 2000));\n                \n                // Fade out loading screen\n                loadingScreen.classList.add('fade-out');\n                \n                // Wait for fade animation to complete\n                await new Promise(resolve => setTimeout(resolve, 500));\n                \n                // Hide loading screen completely\n                loadingScreen.style.display = 'none';\n                \n                // Load star icons\n                loadStarIcons();\n                \n                // Load book icon for chapters\n                loadBookIcon();\n                \n                // Load dress icon for outfit popup\n                loadDressIcon();\n                \n                // Load outfit button\n                loadOutfitButton();\n                \n                // Load outfit options\n                loadOutfitOptions();\n                \n                if (mode === 'edit') {\n                    setupEditMode();\n                } else {\n                    // Show title screen\n                    showTitleScreen();\n                }\n            }\n\n            // Start the game\n            init();\n        }\n    </script>\n</body>\n</html>", "game_config": "{\"levels\":[{\"showPeekButton\":false,\"imageId\":\"level_1_scene\",\"peekOpacity\":0.6,\"title\":\"Nestled in Sunshine\",\"story\":\"Milo was born into a world full of warmth and love.\",\"gridCols\":2,\"gridRows\":2},{\"title\":\"Days of Tumbling\",\"imageId\":\"level_2_scene\",\"gridRows\":3,\"gridCols\":3,\"story\":\"Every day was filled with play and togetherness.\",\"peekOpacity\":0.8,\"showPeekButton\":true,\"initialVisibilityTime\":6000},{\"title\":\"Quiet Fields\",\"imageId\":\"level_3_scene\",\"gridRows\":3,\"gridCols\":4,\"story\":\"As his siblings found their forever homes, Milo's world grew quieter.\",\"peekOpacity\":0.7,\"showPeekButton\":true},{\"title\":\"A Wish Apart\",\"imageId\":\"level_4_scene\",\"gridRows\":4,\"gridCols\":4,\"story\":\"Somewhere nearby, someone was wishing for a friend.\",\"peekOpacity\":0.6,\"showPeekButton\":true,\"imageVariants\":{\"outfit_blue_dress\":\"level_4_scene\",\"outfit_overalls\":\"level_4_scene_overalls\",\"outfit_purple_shirt\":\"level_4_scene_purple\"}},{\"title\":\"What the Heart Misses\",\"imageId\":\"level_5_scene\",\"gridRows\":4,\"gridCols\":5,\"story\":\"Lebo knew exactly what her heart was missing.\",\"peekOpacity\":0.5,\"showPeekButton\":true,\"imageVariants\":{\"outfit_blue_dress\":\"level_5_scene\",\"outfit_overalls\":\"level_5_scene_overalls\",\"outfit_purple_shirt\":\"level_5_scene_purple\"},\"initialVisibilityTime\":5000},{\"title\":\"Still Waiting\",\"imageId\":\"level_6_scene\",\"gridRows\":5,\"gridCols\":5,\"story\":\"And far away, Milo was still waiting.\",\"peekOpacity\":0.4,\"showPeekButton\":true,\"initialVisibilityTime\":7000},{\"title\":\"The Day Everything Changed\",\"imageId\":\"level_7_scene\",\"gridRows\":5,\"gridCols\":6,\"story\":\"Then, everything changed.\",\"peekOpacity\":0.3,\"showPeekButton\":true,\"imageVariants\":{\"outfit_blue_dress\":\"level_7_scene\",\"outfit_overalls\":\"level_7_scene_overalls\",\"outfit_purple_shirt\":\"level_7_scene_purple\"},\"initialVisibilityTime\":9000},{\"title\":\"Two Stories Meet\",\"imageId\":\"level_8_scene\",\"gridRows\":6,\"gridCols\":6,\"story\":\"Two stories were about to become one.\",\"peekOpacity\":0.25,\"showPeekButton\":true,\"imageVariants\":{\"outfit_blue_dress\":\"level_8_scene\",\"outfit_overalls\":\"level_8_scene_overalls\",\"outfit_purple_shirt\":\"level_8_scene_purple\"},\"initialVisibilityTime\":11000},{\"title\":\"Never Alone Again\",\"imageId\":\"level_10a_scene\",\"gridRows\":6,\"gridCols\":7,\"story\":\"From that day on, they were never alone again.\",\"peekOpacity\":0.2,\"showPeekButton\":true,\"imageVariants\":{\"outfit_blue_dress\":\"level_10a_scene\",\"outfit_overalls\":\"level_10a_scene_overalls\",\"outfit_purple_shirt\":\"level_10a_scene_purple\"},\"initialVisibilityTime\":13000},{\"title\":\"Forever Home\",\"imageId\":\"level_10_scene\",\"gridRows\":7,\"gridCols\":7,\"story\":\"Milo had found his forever home.\",\"peekOpacity\":0.15,\"showPeekButton\":true,\"initialVisibilityTime\":15000}],\"completedLevels\":[],\"settings\":{\"referenceDefaultLevel1\":true},\"levelStars\":{},\"selectedOutfit\":\"outfit_purple_shirt\"}", "game_design_document": "## Core Game Loop and Mechanics\n\n**Moment-to-Moment Gameplay:**\nPlayers engage in a relaxing jigsaw puzzle experience where they:\n1. Select and drag puzzle pieces from the bottom tray\n2. Place pieces into the correct positions on the puzzle grid\n3. Optionally toggle a reference image overlay to guide placement\n4. Complete the puzzle to reveal the full illustration and story text\n5. Progress to the next level with increased difficulty\n\n**Progression System:**\n- Puzzles increase in complexity from 2\u00d72 (4 pieces) to 6\u00d76 (36 pieces) and beyond\n- Each completed puzzle unlocks the next level\n- Progress is saved automatically using window.gameConfig\n\n**Win Condition:**\nComplete the puzzle by correctly placing all pieces in their designated grid positions\n\n**Engagement Drivers:**\n- Beautiful Pixar-inspired illustrations that players want to see completed\n- Heartwarming stories revealed after each puzzle\n- Gradual difficulty progression that maintains challenge without frustration\n- Satisfying piece-snapping feedback when pieces are placed correctly\n- Calming, stress-free gameplay perfect for all ages\n\n## Starting Scope\n\n**First Implementation Must Include:**\n- 6 complete levels with unique illustrations and stories\n- Puzzle grid sizes: 2\u00d72, 3\u00d73, 4\u00d74, 5\u00d75, 6\u00d76, 7\u00d77\n- Drag-and-drop piece placement with snap-to-grid functionality\n- \"Show Picture\" reference toggle (on by default for Level 1)\n- Smooth state transition from puzzle mode to story reveal mode\n- Level selection screen showing completed and locked levels\n- Progress persistence using window.gameConfig\n- Touch-optimized controls for mobile\n- Celebratory completion animation and sound\n\n**Future Enhancements (Not in First Version):**\n- Additional levels beyond 7\u00d77\n- Hint system for stuck players\n- Timer and scoring system\n- Multiple story chapters\n- Piece rotation mechanics\n\n## Game Perspective\n\n**Fixed-screen 2D** with full-frame illustrated scenes designed for jigsaw segmentation. The game uses a portrait-oriented layout optimized for the 720\u00d71280 iframe.\n\n## Game World and Level Design\n\n**Level Structure:**\n\nEach level consists of:\n- A chapter title displayed at the top\n- A central puzzle grid area (\u5360\u636e\u5927\u90e8\u5206\u5c4f\u5e55\u7a7a\u95f4)\n- A bottom tray containing shuffled puzzle pieces\n- A \"Show Picture\" toggle button\n- Post-completion: full illustration with story text below\n\n**Six Starting Levels:**\n\n**Level 1: \"Best Friends\" (2\u00d72 grid)**\n- Scene: Milo and Lebo meeting for the first time in a sunny backyard\n- Story: \"Milo's eyes sparkled when she first saw the fluffy golden puppy. 'You're perfect!' she giggled. Lebo wagged his tail so hard his whole body wiggled. From that moment, they were best friends forever.\"\n\n**Level 2: \"Garden Adventure\" (3\u00d73 grid)**\n- Scene: Milo and Lebo exploring a colorful garden with butterflies and flowers\n- Story: \"In Grandma's garden, Milo and Lebo discovered a world of wonders. Butterflies danced around them as Lebo tried to catch one, making Milo laugh. 'Gentle, Lebo!' she reminded him, as they explored together.\"\n\n**Level 3: \"Rainy Day Fun\" (4\u00d74 grid)**\n- Scene: Milo and Lebo playing indoors with toys during a rainy day, window showing rain outside\n- Story: \"Pitter-patter went the rain on the window. But inside, Milo and Lebo were having the best time! They built a blanket fort, played with toys, and Lebo learned a new trick. Rainy days were the coziest.\"\n\n**Level 4: \"Beach Day\" (5\u00d75 grid)**\n- Scene: Milo building a sandcastle while Lebo digs enthusiastically beside her at the beach\n- Story: \"The beach was Lebo's favorite place! While Milo carefully built a magnificent sandcastle, Lebo dug the biggest hole ever. Sand flew everywhere, covering them both. Milo couldn't stop giggling at her silly, sandy puppy.\"\n\n**Level 5: \"Autumn Leaves\" (6\u00d76 grid)**\n- Scene: Milo and Lebo playing in a pile of colorful autumn leaves in a park\n- Story: \"Crunch, crunch, crunch! Milo jumped into the biggest leaf pile she'd ever seen. Lebo bounded after her, sending leaves flying like confetti. They played until the sun set, painting the sky orange and pink.\"\n\n**Level 6: \"Starry Night\" (7\u00d77 grid)**\n- Scene: Milo and Lebo lying on a blanket under a starry sky, looking up at the stars\n- Story: \"As stars twinkled above, Milo pointed at the brightest one. 'That's our star, Lebo,' she whispered. Lebo snuggled closer, his warm fur keeping her cozy. Together, they made wishes on every shooting star they saw.\"\n\n**Visual Composition Guidelines:**\n- Each scene should have a clear focal point (Milo and Lebo)\n- Balanced composition with foreground, midground, and background elements\n- Avoid excessive fine detail that would be difficult to distinguish when segmented\n- Use distinct color zones to help with piece identification\n- Maintain consistent lighting direction across the scene\n\n## Edit Mode Design\n\n**Editable Properties:**\n\nPlayers can customize the following in Edit mode:\n\n1. **Level Stories** (text fields)\n   - Edit the story text for each level\n   - Maximum 500 characters per story\n   - Stories must be appropriate for all ages\n\n2. **Level Titles** (text fields)\n   - Edit chapter titles for each level\n   - Maximum 50 characters\n\n3. **Grid Sizes** (dropdown per level)\n   - Choose from: 2\u00d72, 3\u00d73, 4\u00d74, 5\u00d75, 6\u00d76, 7\u00d77, 8\u00d78, 9\u00d79, 10\u00d710\n   - Cannot set Level 1 larger than 3\u00d73 (to maintain accessibility)\n\n4. **Reference Image Default** (toggle)\n   - Choose whether \"Show Picture\" is on or off by default for Level 1\n   - Other levels always start with reference off\n\n5. **Level Images** (image upload)\n   - Upload custom images for each level\n   - Images should be square or near-square aspect ratio\n   - Recommended minimum: 1000\u00d71000 pixels\n   - System will automatically segment uploaded images into puzzle pieces\n\n**Constraints:**\n- Minimum 2 levels, maximum 20 levels\n- Level 1 must remain 2\u00d72 or 3\u00d73 for new player accessibility\n- Stories must be under 500 characters\n- Uploaded images must be appropriate for all ages\n- Grid sizes cannot exceed 10\u00d710 (100 pieces) for performance\n\n**Edit Mode UI:**\n- Level list showing all levels with thumbnails\n- Click a level to edit its properties\n- \"Add Level\" button (if under 20 levels)\n- \"Delete Level\" button (if more than 2 levels)\n- \"Preview\" button to test the level\n- Changes save automatically to window.gameConfig\n\n## Control Design\n\n**Mobile (Touch) Controls:**\n\n*During Puzzle:*\n- **Tap and drag** a piece from the tray to move it\n- **Drag** the piece over the grid\n- **Release** to attempt placement (piece snaps if correct, returns to tray if wrong)\n- **Tap** \"Show Picture\" button to toggle reference overlay\n- Pieces in the tray can be scrolled horizontally if they don't all fit\n\n*After Completion:*\n- **Tap** \"Continue\" button to proceed to next level\n- **Scroll** to read story text if it extends beyond visible area\n\n**Desktop (Mouse/Keyboard) Controls:**\n\n*During Puzzle:*\n- **Click and drag** pieces from tray to grid\n- **Click** \"Show Picture\" button to toggle reference\n- **Mouse wheel** to scroll piece tray if needed\n\n*After Completion:*\n- **Click** \"Continue\" button\n- **Mouse wheel** to scroll story text\n\n**Control Implementation Details:**\n\n1. **Piece Dragging:**\n   - Track touch/mouse down on piece\n   - Move piece with finger/cursor (piece follows exactly)\n   - Visual feedback: piece scales up slightly (1.1x) while being dragged\n   - Drop zone highlighting: grid cell highlights when dragged piece is over it\n\n2. **Snap Behavior:**\n   - When piece is released within 40px of correct grid position, it snaps into place\n   - Snapping includes smooth animation (200ms ease-out)\n   - Correct placement triggers satisfying sound effect\n   - Incorrect placement returns piece to tray with gentle bounce animation\n\n3. **Reference Toggle:**\n   - Button positioned in top-right corner (safe from accidental taps)\n   - Toggle shows/hides semi-transparent (40% opacity) reference image overlay\n   - Overlay covers entire puzzle grid area\n   - Button shows current state: \"Show Picture\" or \"Hide Picture\"\n\n4. **Piece Tray:**\n   - Horizontal scrollable container at bottom\n   - Pieces arranged in single row with 8px spacing\n   - Smooth scroll with momentum on mobile\n   - Pieces shuffle randomly at level start\n\n5. **Touch Handling:**\n   - Prevent default touch behaviors to avoid scrolling during drag\n   - Track touch IDs to handle multi-touch correctly\n   - Clear all input states on window blur\n\n## Art Style and Visual Theme\n\n**Overall Art Direction:**\nPixar-inspired illustrated style with:\n- Soft, gradient shading with subtle highlights\n- Expressive character faces with large, emotive eyes\n- Rounded, friendly forms throughout\n- Warm, inviting color palettes\n- Gentle rim lighting to separate subjects from backgrounds\n- Painterly texture overlays for depth\n\n**Character Design (Milo):**\nBased on reference image 01KNKMXRG1R3E2XBSABSSCWFGM:\n- Young girl with large, expressive brown eyes\n- Two large, fluffy afro puffs with blue and white beaded hair ties\n- Warm brown skin tone with soft highlights\n- Bright, joyful smile showing personality\n- Blue dress with white polka dots and white ruffled hem\n- Blue beaded earrings matching hair accessories\n- Proportions: large head relative to body (child-like, appealing)\n\n**Character Design (Lebo):**\nBased on reference image 01KNKMXRG1R3E2XBSABSSCWFGM:\n- Golden retriever puppy with fluffy, textured fur\n- Large, friendly brown eyes with highlights\n- Black nose and warm, happy expression\n- Tongue often visible (playful personality)\n- Blue collar with yellow bone-shaped tag\n- Soft, rounded body proportions\n- Fur rendered with directional strokes for texture\n\n**Environment Style:**\n- Storybook-quality backgrounds with moderate detail\n- Layered depth: clear foreground, midground, background\n- Atmospheric perspective (distant elements lighter/softer)\n- Balanced composition avoiding visual clutter\n- Distinct color zones to aid puzzle piece identification\n- Natural lighting with warm tones (golden hour feel)\n\n**Color Palette:**\n- Primary: Warm blues, golden yellows, soft greens\n- Skin tones: Rich, warm browns with peachy highlights\n- Accents: Bright but not oversaturated colors\n- Backgrounds: Softer, desaturated versions of main palette\n- Consistent color temperature across all scenes (warm)\n\n**Asset Sizes:**\n- Full scene illustrations: 2048\u00d72048 pixels (high quality for segmentation)\n- UI elements: Sized appropriately for 720\u00d71280 viewport\n- Puzzle pieces: Generated dynamically from full illustrations\n\n**Rendering Approach:**\n- Full scene illustrations: Generated image assets\n- Puzzle grid and pieces: Canvas rendering (dynamic segmentation)\n- UI elements: CSS with rounded corners, soft shadows\n- Reference overlay: Semi-transparent canvas rendering\n\n**UI Visual Style:**\n- Rounded rectangles for all buttons (16px border radius)\n- Soft drop shadows (0 4px 8px rgba(0,0,0,0.1))\n- Friendly sans-serif font: Rounded, high legibility (Nunito or similar)\n- Button colors: Warm, inviting (blues and yellows matching theme)\n- Generous padding and spacing (minimum 16px)\n- Clear visual hierarchy with size and color\n\n**Animation Style:**\n- Smooth, eased transitions (ease-out curves)\n- Gentle bounces for feedback (spring physics)\n- Celebratory effects: Confetti, sparkles, scale pulses\n- Piece placement: Quick snap with slight overshoot\n- All animations under 500ms for responsiveness\n\n## Audio Design\n\n**Music:**\n- Soft, calm instrumental background music\n- Gentle piano or acoustic guitar melodies\n- Warm, major key progressions\n- Subtle orchestral padding\n- Looping seamlessly\n- Volume: Quiet enough to not distract (20-30% of max)\n\n**Sound Effects:**\n\n*Puzzle Gameplay:*\n- **Piece pickup**: Soft \"pop\" sound (light, airy)\n- **Piece placement (correct)**: Satisfying \"click\" or \"snap\" (medium pitch)\n- **Piece placement (incorrect)**: Gentle \"boop\" (lower pitch, non-punishing)\n- **Puzzle completion**: Cheerful chime sequence (ascending notes)\n\n*UI Interactions:*\n- **Button tap**: Soft \"tap\" sound\n- **Level unlock**: Gentle \"ding\" with sparkle\n- **Reference toggle**: Subtle \"whoosh\"\n\n*Story Reveal:*\n- **Transition to story**: Warm, magical \"shimmer\" sound\n- **Continue button**: Encouraging \"chirp\"\n\n**Audio Implementation:**\n- All sounds under 2 seconds duration\n- Sounds triggered by game events, not looping\n- Music fades out during story reveal, fades back in for next level\n- Volume controls in settings (music and SFX separate)\n- Sounds preloaded to avoid latency", "persistent_storage_config": null, "game_metadata": {"game_id": "01KNKMX93NYS0KVH3BAQ4T3CQD", "title": "Milo & Lebo\u2019s Puzzle Story", "description": "Assemble jigsaw puzzles to reveal heartwarming illustrated stories about Milo and her puppy Lebo's adventures!", "thumbnail_url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/IMG_7731_png_dd110bbb-91ae-409a-8f22-a56c85ba28c5.webp", "created_at": 1775554792.0, "updated_at": 1779773818047.1018, "art_style": null}}